From 6120218f9b5338fb93a07adb92813826a9b9ccbf Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Thu, 4 Feb 2021 03:37:11 +0200 Subject: [PATCH 001/385] Add UVC node bindings --- depthai-core | 2 +- src/pipeline/NodeBindings.cpp | 6 ++++++ src/pipeline/PipelineBindings.cpp | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 6be0cab6a..9d3aa5c67 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 6be0cab6a9c8c68d71e2a0ec800b0e09e5c7cc06 +Subproject commit 9d3aa5c67c12d507f3497213e4fbea00f9f7be33 diff --git a/src/pipeline/NodeBindings.cpp b/src/pipeline/NodeBindings.cpp index a1177bfde..2ac8652c6 100644 --- a/src/pipeline/NodeBindings.cpp +++ b/src/pipeline/NodeBindings.cpp @@ -12,6 +12,7 @@ #include "depthai/pipeline/node/SPIOut.hpp" #include "depthai/pipeline/node/DetectionNetwork.hpp" #include "depthai/pipeline/node/SystemLogger.hpp" +#include "depthai/pipeline/node/UVC.hpp" // Libraries #include "hedley/hedley.h" @@ -299,6 +300,11 @@ void NodeBindings::bind(pybind11::module& m){ .def("setRate", &SystemLogger::setRate) ; + // UVC node + py::class_>(m, "UVC") + .def_readonly("input", &UVC::input) + ; + //////////////////////////////////// // Node properties bindings diff --git a/src/pipeline/PipelineBindings.cpp b/src/pipeline/PipelineBindings.cpp index 306f30548..eebfed5ff 100644 --- a/src/pipeline/PipelineBindings.cpp +++ b/src/pipeline/PipelineBindings.cpp @@ -14,6 +14,7 @@ #include "depthai/pipeline/node/MonoCamera.hpp" #include "depthai/pipeline/node/StereoDepth.hpp" #include "depthai/pipeline/node/DetectionNetwork.hpp" +#include "depthai/pipeline/node/UVC.hpp" // depthai-shared #include "depthai-shared/pb/properties/GlobalProperties.hpp" @@ -65,6 +66,7 @@ void PipelineBindings::bind(pybind11::module& m){ .def("createStereoDepth", &Pipeline::create) .def("createMobileNetDetectionNetwork", &Pipeline::create) .def("createYoloDetectionNetwork", &Pipeline::create) + .def("createUVC", &Pipeline::create) ; From fe7966d6c9d38c1416f98c07ce690f22d926be65 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Thu, 4 Feb 2021 03:57:55 +0200 Subject: [PATCH 002/385] Add UVC example, streaming NV12 from ColorCamera `video` --- examples/19_uvc_video.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100755 examples/19_uvc_video.py diff --git a/examples/19_uvc_video.py b/examples/19_uvc_video.py new file mode 100755 index 000000000..5a320aa24 --- /dev/null +++ b/examples/19_uvc_video.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +import depthai as dai +import time + +# Start defining a pipeline +pipeline = dai.Pipeline() + +# Define a source - color camera +cam_rgb = pipeline.createColorCamera() +cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB) +cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) +cam_rgb.setInterleaved(False) + +# Create an UVC (USB Video Class) output node +uvc = pipeline.createUVC() +cam_rgb.video.link(uvc.input) + +# Pipeline defined, now the device is connected to +with dai.Device(pipeline) as device: + # Start pipeline + device.startPipeline() + + print("\nDevice started, please keep this process running") + print("and open an UVC viewer. Example on Linux:") + print(" guvcview -d /dev/video0") + print("\nTo close: Ctrl+C") + + while True: + try: + time.sleep(0.1) + except KeyboardInterrupt: + break From 8b2d21a31afeb735f09e957431b44b3f7dcc42ce Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Thu, 7 Jan 2021 06:07:42 +0200 Subject: [PATCH 003/385] ColorCamera add bindings: `isp`/`raw` outputs --- depthai-core | 2 +- src/pipeline/NodeBindings.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 9d3aa5c67..3088124a6 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 9d3aa5c67c12d507f3497213e4fbea00f9f7be33 +Subproject commit 3088124a6af64d72d490f60d97e27f590b795784 diff --git a/src/pipeline/NodeBindings.cpp b/src/pipeline/NodeBindings.cpp index 2ac8652c6..b031c43fe 100644 --- a/src/pipeline/NodeBindings.cpp +++ b/src/pipeline/NodeBindings.cpp @@ -94,6 +94,8 @@ void NodeBindings::bind(pybind11::module& m){ .def_readonly("video", &ColorCamera::video) .def_readonly("preview", &ColorCamera::preview) .def_readonly("still", &ColorCamera::still) + .def_readonly("isp", &ColorCamera::isp) + .def_readonly("raw", &ColorCamera::raw) .def("setCamId", [](ColorCamera& c, int64_t id) { // Issue an deprecation warning PyErr_WarnEx(PyExc_DeprecationWarning, "setCamId() is deprecated, use setBoardSocket() instead.", 1); From 320712e14c99f6c86439dbf6f4b9abb0f4826a65 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Fri, 12 Feb 2021 01:06:02 +0200 Subject: [PATCH 004/385] Update core: ColorCamera setIspScale --- depthai-core | 2 +- src/pipeline/NodeBindings.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 6e4906911..97249f8e5 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 6e4906911a94096cd393daaff496eea06c1af24b +Subproject commit 97249f8e5301a6f4532d5d3c9d565a4ebfb0d53e diff --git a/src/pipeline/NodeBindings.cpp b/src/pipeline/NodeBindings.cpp index 36de61c5a..123862923 100644 --- a/src/pipeline/NodeBindings.cpp +++ b/src/pipeline/NodeBindings.cpp @@ -151,6 +151,8 @@ void NodeBindings::bind(pybind11::module& m){ .def("getWaitForConfigInput", &ColorCamera::getWaitForConfigInput) .def("setPreviewKeepAspectRatio", &ColorCamera::setPreviewKeepAspectRatio) .def("getPreviewKeepAspectRatio", &ColorCamera::getPreviewKeepAspectRatio) + .def("setIspScale", &ColorCamera::setIspScale) + .def("setIspScaleFull", &ColorCamera::setIspScaleFull) ; // NeuralNetwork node From e70becfe1c1908c6148f842006b9986ebab39cab Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Fri, 12 Feb 2021 01:07:00 +0200 Subject: [PATCH 005/385] UVC example: enable 4K, downscaled to 1080p, fixed focus --- examples/19_uvc_video.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/19_uvc_video.py b/examples/19_uvc_video.py index 5a320aa24..86adc461c 100755 --- a/examples/19_uvc_video.py +++ b/examples/19_uvc_video.py @@ -3,18 +3,26 @@ import depthai as dai import time +enable_4k = True + # Start defining a pipeline pipeline = dai.Pipeline() # Define a source - color camera cam_rgb = pipeline.createColorCamera() cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB) -cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) cam_rgb.setInterleaved(False) +cam_rgb.initialControl.setManualFocus(130) + +if enable_4k: + cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) + cam_rgb.setIspScale(1, 2) +else: + cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) # Create an UVC (USB Video Class) output node uvc = pipeline.createUVC() -cam_rgb.video.link(uvc.input) +cam_rgb.isp.link(uvc.input) # Pipeline defined, now the device is connected to with dai.Device(pipeline) as device: From 0c2f38b8117e6907550b7eab1b420bf3418338ea Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Sun, 2 May 2021 23:15:39 +0300 Subject: [PATCH 006/385] Update UVC FW, update example: input changed from ColorCamera `isp` (YUV420p) to `video` (NV12) - as macOS doesn't work with YUV420p --- depthai-core | 2 +- examples/19_uvc_video.py | 4 ++-- src/pipeline/NodeBindings.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/depthai-core b/depthai-core index f3f4f2139..4eb7f7683 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit f3f4f2139cf56fc6fb8cc1fe52cb923d0f50cfd2 +Subproject commit 4eb7f76833af60ae9ec264314041b4ab41bbe5f6 diff --git a/examples/19_uvc_video.py b/examples/19_uvc_video.py index 86adc461c..eb8be3a9a 100755 --- a/examples/19_uvc_video.py +++ b/examples/19_uvc_video.py @@ -20,9 +20,9 @@ else: cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) -# Create an UVC (USB Video Class) output node +# Create an UVC (USB Video Class) output node. It needs 1920x1080, NV12 input uvc = pipeline.createUVC() -cam_rgb.isp.link(uvc.input) +cam_rgb.video.link(uvc.input) # Pipeline defined, now the device is connected to with dai.Device(pipeline) as device: diff --git a/src/pipeline/NodeBindings.cpp b/src/pipeline/NodeBindings.cpp index 56aff6108..aaf135574 100644 --- a/src/pipeline/NodeBindings.cpp +++ b/src/pipeline/NodeBindings.cpp @@ -451,7 +451,7 @@ void NodeBindings::bind(pybind11::module& m){ // UVC node py::class_>(m, "UVC") - .def_readonly("input", &UVC::input) + .def_readonly("input", &UVC::input, DOC(dai, node, UVC, input)) ; //////////////////////////////////// From 5494d2407ab2c32c2566770a94a8feb814d6efd9 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 20 Sep 2021 22:54:33 +0300 Subject: [PATCH 007/385] Rename 19_uvc_video.py -> rgb_uvc.py --- examples/{19_uvc_video.py => rgb_uvc.py} | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) rename examples/{19_uvc_video.py => rgb_uvc.py} (90%) diff --git a/examples/19_uvc_video.py b/examples/rgb_uvc.py similarity index 90% rename from examples/19_uvc_video.py rename to examples/rgb_uvc.py index eb8be3a9a..d90fd6938 100755 --- a/examples/19_uvc_video.py +++ b/examples/rgb_uvc.py @@ -3,7 +3,7 @@ import depthai as dai import time -enable_4k = True +enable_4k = True # Will downscale 4K -> 1080p # Start defining a pipeline pipeline = dai.Pipeline() @@ -26,14 +26,12 @@ # Pipeline defined, now the device is connected to with dai.Device(pipeline) as device: - # Start pipeline - device.startPipeline() - print("\nDevice started, please keep this process running") print("and open an UVC viewer. Example on Linux:") print(" guvcview -d /dev/video0") print("\nTo close: Ctrl+C") + # Doing nothing here, just keeping the host feeding the watchdog while True: try: time.sleep(0.1) From e8bbeabeed5b6b6ebdfd6f2a71c1b2348b0af44d Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 20 Sep 2021 22:57:33 +0300 Subject: [PATCH 008/385] Disable manual focus for now --- examples/rgb_uvc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rgb_uvc.py b/examples/rgb_uvc.py index d90fd6938..34c378099 100755 --- a/examples/rgb_uvc.py +++ b/examples/rgb_uvc.py @@ -12,7 +12,7 @@ cam_rgb = pipeline.createColorCamera() cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB) cam_rgb.setInterleaved(False) -cam_rgb.initialControl.setManualFocus(130) +#cam_rgb.initialControl.setManualFocus(130) if enable_4k: cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) From f513b0f27b4f07a9940cd9dfe65d7fd6deeaf29d Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Fri, 31 Dec 2021 16:33:42 +0200 Subject: [PATCH 009/385] Update core/XLink: `fix_run_with_uvc` --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index ebf00a503..9ac4971dc 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit ebf00a5032bf823e2be63adf499293c24ab545d5 +Subproject commit 9ac4971dc8779efa1978f1841bab670dc2eb4b9e From c188a67723295e863e9f08d6ca2179c1a39df551 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 14 Mar 2022 02:45:00 +0200 Subject: [PATCH 010/385] rgb_uvc.py: add flashing capability (for devices like IoT or Pro). Usage: python3 examples/rgb_uvc.py -fb -f ^ Will flash the bootloader, then the application (app firmware + UVC pipeline). Then the device should start automatically in UVC mode upon power-on. It's still possible to use the depthai library --- examples/rgb_uvc.py | 73 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/examples/rgb_uvc.py b/examples/rgb_uvc.py index 34c378099..00f7d76aa 100755 --- a/examples/rgb_uvc.py +++ b/examples/rgb_uvc.py @@ -2,30 +2,71 @@ import depthai as dai import time +import argparse enable_4k = True # Will downscale 4K -> 1080p -# Start defining a pipeline -pipeline = dai.Pipeline() +parser = argparse.ArgumentParser() +parser.add_argument('-fb', '--flash-bootloader', default=False, action="store_true") +parser.add_argument('-f', '--flash-app', default=False, action="store_true") +args = parser.parse_args() -# Define a source - color camera -cam_rgb = pipeline.createColorCamera() -cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB) -cam_rgb.setInterleaved(False) -#cam_rgb.initialControl.setManualFocus(130) +def getPipeline(): + # Start defining a pipeline + pipeline = dai.Pipeline() -if enable_4k: - cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) - cam_rgb.setIspScale(1, 2) -else: - cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) + # Define a source - color camera + cam_rgb = pipeline.createColorCamera() + cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB) + cam_rgb.setInterleaved(False) + #cam_rgb.initialControl.setManualFocus(130) -# Create an UVC (USB Video Class) output node. It needs 1920x1080, NV12 input -uvc = pipeline.createUVC() -cam_rgb.video.link(uvc.input) + if enable_4k: + cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) + cam_rgb.setIspScale(1, 2) + else: + cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) + + # Create an UVC (USB Video Class) output node. It needs 1920x1080, NV12 input + uvc = pipeline.createUVC() + cam_rgb.video.link(uvc.input) + + return pipeline + +# Workaround for a bug with the timeout-enabled bootloader +progressCalled = False +# TODO move this under flash(), will need to handle `progressCalled` differently +def progress(p): + global progressCalled + progressCalled = True + print(f'Flashing progress: {p*100:.1f}%') + +# Will flash the bootloader if no pipeline is provided as argument +def flash(pipeline=None): + (f, bl) = dai.DeviceBootloader.getFirstAvailableDevice() + bootloader = dai.DeviceBootloader(bl, True) + + startTime = time.monotonic() + if pipeline is None: + print("Flashing bootloader...") + bootloader.flashBootloader(progress) + else: + print("Flashing application pipeline...") + bootloader.flash(progress, pipeline) + + if not progressCalled: + raise RuntimeError('Flashing failed, please try again') + elapsedTime = round(time.monotonic() - startTime, 2) + print("Done in", elapsedTime, "seconds") + +if args.flash_bootloader or args.flash_app: + if args.flash_bootloader: flash() + if args.flash_app: flash(getPipeline()) + print("Flashing successful. Please power-cycle the device") + quit() # Pipeline defined, now the device is connected to -with dai.Device(pipeline) as device: +with dai.Device(getPipeline()) as device: print("\nDevice started, please keep this process running") print("and open an UVC viewer. Example on Linux:") print(" guvcview -d /dev/video0") From 50ae49aedfc8050be10339e2c4bda54e6270696a Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Wed, 13 Jul 2022 00:07:32 +0300 Subject: [PATCH 011/385] Update UVC node to latest from custom_uvc_uac_scripting --- src/pipeline/NodeBindings.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pipeline/NodeBindings.cpp b/src/pipeline/NodeBindings.cpp index cbcaf65f7..c0cf4c321 100644 --- a/src/pipeline/NodeBindings.cpp +++ b/src/pipeline/NodeBindings.cpp @@ -1318,6 +1318,9 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ // UVC node uvc .def_readonly("input", &UVC::input, DOC(dai, node, UVC, input)) + .def("setGpiosOnInit", &UVC::setGpiosOnInit, py::arg("list"), DOC(dai, node, UVC, setGpiosOnInit)) + .def("setGpiosOnStreamOn", &UVC::setGpiosOnStreamOn, py::arg("list"), DOC(dai, node, UVC, setGpiosOnStreamOn)) + .def("setGpiosOnStreamOff", &UVC::setGpiosOnStreamOff, py::arg("list"), DOC(dai, node, UVC, setGpiosOnStreamOff)) ; From 8fad35045cadc034332f683a36781d010c1f7157 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Tue, 9 Aug 2022 22:41:48 +0300 Subject: [PATCH 012/385] FW: fix UVC with USB2 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index e1b68a3a2..0c71697cc 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit e1b68a3a2020b875d5a875c80321fdccb87c042a +Subproject commit 0c71697cc8ea7d65c39227b0c46655bdbea44076 From 4ca3aa8183078f77f06cd8ab4687401dae868439 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Thu, 11 Aug 2022 03:49:31 +0300 Subject: [PATCH 013/385] FW: fix auto-exposure with large frame sizes (over 5000 W/H) --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 0c71697cc..42b329d43 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 0c71697cc8ea7d65c39227b0c46655bdbea44076 +Subproject commit 42b329d4352fc2c82f31bd181ede1c0678a39f3d From e6a4209ba1880a3c0f162a9ce2be5f34ba7579f6 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Wed, 7 Sep 2022 18:11:56 +0300 Subject: [PATCH 014/385] FW: IMX582 48MP config: 6.44 -> 10fps --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 42b329d43..9e5ca392f 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 42b329d4352fc2c82f31bd181ede1c0678a39f3d +Subproject commit 9e5ca392f54611f72cb2b1528901f27f48be6551 From 87a13df50d101d08b9f47f93dd34c9fb83c269f0 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Tue, 25 Oct 2022 19:02:17 +0300 Subject: [PATCH 015/385] FW: OV9282 720p/800p max FPS: 60 -> 120 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index c87740908..3ecbb1e8f 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit c87740908330100112bf12ea7f5aaf016fda89b2 +Subproject commit 3ecbb1e8f28f1b2e1a1e227b735526afabd87c05 From dc88bcd4d49e57f3524554b9703da27129b5fba3 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 8 Nov 2022 14:15:12 +0100 Subject: [PATCH 016/385] Added device side trace logging --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 0ba40d0f8..d38eb3bda 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 0ba40d0f8837d642c16c8d2b941df5aa92235563 +Subproject commit d38eb3bda395b7161cbfd094c8c5afccf1e149c5 From 7237b9cde852fd78a1fe80013cb6574a4aea7cdb Mon Sep 17 00:00:00 2001 From: petrnovota Date: Thu, 10 Nov 2022 12:18:23 +0100 Subject: [PATCH 017/385] installing python dependencies, creating virtual enviroment, downloading depthia from git --- docs/source/_static/install_dependencies.sh | 87 +++++++++++++++++---- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/docs/source/_static/install_dependencies.sh b/docs/source/_static/install_dependencies.sh index 9267743d1..9bd8dff6c 100755 --- a/docs/source/_static/install_dependencies.sh +++ b/docs/source/_static/install_dependencies.sh @@ -96,26 +96,11 @@ print_and_exec () { if [[ $(uname) == "Darwin" ]]; then echo "During Homebrew install, certain commands need 'sudo'. Requesting access..." sudo true - arch_cmd= - if [[ $(uname -m) == "arm64" ]]; then - arch_cmd="arch -x86_64" - echo "Running in native arm64 mode, will prefix commands with: $arch_cmd" - # Check if able to run with x86_64 emulation - retcode=0 - $arch_cmd true || retcode=$? - if [[ $retcode -ne 0 ]]; then - print_action "=== Installing Rosetta 2 - Apple binary translator" - # Prompts the user to agree to license: - # Could be automated by adding: --agree-to-license - print_and_exec softwareupdate --install-rosetta - fi - fi homebrew_install_url="https://raw.githubusercontent.com/Homebrew/install/master/install.sh" print_action "Installing Homebrew from $homebrew_install_url" # CI=1 will skip some interactive prompts - CI=1 $arch_cmd /bin/bash -c "$(curl -fsSL $homebrew_install_url)" - print_and_exec $arch_cmd brew install python3 git - print_and_exec python3 -m pip install -U pip + CI=1 /bin/bash -c "$(curl -fsSL $homebrew_install_url)" + print_and_exec brew install git echo echo "=== Installed successfully! IMPORTANT: For changes to take effect," echo "please close and reopen the terminal window, or run: exec \$SHELL" @@ -177,3 +162,71 @@ else echo "ERROR: Host not supported" exit 99 fi + +# clone depthai form git +git clone https://github.com/luxonis/depthai.git ~/depthai +cd depthai + +# install pyenv, python 3.10 and python dependencies +brew update +echo "installing pyenv, virtualenv and pyqt5" +brew install pyenv, pyenv-virtualenv + +# pip does not have pyqt5 for arm +if [[ $(uname -m) == 'arm64' ]]; then + brew install pyqt@5 +fi + +# pyenv installation guide from here: https://github.com/pyenv/pyenv + +PROFILE=$"~/.profile" +BASH_PROFILE=$"~/.bash_profile" +BASH_LOGIN=$"~/.bash_login" +BASH_PATHS=mypaths=( PROFILE BASH_PROFILE BASH_LOGIN ) + + +# Bash warning: There are some systems where the BASH_ENV variable is configured to point to .bashrc. On such systems, you should almost certainly put the eval "$(pyenv init -)" line into .bash_profile, and not into .bashrc +if [ "$BASH_ENV" = "~/.bashrc" ] + echo 'export PYENV_ROOT="$HOME/.pyenv"' >> $BASH_PROFILE + echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> $BASH_PROFILE + echo 'eval "$(pyenv init -)"' >> $BASH_PROFILE +else + echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc + echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc + echo 'eval "$(pyenv init -)"' >> ~/.bashrc +fi + +# if you have ~/.profile, ~/.bash_profile or ~/.bash_login, add the commands there as well. If you have none of these, add them to ~/.profile. +# case none of them exist +if [ ![-f "$PROFILE"] && ![-f $BASH_PROFILE] && ![-f $BASH_LOGIN] ]; then + echo 'export PYENV_ROOT="$HOME/.pyenv"' >> $PROFILE + echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> $PROFILE + echo 'eval "$(pyenv init -)"' >> $PROFILE +elif [ -f "$PROFILE" ] + echo 'export PYENV_ROOT="$HOME/.pyenv"' >> $PROFILE + echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> $PROFILE + echo 'eval "$(pyenv init -)"' >> $PROFILE +elif [ -f "$BASH_PROFILE" ] + echo 'export PYENV_ROOT="$HOME/.pyenv"' >> $BASH_PROFILE + echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> $BASH_PROFILE + echo 'eval "$(pyenv init -)"' >> $BASH_PROFILE +elif [ -f "$BASH_LOGIN" ] + echo 'export PYENV_ROOT="$HOME/.pyenv"' >> $BASH_LOGIN + echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> $BASH_LOGIN + echo 'eval "$(pyenv init -)"' >> $BASH_LOGIN +fi + +# reset shell +exec "$SHELL" + +if [ which pyenv ]; then + echo "installing python dependencies." + # install latest python 3.10 + pyenv install 3.10 + pyenv virtualenv 3.10 demo_app_venv + pyenv activate demo_app_venv + python install_requirements.py + +else + echo "Pyenv command does not work, pyenv setup was not successful." + exit 99 From d607fe02171cf1a40eff36c38dcdaa71c01162d9 Mon Sep 17 00:00:00 2001 From: petrnovota Date: Thu, 10 Nov 2022 17:30:46 +0100 Subject: [PATCH 018/385] macOS complete install and run script first draft --- ...cies.sh => install_global_dependencies.sh} | 82 ++++------------- .../_static/install_python_dependencies.sh | 27 ++++++ docs/source/_static/macOS_installer.sh | 87 +++++++++++++++++++ 3 files changed, 129 insertions(+), 67 deletions(-) rename docs/source/_static/{install_dependencies.sh => install_global_dependencies.sh} (67%) create mode 100755 docs/source/_static/install_python_dependencies.sh create mode 100755 docs/source/_static/macOS_installer.sh diff --git a/docs/source/_static/install_dependencies.sh b/docs/source/_static/install_global_dependencies.sh similarity index 67% rename from docs/source/_static/install_dependencies.sh rename to docs/source/_static/install_global_dependencies.sh index 9bd8dff6c..fd020ac87 100755 --- a/docs/source/_static/install_dependencies.sh +++ b/docs/source/_static/install_global_dependencies.sh @@ -163,70 +163,18 @@ else exit 99 fi -# clone depthai form git -git clone https://github.com/luxonis/depthai.git ~/depthai -cd depthai - -# install pyenv, python 3.10 and python dependencies -brew update -echo "installing pyenv, virtualenv and pyqt5" -brew install pyenv, pyenv-virtualenv - -# pip does not have pyqt5 for arm -if [[ $(uname -m) == 'arm64' ]]; then - brew install pyqt@5 -fi - -# pyenv installation guide from here: https://github.com/pyenv/pyenv - -PROFILE=$"~/.profile" -BASH_PROFILE=$"~/.bash_profile" -BASH_LOGIN=$"~/.bash_login" -BASH_PATHS=mypaths=( PROFILE BASH_PROFILE BASH_LOGIN ) - - -# Bash warning: There are some systems where the BASH_ENV variable is configured to point to .bashrc. On such systems, you should almost certainly put the eval "$(pyenv init -)" line into .bash_profile, and not into .bashrc -if [ "$BASH_ENV" = "~/.bashrc" ] - echo 'export PYENV_ROOT="$HOME/.pyenv"' >> $BASH_PROFILE - echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> $BASH_PROFILE - echo 'eval "$(pyenv init -)"' >> $BASH_PROFILE -else - echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc - echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc - echo 'eval "$(pyenv init -)"' >> ~/.bashrc -fi - -# if you have ~/.profile, ~/.bash_profile or ~/.bash_login, add the commands there as well. If you have none of these, add them to ~/.profile. -# case none of them exist -if [ ![-f "$PROFILE"] && ![-f $BASH_PROFILE] && ![-f $BASH_LOGIN] ]; then - echo 'export PYENV_ROOT="$HOME/.pyenv"' >> $PROFILE - echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> $PROFILE - echo 'eval "$(pyenv init -)"' >> $PROFILE -elif [ -f "$PROFILE" ] - echo 'export PYENV_ROOT="$HOME/.pyenv"' >> $PROFILE - echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> $PROFILE - echo 'eval "$(pyenv init -)"' >> $PROFILE -elif [ -f "$BASH_PROFILE" ] - echo 'export PYENV_ROOT="$HOME/.pyenv"' >> $BASH_PROFILE - echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> $BASH_PROFILE - echo 'eval "$(pyenv init -)"' >> $BASH_PROFILE -elif [ -f "$BASH_LOGIN" ] - echo 'export PYENV_ROOT="$HOME/.pyenv"' >> $BASH_LOGIN - echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> $BASH_LOGIN - echo 'eval "$(pyenv init -)"' >> $BASH_LOGIN -fi - -# reset shell -exec "$SHELL" - -if [ which pyenv ]; then - echo "installing python dependencies." - # install latest python 3.10 - pyenv install 3.10 - pyenv virtualenv 3.10 demo_app_venv - pyenv activate demo_app_venv - python install_requirements.py - -else - echo "Pyenv command does not work, pyenv setup was not successful." - exit 99 +echo "Finished installing global libraries." +this_dir=$(pwd) +export CURR_DIR="$this_dir" +echo "$CURR_DIR" + +WORKING_DIR="$HOME/depthai_demo_app" +export WORKING_DIR="$WORKING_DIR" +cp macOS_installer.sh "$WORKING_DIR" +cp install_python_dependencies.sh "$WORKING_DIR" +echo __________________________________ +# now only macos finished + +./macOS_installer.sh +# TODO: next script call is in the macOS_isntaller, otherwise I get [6] + 87718 suspended (tty input) ./install_global_dependencies.sh +#./install_python_dependencies.sh diff --git a/docs/source/_static/install_python_dependencies.sh b/docs/source/_static/install_python_dependencies.sh new file mode 100755 index 000000000..7a8732fc1 --- /dev/null +++ b/docs/source/_static/install_python_dependencies.sh @@ -0,0 +1,27 @@ +#!/bin/bash -i + +cd "$HOME/depthai_demo_app" +export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH +export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH +if which pyenv ; then + echo "installing python dependencies." + # install python 3.10.8 + if [ ! -d "$HOME/.pyenv/versions/3.10.8" ]; then + echo "installing python 3.10.8" + pyenv install 3.10.8 + fi + if [ ! -d "$HOME/.pyenv/versions/3.10.8/envs/demo_app_venv" ]; then + pyenv virtualenv 3.10.8 demo_app_venv + fi + which python + which python3 + pyenv activate demo_app_venv + which python + which python3 + python install_requirements.py + python depthai_demo.py + +else + echo "Pyenv command does not work, pyenv setup was not successful." + exit 99 +fi \ No newline at end of file diff --git a/docs/source/_static/macOS_installer.sh b/docs/source/_static/macOS_installer.sh new file mode 100755 index 000000000..b13180be3 --- /dev/null +++ b/docs/source/_static/macOS_installer.sh @@ -0,0 +1,87 @@ +#!/bin/bash -i + +# global vars +PROFILE="$HOME/.profile" +BASH_PROFILE="$HOME/.bash_profile" +BASH_LOGIN="$HOME/.bash_login" +BASH_PATHS=( PROFILE BASH_PROFILE BASH_LOGIN ) + +# shellcheck disable=SC2016 +PYENV_CMD1='export PYENV_ROOT="$HOME/.pyenv"' +# shellcheck disable=SC2016 +PYENV_CMD2='command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' +# shellcheck disable=SC2016 +PYENV_CMD3='eval "$(pyenv init -)"' + +write_in_file () { + # just make sure only strings are appended which are not in there yet + if ! grep -Fxq "$PYENV_CMD1" "$1" + then + echo "$PYENV_CMD1" >> "$1" + fi + if ! grep -Fxq "$PYENV_CMD2" "$1" + then + echo "$PYENV_CMD2" >> "$1" + fi + if ! grep -Fxq "$PYENV_CMD3" "$1" + then + echo "$PYENV_CMD3" >> "$1" + fi +} + +echo "Running macOS installer." +echo "Upgrading brew." +brew upgrade +# clone depthai form git +if [ -d "$WORKING_DIR" ]; then + echo "Demo app already downloaded. Checking out main and updating." + cd "$WORKING_DIR" + git checkout main + git pull +else + echo "Downloading demo app." + git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" + cd "$WORKING_DIR" +fi + +# install pyenv, python 3.10 and python dependencies +brew update +echo "installing pyenv, virtualenv" +brew install pyenv pyenv-virtualenv + +# pip does not have pyqt5 for arm +if [[ $(uname -m) == 'arm64' ]]; then + echo "Installing pyqt5 with homebrew." + brew install pyqt@5 +fi + +# pyenv installation guide from here: https://github.com/pyenv/pyenv + + + + + +# Bash warning: There are some systems where the BASH_ENV variable is configured to point to .bashrc. On such systems, you should almost certainly put the eval "$(pyenv init -)" line into .bash_profile, and not into .bashrc +if [ "$BASH_ENV" = "$HOME/.bashrc" ]; then + write_in_file >> "$BASH_PROFILE" +else + write_in_file "$HOME/.bashrc" +fi + +# if you have ~/.profile, ~/.bash_profile or ~/.bash_login, add the commands there as well. If you have none of these, add them to ~/.profile. +# case none of them exist +if [ ! -f "$PROFILE" ] && [ ! -f "$BASH_PROFILE" ] && [ ! -f "$BASH_LOGIN" ] ; then + echo "exporting pyenv setup to $PROFILE" + write_in_file "$PROFILE" +elif [ -f "$PROFILE" ]; then + echo "exporting pyenv setup to $PROFILE" + write_in_file "$PROFILE" +elif [ -f "$BASH_PROFILE" ]; then + echo "exporting pyenv setup to $BASH_PROFILE" + write_in_file "$BASH_PROFILE" +elif [ -f "$BASH_LOGIN" ]; then + echo "exporting pyenv setup to $BASH_LOGIN" + write_in_file "$BASH_LOGIN" +fi + +./install_python_dependencies.sh \ No newline at end of file From d7da5e33a996dcc7b8254dbb8ac6191a523a750a Mon Sep 17 00:00:00 2001 From: petrnovota Date: Thu, 10 Nov 2022 17:44:31 +0100 Subject: [PATCH 019/385] macOS complete install and run script first draft --- docs/source/_static/install_global_dependencies.sh | 4 ++-- docs/source/_static/macOS_installer.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/_static/install_global_dependencies.sh b/docs/source/_static/install_global_dependencies.sh index fd020ac87..18601c3b4 100755 --- a/docs/source/_static/install_global_dependencies.sh +++ b/docs/source/_static/install_global_dependencies.sh @@ -168,8 +168,8 @@ this_dir=$(pwd) export CURR_DIR="$this_dir" echo "$CURR_DIR" -WORKING_DIR="$HOME/depthai_demo_app" -export WORKING_DIR="$WORKING_DIR" +WORKING="$HOME/depthai_demo_app" +export WORKING_DIR="$WORKING" cp macOS_installer.sh "$WORKING_DIR" cp install_python_dependencies.sh "$WORKING_DIR" echo __________________________________ diff --git a/docs/source/_static/macOS_installer.sh b/docs/source/_static/macOS_installer.sh index b13180be3..6513fc091 100755 --- a/docs/source/_static/macOS_installer.sh +++ b/docs/source/_static/macOS_installer.sh @@ -40,7 +40,7 @@ if [ -d "$WORKING_DIR" ]; then git pull else echo "Downloading demo app." - git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" + git clone --single-branch --branch demo_app_installationhttps://github.com/luxonis/depthai.git "$WORKING_DIR" cd "$WORKING_DIR" fi From 52c7e70f287eab733cf6f6ef28f521c1ea9b8453 Mon Sep 17 00:00:00 2001 From: petrnovota Date: Thu, 10 Nov 2022 18:32:46 +0100 Subject: [PATCH 020/385] macOS complete install and run script first draft --- .../_static/install_global_dependencies.sh | 5 ++++- docs/source/_static/macOS_installer.sh | 17 +++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/source/_static/install_global_dependencies.sh b/docs/source/_static/install_global_dependencies.sh index 18601c3b4..a8a93f776 100755 --- a/docs/source/_static/install_global_dependencies.sh +++ b/docs/source/_static/install_global_dependencies.sh @@ -168,7 +168,10 @@ this_dir=$(pwd) export CURR_DIR="$this_dir" echo "$CURR_DIR" -WORKING="$HOME/depthai_demo_app" +export APP_NAME="depthai_demo_app" +WORKING="$HOME/$APP_NAME" + +mkdir "$WORKING" export WORKING_DIR="$WORKING" cp macOS_installer.sh "$WORKING_DIR" cp install_python_dependencies.sh "$WORKING_DIR" diff --git a/docs/source/_static/macOS_installer.sh b/docs/source/_static/macOS_installer.sh index 6513fc091..35a6143bf 100755 --- a/docs/source/_static/macOS_installer.sh +++ b/docs/source/_static/macOS_installer.sh @@ -33,16 +33,17 @@ echo "Running macOS installer." echo "Upgrading brew." brew upgrade # clone depthai form git -if [ -d "$WORKING_DIR" ]; then +cd "$HOME" +if [ -d "$APP_NAME" ]; then echo "Demo app already downloaded. Checking out main and updating." - cd "$WORKING_DIR" - git checkout main - git pull + else echo "Downloading demo app." - git clone --single-branch --branch demo_app_installationhttps://github.com/luxonis/depthai.git "$WORKING_DIR" - cd "$WORKING_DIR" + git clone https://github.com/luxonis/depthai.git "$APP_NAME" fi +cd "$APP_NAME" +git checkout demo_app_installation +git pull # install pyenv, python 3.10 and python dependencies brew update @@ -57,10 +58,6 @@ fi # pyenv installation guide from here: https://github.com/pyenv/pyenv - - - - # Bash warning: There are some systems where the BASH_ENV variable is configured to point to .bashrc. On such systems, you should almost certainly put the eval "$(pyenv init -)" line into .bash_profile, and not into .bashrc if [ "$BASH_ENV" = "$HOME/.bashrc" ]; then write_in_file >> "$BASH_PROFILE" From 9ccede14bca09cd35953f8ad92e03e7fad964136 Mon Sep 17 00:00:00 2001 From: petrnovota Date: Thu, 10 Nov 2022 18:36:33 +0100 Subject: [PATCH 021/385] macOS complete install and run script first draft --- docs/source/_static/install_global_dependencies.sh | 3 +-- docs/source/_static/macOS_installer.sh | 10 ++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/source/_static/install_global_dependencies.sh b/docs/source/_static/install_global_dependencies.sh index a8a93f776..dfae11372 100755 --- a/docs/source/_static/install_global_dependencies.sh +++ b/docs/source/_static/install_global_dependencies.sh @@ -170,9 +170,8 @@ echo "$CURR_DIR" export APP_NAME="depthai_demo_app" WORKING="$HOME/$APP_NAME" - -mkdir "$WORKING" export WORKING_DIR="$WORKING" + cp macOS_installer.sh "$WORKING_DIR" cp install_python_dependencies.sh "$WORKING_DIR" echo __________________________________ diff --git a/docs/source/_static/macOS_installer.sh b/docs/source/_static/macOS_installer.sh index 35a6143bf..46bcb9715 100755 --- a/docs/source/_static/macOS_installer.sh +++ b/docs/source/_static/macOS_installer.sh @@ -33,15 +33,17 @@ echo "Running macOS installer." echo "Upgrading brew." brew upgrade # clone depthai form git -cd "$HOME" -if [ -d "$APP_NAME" ]; then + +if [ -d "$WORKING_DIR" ]; then echo "Demo app already downloaded. Checking out main and updating." else echo "Downloading demo app." - git clone https://github.com/luxonis/depthai.git "$APP_NAME" + git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" fi -cd "$APP_NAME" +cp macOS_installer.sh "$WORKING_DIR" +cp install_python_dependencies.sh "$WORKING_DIR" +cd "$WORKING_DIR" git checkout demo_app_installation git pull From 492387bd3654b38a51ff102d27ef949e19113a01 Mon Sep 17 00:00:00 2001 From: Petr Novota Date: Thu, 10 Nov 2022 18:45:56 +0100 Subject: [PATCH 022/385] macOS installation done --- docs/source/_static/install_global_dependencies.sh | 7 +++---- docs/source/_static/install_python_dependencies.sh | 4 ++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/source/_static/install_global_dependencies.sh b/docs/source/_static/install_global_dependencies.sh index dfae11372..43efbebbd 100755 --- a/docs/source/_static/install_global_dependencies.sh +++ b/docs/source/_static/install_global_dependencies.sh @@ -172,11 +172,10 @@ export APP_NAME="depthai_demo_app" WORKING="$HOME/$APP_NAME" export WORKING_DIR="$WORKING" -cp macOS_installer.sh "$WORKING_DIR" -cp install_python_dependencies.sh "$WORKING_DIR" -echo __________________________________ # now only macos finished - +echo _____________________________ +echo "Calling macOS_installer.sh" +echo _____________________________ ./macOS_installer.sh # TODO: next script call is in the macOS_isntaller, otherwise I get [6] + 87718 suspended (tty input) ./install_global_dependencies.sh #./install_python_dependencies.sh diff --git a/docs/source/_static/install_python_dependencies.sh b/docs/source/_static/install_python_dependencies.sh index 7a8732fc1..4f6c95736 100755 --- a/docs/source/_static/install_python_dependencies.sh +++ b/docs/source/_static/install_python_dependencies.sh @@ -1,6 +1,8 @@ #!/bin/bash -i cd "$HOME/depthai_demo_app" +pwd +echo _______ export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH if which pyenv ; then @@ -18,6 +20,8 @@ if which pyenv ; then pyenv activate demo_app_venv which python which python3 + echo __________ + pwd python install_requirements.py python depthai_demo.py From 673452a1cd9079af1ca93db94f8f34b5e09576d7 Mon Sep 17 00:00:00 2001 From: petrnovota Date: Thu, 10 Nov 2022 19:39:39 +0100 Subject: [PATCH 023/385] macOS installation completed --- .../_static/install_python_dependencies.sh | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/docs/source/_static/install_python_dependencies.sh b/docs/source/_static/install_python_dependencies.sh index 4f6c95736..6a682e43b 100755 --- a/docs/source/_static/install_python_dependencies.sh +++ b/docs/source/_static/install_python_dependencies.sh @@ -1,10 +1,25 @@ #!/bin/bash -i +echo $'\n\n:::::::::: RUNNING PYTHON DEPENDENCY INSTALLER ::::::::::\n' + cd "$HOME/depthai_demo_app" -pwd -echo _______ -export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH -export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH + +if [[ ":$PYTHONPATH:" == *":/opt/homebrew/lib/python3.10/site-packages:"* ]]; then + echo "/opt/homebrew/lib/python3.10/site-packages already in PYTHONPATH" +else + export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH + echo "/opt/homebrew/lib/python3.10/site-packages added to PYTHONPATH" +fi + + + +if [[ ":$PATH:" == *":$WORKING_DIR:"* ]]; then + echo "$WORKING_DIR is already in PATH" +else + export PATH="$PATH:$WORKING_DIR" + echo "$WORKING_DIR added to PATH" +fi + if which pyenv ; then echo "installing python dependencies." # install python 3.10.8 @@ -15,14 +30,13 @@ if which pyenv ; then if [ ! -d "$HOME/.pyenv/versions/3.10.8/envs/demo_app_venv" ]; then pyenv virtualenv 3.10.8 demo_app_venv fi - which python - which python3 pyenv activate demo_app_venv - which python - which python3 - echo __________ - pwd python install_requirements.py + + echo $'\n\n:::::::::::::::: INSTALATION COMPLETED ::::::::::::::::\n' + echo $'\nTo run demo app write in terminal.' + read -rsp $'Press any key to finish and run the demo app...\n' -n1 key + echo "STARTING DEMO APP." python depthai_demo.py else From 458bd0bdd9183c0a9fa9ec18117e3725b33c70de Mon Sep 17 00:00:00 2001 From: petrnovota Date: Fri, 11 Nov 2022 00:41:40 +0100 Subject: [PATCH 024/385] Linux installer done --- .../_static/install_global_dependencies.sh | 28 +++++-- .../_static/install_python_dependencies.sh | 23 ++++-- docs/source/_static/linux_installer.sh | 73 +++++++++++++++++++ docs/source/_static/macOS_installer.sh | 3 +- 4 files changed, 109 insertions(+), 18 deletions(-) create mode 100755 docs/source/_static/linux_installer.sh diff --git a/docs/source/_static/install_global_dependencies.sh b/docs/source/_static/install_global_dependencies.sh index 43efbebbd..89996a030 100755 --- a/docs/source/_static/install_global_dependencies.sh +++ b/docs/source/_static/install_global_dependencies.sh @@ -165,17 +165,29 @@ fi echo "Finished installing global libraries." this_dir=$(pwd) +# to be used in next script export CURR_DIR="$this_dir" -echo "$CURR_DIR" export APP_NAME="depthai_demo_app" WORKING="$HOME/$APP_NAME" export WORKING_DIR="$WORKING" - -# now only macos finished -echo _____________________________ -echo "Calling macOS_installer.sh" -echo _____________________________ -./macOS_installer.sh -# TODO: next script call is in the macOS_isntaller, otherwise I get [6] + 87718 suspended (tty input) ./install_global_dependencies.sh +export PATH="$PATH":"$CURR_DIR" + +if [[ $(uname -s) == "Darwin" ]]; then + echo _____________________________ + echo "Calling macOS_installer.sh" + echo _____________________________ + macOS_installer.sh + install_python_dependencies.sh +elif [[ $(uname -s) == "Linux" ]]; then + echo _____________________________ + echo "Calling linux_installer.sh" + echo _____________________________ + linux_installer.sh + install_python_dependencies.sh +else + echo "Error: Host $(uname -s) not supported." + exit 99 +fi +# TODO: next script call is in the macOS_installer or in linux_installer, otherwise I get [6] + 87718 suspended (tty input) ./install_global_dependencies.sh #./install_python_dependencies.sh diff --git a/docs/source/_static/install_python_dependencies.sh b/docs/source/_static/install_python_dependencies.sh index 6a682e43b..1e9250c92 100755 --- a/docs/source/_static/install_python_dependencies.sh +++ b/docs/source/_static/install_python_dependencies.sh @@ -4,15 +4,19 @@ echo $'\n\n:::::::::: RUNNING PYTHON DEPENDENCY INSTALLER ::::::::::\n' cd "$HOME/depthai_demo_app" -if [[ ":$PYTHONPATH:" == *":/opt/homebrew/lib/python3.10/site-packages:"* ]]; then - echo "/opt/homebrew/lib/python3.10/site-packages already in PYTHONPATH" -else - export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH - echo "/opt/homebrew/lib/python3.10/site-packages added to PYTHONPATH" +# only on mac silicon point PYTHONPATH to pyqt5 installation via homebrew +if [[ $(uname -m) == 'arm64' ]]; then + if [[ $(uname -s) == "Darwin" ]]; then + if [[ ":$PYTHONPATH:" == *":/opt/homebrew/lib/python3.10/site-packages:"* ]]; then + echo "/opt/homebrew/lib/python3.10/site-packages already in PYTHONPATH" + else + export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH + echo "/opt/homebrew/lib/python3.10/site-packages added to PYTHONPATH" + fi + fi fi - if [[ ":$PATH:" == *":$WORKING_DIR:"* ]]; then echo "$WORKING_DIR is already in PATH" else @@ -20,6 +24,9 @@ else echo "$WORKING_DIR added to PATH" fi +# enable using demo_app to start the demo. its a script in the project directory +echo 'export PATH=$PATH:"$WORKING_DIR"' >> "$HOME"/.bashrc + if which pyenv ; then echo "installing python dependencies." # install python 3.10.8 @@ -35,9 +42,9 @@ if which pyenv ; then echo $'\n\n:::::::::::::::: INSTALATION COMPLETED ::::::::::::::::\n' echo $'\nTo run demo app write in terminal.' - read -rsp $'Press any key to finish and run the demo app...\n' -n1 key + read -rsp $'Press ANY KEY to finish and run the demo app...\n' -n1 key echo "STARTING DEMO APP." - python depthai_demo.py + python depthai_demo.py -usbs usb2 else echo "Pyenv command does not work, pyenv setup was not successful." diff --git a/docs/source/_static/linux_installer.sh b/docs/source/_static/linux_installer.sh new file mode 100755 index 000000000..8739147a2 --- /dev/null +++ b/docs/source/_static/linux_installer.sh @@ -0,0 +1,73 @@ +#!/bin/bash -i + +# global vars +BASHRC="$HOME/.bashrc" + +PYENV_CMD1='export PATH="$HOME/.pyenv/bin:$PATH"' +PYENV_CMD2='eval "$(pyenv init --path)"' +PYENV_CMD3='eval "$(pyenv init -)"' +PYENV_CMD4='eval "$(pyenv virtualenv-init -)"' + +write_in_file () { + # just make sure only strings are appended which are not in there yet + if ! grep -Fxq "$PYENV_CMD1" "$1" + then + echo "$PYENV_CMD1" >> "$1" + fi + if ! grep -Fxq "$PYENV_CMD2" "$1" + then + echo "$PYENV_CMD2" >> "$1" + fi + if ! grep -Fxq "$PYENV_CMD3" "$1" + then + echo "$PYENV_CMD3" >> "$1" + fi + if ! grep -Fxq "$PYENV_CMD4" "$1" + then + echo "$PYENV_CMD3" >> "$1" + fi +} + +echo '\nRunning Linux installer.' +echo "Upgrading sudo-apt." + +sudo apt-get update +sudo apt-get install make build-essential libssl-dev zlib1g-dev \ +libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \ +libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev + + +# clone depthai form git + +if [ -d "$WORKING_DIR" ]; then + echo "Demo app already downloaded. Checking out main and updating." + +else + echo "Downloading demo app." + git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" +fi + +cp ./install_python_dependencies.sh "$WORKING_DIR" +cd "$WORKING_DIR" +git checkout demo_app_installation +git pull + +# install pyenv +echo "installing pyenv, virtualenv" + +if [ ! -d "$HOME/.pyenv/" ]; then + curl https://pyenv.run | bash +else + echo "pyenv is already installed" +fi + +# pip does not have pyqt5 for arm but for linux it is in the global dependencies +#if [[ $(uname -m) == 'arm64' ]]; then +# echo "Installing pyqt5 with homebrew." +# sudo apt-get install python3-pyqt5 +#fi + +# pyenv installation guide from here: https://brain2life.hashnode.dev/how-to-install-pyenv-python-version-manager-on-ubuntu-2004 +write_in_file "$BASHRC" +# activate pyenv +# exec "$SHELL" \ No newline at end of file diff --git a/docs/source/_static/macOS_installer.sh b/docs/source/_static/macOS_installer.sh index 46bcb9715..ef833a16e 100755 --- a/docs/source/_static/macOS_installer.sh +++ b/docs/source/_static/macOS_installer.sh @@ -41,7 +41,6 @@ else echo "Downloading demo app." git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" fi -cp macOS_installer.sh "$WORKING_DIR" cp install_python_dependencies.sh "$WORKING_DIR" cd "$WORKING_DIR" git checkout demo_app_installation @@ -62,7 +61,7 @@ fi # Bash warning: There are some systems where the BASH_ENV variable is configured to point to .bashrc. On such systems, you should almost certainly put the eval "$(pyenv init -)" line into .bash_profile, and not into .bashrc if [ "$BASH_ENV" = "$HOME/.bashrc" ]; then - write_in_file >> "$BASH_PROFILE" + write_in_file "$BASH_PROFILE" else write_in_file "$HOME/.bashrc" fi From a61205d561401ebcf5b5c0afea8286cc446cda5e Mon Sep 17 00:00:00 2001 From: petrnovota Date: Fri, 11 Nov 2022 14:54:41 +0100 Subject: [PATCH 025/385] demo app installation using launcher --- .../_static/install_global_dependencies.sh | 4 +- .../_static/install_python_dependencies.sh | 52 ------------ docs/source/_static/linux_installer.sh | 65 ++++----------- docs/source/_static/macOS_installer.sh | 81 ++++++------------- 4 files changed, 42 insertions(+), 160 deletions(-) delete mode 100755 docs/source/_static/install_python_dependencies.sh diff --git a/docs/source/_static/install_global_dependencies.sh b/docs/source/_static/install_global_dependencies.sh index 89996a030..63de1af53 100755 --- a/docs/source/_static/install_global_dependencies.sh +++ b/docs/source/_static/install_global_dependencies.sh @@ -168,7 +168,7 @@ this_dir=$(pwd) # to be used in next script export CURR_DIR="$this_dir" -export APP_NAME="depthai_demo_app" +export APP_NAME="depthai" WORKING="$HOME/$APP_NAME" export WORKING_DIR="$WORKING" export PATH="$PATH":"$CURR_DIR" @@ -189,5 +189,3 @@ else echo "Error: Host $(uname -s) not supported." exit 99 fi -# TODO: next script call is in the macOS_installer or in linux_installer, otherwise I get [6] + 87718 suspended (tty input) ./install_global_dependencies.sh -#./install_python_dependencies.sh diff --git a/docs/source/_static/install_python_dependencies.sh b/docs/source/_static/install_python_dependencies.sh deleted file mode 100755 index 1e9250c92..000000000 --- a/docs/source/_static/install_python_dependencies.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -i - -echo $'\n\n:::::::::: RUNNING PYTHON DEPENDENCY INSTALLER ::::::::::\n' - -cd "$HOME/depthai_demo_app" - -# only on mac silicon point PYTHONPATH to pyqt5 installation via homebrew -if [[ $(uname -m) == 'arm64' ]]; then - if [[ $(uname -s) == "Darwin" ]]; then - if [[ ":$PYTHONPATH:" == *":/opt/homebrew/lib/python3.10/site-packages:"* ]]; then - echo "/opt/homebrew/lib/python3.10/site-packages already in PYTHONPATH" - else - export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH - echo "/opt/homebrew/lib/python3.10/site-packages added to PYTHONPATH" - fi - fi -fi - - -if [[ ":$PATH:" == *":$WORKING_DIR:"* ]]; then - echo "$WORKING_DIR is already in PATH" -else - export PATH="$PATH:$WORKING_DIR" - echo "$WORKING_DIR added to PATH" -fi - -# enable using demo_app to start the demo. its a script in the project directory -echo 'export PATH=$PATH:"$WORKING_DIR"' >> "$HOME"/.bashrc - -if which pyenv ; then - echo "installing python dependencies." - # install python 3.10.8 - if [ ! -d "$HOME/.pyenv/versions/3.10.8" ]; then - echo "installing python 3.10.8" - pyenv install 3.10.8 - fi - if [ ! -d "$HOME/.pyenv/versions/3.10.8/envs/demo_app_venv" ]; then - pyenv virtualenv 3.10.8 demo_app_venv - fi - pyenv activate demo_app_venv - python install_requirements.py - - echo $'\n\n:::::::::::::::: INSTALATION COMPLETED ::::::::::::::::\n' - echo $'\nTo run demo app write in terminal.' - read -rsp $'Press ANY KEY to finish and run the demo app...\n' -n1 key - echo "STARTING DEMO APP." - python depthai_demo.py -usbs usb2 - -else - echo "Pyenv command does not work, pyenv setup was not successful." - exit 99 -fi \ No newline at end of file diff --git a/docs/source/_static/linux_installer.sh b/docs/source/_static/linux_installer.sh index 8739147a2..cb70f7e37 100755 --- a/docs/source/_static/linux_installer.sh +++ b/docs/source/_static/linux_installer.sh @@ -1,41 +1,9 @@ #!/bin/bash -i -# global vars -BASHRC="$HOME/.bashrc" - -PYENV_CMD1='export PATH="$HOME/.pyenv/bin:$PATH"' -PYENV_CMD2='eval "$(pyenv init --path)"' -PYENV_CMD3='eval "$(pyenv init -)"' -PYENV_CMD4='eval "$(pyenv virtualenv-init -)"' - -write_in_file () { - # just make sure only strings are appended which are not in there yet - if ! grep -Fxq "$PYENV_CMD1" "$1" - then - echo "$PYENV_CMD1" >> "$1" - fi - if ! grep -Fxq "$PYENV_CMD2" "$1" - then - echo "$PYENV_CMD2" >> "$1" - fi - if ! grep -Fxq "$PYENV_CMD3" "$1" - then - echo "$PYENV_CMD3" >> "$1" - fi - if ! grep -Fxq "$PYENV_CMD4" "$1" - then - echo "$PYENV_CMD3" >> "$1" - fi -} - echo '\nRunning Linux installer.' echo "Upgrading sudo-apt." sudo apt-get update -sudo apt-get install make build-essential libssl-dev zlib1g-dev \ -libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \ -libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev - # clone depthai form git @@ -47,27 +15,24 @@ else git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" fi -cp ./install_python_dependencies.sh "$WORKING_DIR" cd "$WORKING_DIR" -git checkout demo_app_installation +git checkout demo_app_installation_v2 git pull -# install pyenv -echo "installing pyenv, virtualenv" +# install python 3.10 +echo "installing python 3.10" -if [ ! -d "$HOME/.pyenv/" ]; then - curl https://pyenv.run | bash -else - echo "pyenv is already installed" -fi +sudo add-apt-repository ppa:deadsnakes/ppa +sudo apt install python3.10 + +# create python virtual environment +echo "Creating python virtual environment in $WORKING_DIR/venv" -# pip does not have pyqt5 for arm but for linux it is in the global dependencies -#if [[ $(uname -m) == 'arm64' ]]; then -# echo "Installing pyqt5 with homebrew." -# sudo apt-get install python3-pyqt5 -#fi +python3.10 -m venv "$WORKING_DIR/venv" +# activate environment +source "$WORKING_DIR/venv/bin/activate" +pip install --upgrade pip -# pyenv installation guide from here: https://brain2life.hashnode.dev/how-to-install-pyenv-python-version-manager-on-ubuntu-2004 -write_in_file "$BASHRC" -# activate pyenv -# exec "$SHELL" \ No newline at end of file +pip install packaging +# run launcher +python "$WORKING_DIR/launcher/launcher.py" -r "$WORKING_DIR" diff --git a/docs/source/_static/macOS_installer.sh b/docs/source/_static/macOS_installer.sh index ef833a16e..a8cddc753 100755 --- a/docs/source/_static/macOS_installer.sh +++ b/docs/source/_static/macOS_installer.sh @@ -1,34 +1,5 @@ #!/bin/bash -i -# global vars -PROFILE="$HOME/.profile" -BASH_PROFILE="$HOME/.bash_profile" -BASH_LOGIN="$HOME/.bash_login" -BASH_PATHS=( PROFILE BASH_PROFILE BASH_LOGIN ) - -# shellcheck disable=SC2016 -PYENV_CMD1='export PYENV_ROOT="$HOME/.pyenv"' -# shellcheck disable=SC2016 -PYENV_CMD2='command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' -# shellcheck disable=SC2016 -PYENV_CMD3='eval "$(pyenv init -)"' - -write_in_file () { - # just make sure only strings are appended which are not in there yet - if ! grep -Fxq "$PYENV_CMD1" "$1" - then - echo "$PYENV_CMD1" >> "$1" - fi - if ! grep -Fxq "$PYENV_CMD2" "$1" - then - echo "$PYENV_CMD2" >> "$1" - fi - if ! grep -Fxq "$PYENV_CMD3" "$1" - then - echo "$PYENV_CMD3" >> "$1" - fi -} - echo "Running macOS installer." echo "Upgrading brew." brew upgrade @@ -41,15 +12,14 @@ else echo "Downloading demo app." git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" fi -cp install_python_dependencies.sh "$WORKING_DIR" cd "$WORKING_DIR" -git checkout demo_app_installation +git checkout demo_app_installation_v2 git pull -# install pyenv, python 3.10 and python dependencies +# install python 3.10 and python dependencies brew update -echo "installing pyenv, virtualenv" -brew install pyenv pyenv-virtualenv +echo "installing python 3.10" +brew install python@3.10 # pip does not have pyqt5 for arm if [[ $(uname -m) == 'arm64' ]]; then @@ -57,29 +27,30 @@ if [[ $(uname -m) == 'arm64' ]]; then brew install pyqt@5 fi -# pyenv installation guide from here: https://github.com/pyenv/pyenv +# create python virtual environment +echo "Creating python virtual environment in $WORKING_DIR/venv" + +python3.10 -m venv "$WORKING_DIR/venv" +# activate environment +source "$WORKING_DIR/venv/bin/activate" +pip install --upgrade pip -# Bash warning: There are some systems where the BASH_ENV variable is configured to point to .bashrc. On such systems, you should almost certainly put the eval "$(pyenv init -)" line into .bash_profile, and not into .bashrc -if [ "$BASH_ENV" = "$HOME/.bashrc" ]; then - write_in_file "$BASH_PROFILE" +# install launcher dependencies +# only on mac silicon point PYTHONPATH to pyqt5 installation via homebrew, otherwise install pyqth5 with pip +if [[ $(uname -m) == 'arm64' ]]; then + #if [[ $(uname -s) == "Darwin" ]]; then + if [[ ":$PYTHONPATH:" == *":/opt/homebrew/lib/python3.10/site-packages:"* ]]; then + echo "/opt/homebrew/lib/python3.10/site-packages already in PYTHONPATH" + else + export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH + echo "/opt/homebrew/lib/python3.10/site-packages added to PYTHONPATH" + fi + #fi else - write_in_file "$HOME/.bashrc" + pip install pyqt5 fi -# if you have ~/.profile, ~/.bash_profile or ~/.bash_login, add the commands there as well. If you have none of these, add them to ~/.profile. -# case none of them exist -if [ ! -f "$PROFILE" ] && [ ! -f "$BASH_PROFILE" ] && [ ! -f "$BASH_LOGIN" ] ; then - echo "exporting pyenv setup to $PROFILE" - write_in_file "$PROFILE" -elif [ -f "$PROFILE" ]; then - echo "exporting pyenv setup to $PROFILE" - write_in_file "$PROFILE" -elif [ -f "$BASH_PROFILE" ]; then - echo "exporting pyenv setup to $BASH_PROFILE" - write_in_file "$BASH_PROFILE" -elif [ -f "$BASH_LOGIN" ]; then - echo "exporting pyenv setup to $BASH_LOGIN" - write_in_file "$BASH_LOGIN" -fi +pip install packaging -./install_python_dependencies.sh \ No newline at end of file +# run launcher +python "$WORKING_DIR/launcher/launcher.py" -r "$WORKING_DIR" From b72b4523b869dbf741fc51ec2c522c21f4a632a4 Mon Sep 17 00:00:00 2001 From: petrnovota Date: Fri, 11 Nov 2022 16:41:01 +0100 Subject: [PATCH 026/385] Linux installer done --- docs/source/_static/linux_installer.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/source/_static/linux_installer.sh b/docs/source/_static/linux_installer.sh index cb70f7e37..8ecb24875 100755 --- a/docs/source/_static/linux_installer.sh +++ b/docs/source/_static/linux_installer.sh @@ -23,7 +23,9 @@ git pull echo "installing python 3.10" sudo add-apt-repository ppa:deadsnakes/ppa -sudo apt install python3.10 +sudo apt -y install python3.10 +sudo apt -y install python3.10-venv + # create python virtual environment echo "Creating python virtual environment in $WORKING_DIR/venv" @@ -34,5 +36,7 @@ source "$WORKING_DIR/venv/bin/activate" pip install --upgrade pip pip install packaging +pip install pyqt5 # run launcher -python "$WORKING_DIR/launcher/launcher.py" -r "$WORKING_DIR" +echo '\n>>>>>>>>>>>> RUNNING LAUNCHER <<<<<<<<<<<<<<<<<<<\n' +python3.10 "$WORKING_DIR/launcher/launcher.py" -r "$WORKING_DIR" From 7c06771138484197a2e6e12f26431b46fbfce8eb Mon Sep 17 00:00:00 2001 From: luxonis Date: Sat, 12 Nov 2022 12:54:27 +0100 Subject: [PATCH 027/385] linux installer completed --- .../_static/install_global_dependencies.sh | 124 +++++++++++++++++- docs/source/_static/linux_installer.sh | 5 +- docs/source/_static/macOS_installer.sh | 1 + 3 files changed, 124 insertions(+), 6 deletions(-) diff --git a/docs/source/_static/install_global_dependencies.sh b/docs/source/_static/install_global_dependencies.sh index 63de1af53..1f553b84b 100755 --- a/docs/source/_static/install_global_dependencies.sh +++ b/docs/source/_static/install_global_dependencies.sh @@ -2,6 +2,15 @@ trap 'RET=$? ; echo -e >&2 "\n\x1b[31mFailed installing dependencies. Could be a bug in the installer or unsupported platform. Open a bug report over at https://github.com/luxonis/depthai - exited with status $RET at line $LINENO \x1b[0m\n" ; exit $RET' ERR +write_in_file () { + # just make sure only strings are appended which are not in there yet + # first arg is text to write, second arg is the file path + if ! grep -Fxq "$1" "$2" + then + echo "$1" >> "$2" + fi +} + readonly linux_pkgs=( python3 python3-pip @@ -164,6 +173,8 @@ else fi echo "Finished installing global libraries." + +# create environment variables and save important ones into .bashrc this_dir=$(pwd) # to be used in next script export CURR_DIR="$this_dir" @@ -173,19 +184,124 @@ WORKING="$HOME/$APP_NAME" export WORKING_DIR="$WORKING" export PATH="$PATH":"$CURR_DIR" +# add depthai working dir to .bashrc if its not already there +COMMENT='# Home directory of depthai and depthai app, enables to run in terminal' +BASHRC="$HOME/.bashrc" +ADD_DEPTHAI_TO_PATH='export PATH=$PATH'":$WORKING_DIR" +ADD_LUXONIS_DEPTHAI_HOME='export LUXONIS_DEPTHAI_HOME='"$WORKING_DIR" + +# add to .bashrc only if it is not in there already +write_in_file "$COMMENT" "$BASHRC" +write_in_file "$ADD_DEPTHAI_TO_PATH" "$BASHRC" +write_in_file "$ADD_LUXONIS_DEPTHAI_HOME" "$BASHRC" + + if [[ $(uname -s) == "Darwin" ]]; then echo _____________________________ echo "Calling macOS_installer.sh" echo _____________________________ - macOS_installer.sh - install_python_dependencies.sh + echo "Running macOS installer." + echo "Upgrading brew." + brew upgrade + + # clone depthai form git + if [ -d "$WORKING_DIR" ]; then + echo "Demo app already downloaded. Checking out main and updating." + else + echo "Downloading demo app." + git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" + fi + cd "$WORKING_DIR" + git fetch + git checkout demo_app_installation_v2 + git pull + + # install python 3.10 and python dependencies + brew update + echo "installing python 3.10" + brew install python@3.10 + + # pip does not have pyqt5 for arm + if [[ $(uname -m) == 'arm64' ]]; then + echo "Installing pyqt5 with homebrew." + brew install pyqt@5 + fi + + # create python virtual environment + echo "Creating python virtual environment in $WORKING_DIR/venv" + + python3.10 -m venv "$WORKING_DIR/venv" + # activate environment + source "$WORKING_DIR/venv/bin/activate" + pip install --upgrade pip + + # install launcher dependencies + # only on mac silicon point PYTHONPATH to pyqt5 installation via homebrew, otherwise install pyqth5 with pip + if [[ $(uname -m) == 'arm64' ]]; then + #if [[ $(uname -s) == "Darwin" ]]; then + if [[ ":$PYTHONPATH:" == *":/opt/homebrew/lib/python3.10/site-packages:"* ]]; then + echo "/opt/homebrew/lib/python3.10/site-packages already in PYTHONPATH" + else + export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH + echo "/opt/homebrew/lib/python3.10/site-packages added to PYTHONPATH" + fi + #fi + else + pip install pyqt5 + fi + + pip install packaging + elif [[ $(uname -s) == "Linux" ]]; then echo _____________________________ echo "Calling linux_installer.sh" echo _____________________________ - linux_installer.sh - install_python_dependencies.sh + echo $'\nRunning Linux installer.' + echo "Updating sudo-apt." + echo "$WORKING_DIR" + + sudo apt-get update + + # clone depthai form git + + if [ -d "$WORKING_DIR" ]; then + echo "Demo app already downloaded. Checking out main and updating." + + else + echo "Downloading demo app." + git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" + fi + + cd "$WORKING_DIR" + git fetch + git checkout demo_app_installation_v2 + git pull + + # install python 3.10 + echo "installing python 3.10" + + sudo yes "" | sudo add-apt-repository ppa:deadsnakes/ppa + sudo apt -y install python3.10 + sudo apt -y install python3.10-venv + + # create python virtual environment + echo "Creating python virtual environment in $WORKING_DIR/venv" + + python3.10 -m venv "$WORKING_DIR/venv" + # activate environment + source "$WORKING_DIR/venv/bin/activate" + pip install --upgrade pip + + pip install packaging + pip install pyqt5 else echo "Error: Host $(uname -s) not supported." exit 99 fi + +echo $'\n\n:::::::::::::::: INSTALATION COMPLETE ::::::::::::::::\n' +echo $'\nTo run demo app write in terminal.' +read -rsp $'Press ANY KEY to finish and run the demo app...\n' -n1 key +echo "STARTING DEMO APP." +python3.10 "$WORKING_DIR/launcher/launcher.py" -r "$WORKING_DIR" + diff --git a/docs/source/_static/linux_installer.sh b/docs/source/_static/linux_installer.sh index 8ecb24875..738e5fa23 100755 --- a/docs/source/_static/linux_installer.sh +++ b/docs/source/_static/linux_installer.sh @@ -2,6 +2,7 @@ echo '\nRunning Linux installer.' echo "Upgrading sudo-apt." +echo "$WORKING_DIR" sudo apt-get update @@ -16,17 +17,17 @@ else fi cd "$WORKING_DIR" +git fetch git checkout demo_app_installation_v2 git pull # install python 3.10 echo "installing python 3.10" -sudo add-apt-repository ppa:deadsnakes/ppa +sudo yes "" | sudo add-apt-repository ppa:deadsnakes/ppa sudo apt -y install python3.10 sudo apt -y install python3.10-venv - # create python virtual environment echo "Creating python virtual environment in $WORKING_DIR/venv" diff --git a/docs/source/_static/macOS_installer.sh b/docs/source/_static/macOS_installer.sh index a8cddc753..a7b5f1b3f 100755 --- a/docs/source/_static/macOS_installer.sh +++ b/docs/source/_static/macOS_installer.sh @@ -13,6 +13,7 @@ else git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" fi cd "$WORKING_DIR" +git fetch git checkout demo_app_installation_v2 git pull From db1367a595fb6302fef1ca7042b431e48f23fb6a Mon Sep 17 00:00:00 2001 From: luxonis Date: Sat, 12 Nov 2022 13:43:47 +0100 Subject: [PATCH 028/385] read installation path from user --- .../_static/install_global_dependencies.sh | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/docs/source/_static/install_global_dependencies.sh b/docs/source/_static/install_global_dependencies.sh index 1f553b84b..b5fc40220 100755 --- a/docs/source/_static/install_global_dependencies.sh +++ b/docs/source/_static/install_global_dependencies.sh @@ -1,7 +1,35 @@ #!/bin/bash +# create environment variables +export APP_NAME="depthai" +WORKING="$HOME/$APP_NAME" +install_path="" +export WORKING_DIR="$WORKING" +path_correct="false" + trap 'RET=$? ; echo -e >&2 "\n\x1b[31mFailed installing dependencies. Could be a bug in the installer or unsupported platform. Open a bug report over at https://github.com/luxonis/depthai - exited with status $RET at line $LINENO \x1b[0m\n" ; exit $RET' ERR +while [ "$path_correct" = "false" ] +do + echo "" + read -p $'ENTER absolute installation path for depthai or leave empty and default path: $HOME will be used.\n' -r install_path + echo "" + + if [ "$install_path" = "" ]; then + echo "Using default installation path: $WORKING_DIR" + else + echo "Using given installation path: $install_path" + export WORKING_DIR="$install_path/$APP_NAME" + fi + + if [ -d "$(dirname "$WORKING_DIR")" ]; then + echo "Directory: $WORKING_DIR is OK" + path_correct="true" + else + echo "Directory: $WORKING_DIR is not valid. Try again!" + fi +done + write_in_file () { # just make sure only strings are appended which are not in there yet # first arg is text to write, second arg is the file path @@ -174,16 +202,6 @@ fi echo "Finished installing global libraries." -# create environment variables and save important ones into .bashrc -this_dir=$(pwd) -# to be used in next script -export CURR_DIR="$this_dir" - -export APP_NAME="depthai" -WORKING="$HOME/$APP_NAME" -export WORKING_DIR="$WORKING" -export PATH="$PATH":"$CURR_DIR" - # add depthai working dir to .bashrc if its not already there COMMENT='# Home directory of depthai and depthai app, enables to run in terminal' BASHRC="$HOME/.bashrc" From 04f7447dc95639e96096b95f9c1024aa6a10160b Mon Sep 17 00:00:00 2001 From: petrnovota Date: Sat, 12 Nov 2022 19:49:38 +0100 Subject: [PATCH 029/385] script_renamed_to_fit_its_purpose_better --- ...l_dependencies.sh => depthai_env_setup.sh} | 0 docs/source/_static/linux_installer.sh | 43 -------------- docs/source/_static/macOS_installer.sh | 57 ------------------- 3 files changed, 100 deletions(-) rename docs/source/_static/{install_global_dependencies.sh => depthai_env_setup.sh} (100%) delete mode 100755 docs/source/_static/linux_installer.sh delete mode 100755 docs/source/_static/macOS_installer.sh diff --git a/docs/source/_static/install_global_dependencies.sh b/docs/source/_static/depthai_env_setup.sh similarity index 100% rename from docs/source/_static/install_global_dependencies.sh rename to docs/source/_static/depthai_env_setup.sh diff --git a/docs/source/_static/linux_installer.sh b/docs/source/_static/linux_installer.sh deleted file mode 100755 index 738e5fa23..000000000 --- a/docs/source/_static/linux_installer.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -i - -echo '\nRunning Linux installer.' -echo "Upgrading sudo-apt." -echo "$WORKING_DIR" - -sudo apt-get update - -# clone depthai form git - -if [ -d "$WORKING_DIR" ]; then - echo "Demo app already downloaded. Checking out main and updating." - -else - echo "Downloading demo app." - git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" -fi - -cd "$WORKING_DIR" -git fetch -git checkout demo_app_installation_v2 -git pull - -# install python 3.10 -echo "installing python 3.10" - -sudo yes "" | sudo add-apt-repository ppa:deadsnakes/ppa -sudo apt -y install python3.10 -sudo apt -y install python3.10-venv - -# create python virtual environment -echo "Creating python virtual environment in $WORKING_DIR/venv" - -python3.10 -m venv "$WORKING_DIR/venv" -# activate environment -source "$WORKING_DIR/venv/bin/activate" -pip install --upgrade pip - -pip install packaging -pip install pyqt5 -# run launcher -echo '\n>>>>>>>>>>>> RUNNING LAUNCHER <<<<<<<<<<<<<<<<<<<\n' -python3.10 "$WORKING_DIR/launcher/launcher.py" -r "$WORKING_DIR" diff --git a/docs/source/_static/macOS_installer.sh b/docs/source/_static/macOS_installer.sh deleted file mode 100755 index a7b5f1b3f..000000000 --- a/docs/source/_static/macOS_installer.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash -i - -echo "Running macOS installer." -echo "Upgrading brew." -brew upgrade -# clone depthai form git - -if [ -d "$WORKING_DIR" ]; then - echo "Demo app already downloaded. Checking out main and updating." - -else - echo "Downloading demo app." - git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" -fi -cd "$WORKING_DIR" -git fetch -git checkout demo_app_installation_v2 -git pull - -# install python 3.10 and python dependencies -brew update -echo "installing python 3.10" -brew install python@3.10 - -# pip does not have pyqt5 for arm -if [[ $(uname -m) == 'arm64' ]]; then - echo "Installing pyqt5 with homebrew." - brew install pyqt@5 -fi - -# create python virtual environment -echo "Creating python virtual environment in $WORKING_DIR/venv" - -python3.10 -m venv "$WORKING_DIR/venv" -# activate environment -source "$WORKING_DIR/venv/bin/activate" -pip install --upgrade pip - -# install launcher dependencies -# only on mac silicon point PYTHONPATH to pyqt5 installation via homebrew, otherwise install pyqth5 with pip -if [[ $(uname -m) == 'arm64' ]]; then - #if [[ $(uname -s) == "Darwin" ]]; then - if [[ ":$PYTHONPATH:" == *":/opt/homebrew/lib/python3.10/site-packages:"* ]]; then - echo "/opt/homebrew/lib/python3.10/site-packages already in PYTHONPATH" - else - export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH - echo "/opt/homebrew/lib/python3.10/site-packages added to PYTHONPATH" - fi - #fi -else - pip install pyqt5 -fi - -pip install packaging - -# run launcher -python "$WORKING_DIR/launcher/launcher.py" -r "$WORKING_DIR" From e3f0f6bbd03c7dae22e689c023b36014b4c1859e Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Mon, 14 Nov 2022 13:34:44 +0100 Subject: [PATCH 030/385] Updated README with supported OSes --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7c3844cfe..450d7ffd5 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,8 @@ If `TEST_TIMEOUT=0`, the test will run until stopped or it ends. ## Tested platforms -- Windows 10 -- Ubuntu 16.04, 18.04; +- Windows 10, Windows 11 +- Ubuntu 18.04, 20.04, 22.04; - Raspbian 10; - macOS 10.14.6, 10.15.4; From d19e65f01a311643696323b227a1468fcb0da8d1 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Mon, 14 Nov 2022 13:42:52 +0100 Subject: [PATCH 031/385] Add a workflow for testing the scripts --- .../workflows/test-install-dependencies.yml | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/test-install-dependencies.yml diff --git a/.github/workflows/test-install-dependencies.yml b/.github/workflows/test-install-dependencies.yml new file mode 100644 index 000000000..0b39ccb1c --- /dev/null +++ b/.github/workflows/test-install-dependencies.yml @@ -0,0 +1,33 @@ + name: Test dependencies and requirements installation + + on: + workflow_dispatch: + push: + paths: + - 'docs/source/_static/install_dependencies.sh' + - 'examples/install_requirements.py' + + jobs: + install_dependencies: + runs-on: ubuntu-latest + strategy: + matrix: + container_image: ["fedora:34", "fedora:35", "fedora:36", "ubuntu:18.04", "ubuntu:20.04", "ubuntu:22.04"] + container: + image: ${{ matrix.container_image }} + steps: + - uses: actions/checkout@v3 + - name: Install sudo + if: startsWith(matrix.container_image, 'fedora') == true + run: yum update -y && yum install -y sudo + - name: Install sudo + if: startsWith(matrix.container_image, 'ubuntu') == true + run: apt-get update -qq && apt-get -qq install sudo + - name: Install dependencies + run: | + ln -snf /usr/share/zoneinfo/UTC /etc/localtime && echo UTC > /etc/timezone # Otherwise tzdata installer prompts for user input + sed '/udevadm control --reload-rules && sudo udevadm trigger/d' docs/source/_static/install_dependencies.sh > tmp_script.sh # Doesn't work on docker + bash tmp_script.sh + - name: Install example requirements + run: | + python3 examples/install_requirements.py \ No newline at end of file From 2a37003bd86c62700c6b42d494ff99c8e0d8fe3d Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Mon, 14 Nov 2022 13:46:44 +0100 Subject: [PATCH 032/385] Test workflow --- examples/install_requirements.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/install_requirements.py b/examples/install_requirements.py index 1bcd555fa..12fb93525 100755 --- a/examples/install_requirements.py +++ b/examples/install_requirements.py @@ -5,6 +5,8 @@ import re import platform +# TODO remove, just for testing + convert_default = "empty" parser = argparse.ArgumentParser() parser.add_argument('-sdai', "--skip_depthai", action="store_true", help="Skip installation of depthai library.") From ca552502174832d4dd6738e6dd94839275dd4f4d Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Mon, 14 Nov 2022 13:52:55 +0100 Subject: [PATCH 033/385] Remove comment --- examples/install_requirements.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/install_requirements.py b/examples/install_requirements.py index 12fb93525..1bcd555fa 100755 --- a/examples/install_requirements.py +++ b/examples/install_requirements.py @@ -5,8 +5,6 @@ import re import platform -# TODO remove, just for testing - convert_default = "empty" parser = argparse.ArgumentParser() parser.add_argument('-sdai', "--skip_depthai", action="store_true", help="Skip installation of depthai library.") From 1747a6b06ae568df40f1b57d15ae5cc2d41d541f Mon Sep 17 00:00:00 2001 From: moratom <47612463+moratom@users.noreply.github.com> Date: Mon, 14 Nov 2022 18:09:27 +0100 Subject: [PATCH 034/385] Update test-install-dependencies.yml --- .github/workflows/test-install-dependencies.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-install-dependencies.yml b/.github/workflows/test-install-dependencies.yml index 0b39ccb1c..6c5514dd6 100644 --- a/.github/workflows/test-install-dependencies.yml +++ b/.github/workflows/test-install-dependencies.yml @@ -6,6 +6,7 @@ paths: - 'docs/source/_static/install_dependencies.sh' - 'examples/install_requirements.py' + pull_request: jobs: install_dependencies: @@ -30,4 +31,4 @@ bash tmp_script.sh - name: Install example requirements run: | - python3 examples/install_requirements.py \ No newline at end of file + python3 examples/install_requirements.py From 083b2ab43d059f9874e4fb0d551653adb7482b03 Mon Sep 17 00:00:00 2001 From: moratom <47612463+moratom@users.noreply.github.com> Date: Mon, 14 Nov 2022 18:22:21 +0100 Subject: [PATCH 035/385] Only run test_dependencies workflow on PRs with relevant files --- .github/workflows/test-install-dependencies.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test-install-dependencies.yml b/.github/workflows/test-install-dependencies.yml index 6c5514dd6..0c206f720 100644 --- a/.github/workflows/test-install-dependencies.yml +++ b/.github/workflows/test-install-dependencies.yml @@ -7,6 +7,9 @@ - 'docs/source/_static/install_dependencies.sh' - 'examples/install_requirements.py' pull_request: + paths: + - 'docs/source/_static/install_dependencies.sh' + - 'examples/install_requirements.py' jobs: install_dependencies: From 911403e8f75c24e3df8fb424feb8697573fcbbb0 Mon Sep 17 00:00:00 2001 From: petrnovota Date: Tue, 15 Nov 2022 11:17:50 +0100 Subject: [PATCH 036/385] mistakes corrected --- ...i_env_setup.sh => install_dependencies.sh} | 161 ------------ docs/source/_static/install_depthai.sh | 243 ++++++++++++++++++ docs/source/_static/run_demo | 46 ++++ 3 files changed, 289 insertions(+), 161 deletions(-) rename docs/source/_static/{depthai_env_setup.sh => install_dependencies.sh} (52%) create mode 100755 docs/source/_static/install_depthai.sh create mode 100755 docs/source/_static/run_demo diff --git a/docs/source/_static/depthai_env_setup.sh b/docs/source/_static/install_dependencies.sh similarity index 52% rename from docs/source/_static/depthai_env_setup.sh rename to docs/source/_static/install_dependencies.sh index b5fc40220..7e9ff15c7 100755 --- a/docs/source/_static/depthai_env_setup.sh +++ b/docs/source/_static/install_dependencies.sh @@ -1,44 +1,5 @@ #!/bin/bash -# create environment variables -export APP_NAME="depthai" -WORKING="$HOME/$APP_NAME" -install_path="" -export WORKING_DIR="$WORKING" -path_correct="false" - -trap 'RET=$? ; echo -e >&2 "\n\x1b[31mFailed installing dependencies. Could be a bug in the installer or unsupported platform. Open a bug report over at https://github.com/luxonis/depthai - exited with status $RET at line $LINENO \x1b[0m\n" ; exit $RET' ERR - -while [ "$path_correct" = "false" ] -do - echo "" - read -p $'ENTER absolute installation path for depthai or leave empty and default path: $HOME will be used.\n' -r install_path - echo "" - - if [ "$install_path" = "" ]; then - echo "Using default installation path: $WORKING_DIR" - else - echo "Using given installation path: $install_path" - export WORKING_DIR="$install_path/$APP_NAME" - fi - - if [ -d "$(dirname "$WORKING_DIR")" ]; then - echo "Directory: $WORKING_DIR is OK" - path_correct="true" - else - echo "Directory: $WORKING_DIR is not valid. Try again!" - fi -done - -write_in_file () { - # just make sure only strings are appended which are not in there yet - # first arg is text to write, second arg is the file path - if ! grep -Fxq "$1" "$2" - then - echo "$1" >> "$2" - fi -} - readonly linux_pkgs=( python3 python3-pip @@ -201,125 +162,3 @@ else fi echo "Finished installing global libraries." - -# add depthai working dir to .bashrc if its not already there -COMMENT='# Home directory of depthai and depthai app, enables to run in terminal' -BASHRC="$HOME/.bashrc" -ADD_DEPTHAI_TO_PATH='export PATH=$PATH'":$WORKING_DIR" -ADD_LUXONIS_DEPTHAI_HOME='export LUXONIS_DEPTHAI_HOME='"$WORKING_DIR" - -# add to .bashrc only if it is not in there already -write_in_file "$COMMENT" "$BASHRC" -write_in_file "$ADD_DEPTHAI_TO_PATH" "$BASHRC" -write_in_file "$ADD_LUXONIS_DEPTHAI_HOME" "$BASHRC" - - -if [[ $(uname -s) == "Darwin" ]]; then - echo _____________________________ - echo "Calling macOS_installer.sh" - echo _____________________________ - echo "Running macOS installer." - echo "Upgrading brew." - brew upgrade - - # clone depthai form git - if [ -d "$WORKING_DIR" ]; then - echo "Demo app already downloaded. Checking out main and updating." - else - echo "Downloading demo app." - git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" - fi - cd "$WORKING_DIR" - git fetch - git checkout demo_app_installation_v2 - git pull - - # install python 3.10 and python dependencies - brew update - echo "installing python 3.10" - brew install python@3.10 - - # pip does not have pyqt5 for arm - if [[ $(uname -m) == 'arm64' ]]; then - echo "Installing pyqt5 with homebrew." - brew install pyqt@5 - fi - - # create python virtual environment - echo "Creating python virtual environment in $WORKING_DIR/venv" - - python3.10 -m venv "$WORKING_DIR/venv" - # activate environment - source "$WORKING_DIR/venv/bin/activate" - pip install --upgrade pip - - # install launcher dependencies - # only on mac silicon point PYTHONPATH to pyqt5 installation via homebrew, otherwise install pyqth5 with pip - if [[ $(uname -m) == 'arm64' ]]; then - #if [[ $(uname -s) == "Darwin" ]]; then - if [[ ":$PYTHONPATH:" == *":/opt/homebrew/lib/python3.10/site-packages:"* ]]; then - echo "/opt/homebrew/lib/python3.10/site-packages already in PYTHONPATH" - else - export PYTHONPATH=/opt/homebrew/lib/python3.10/site-packages:$PYTHONPATH - echo "/opt/homebrew/lib/python3.10/site-packages added to PYTHONPATH" - fi - #fi - else - pip install pyqt5 - fi - - pip install packaging - -elif [[ $(uname -s) == "Linux" ]]; then - echo _____________________________ - echo "Calling linux_installer.sh" - echo _____________________________ - echo $'\nRunning Linux installer.' - echo "Updating sudo-apt." - echo "$WORKING_DIR" - - sudo apt-get update - - # clone depthai form git - - if [ -d "$WORKING_DIR" ]; then - echo "Demo app already downloaded. Checking out main and updating." - - else - echo "Downloading demo app." - git clone https://github.com/luxonis/depthai.git "$WORKING_DIR" - fi - - cd "$WORKING_DIR" - git fetch - git checkout demo_app_installation_v2 - git pull - - # install python 3.10 - echo "installing python 3.10" - - sudo yes "" | sudo add-apt-repository ppa:deadsnakes/ppa - sudo apt -y install python3.10 - sudo apt -y install python3.10-venv - - # create python virtual environment - echo "Creating python virtual environment in $WORKING_DIR/venv" - - python3.10 -m venv "$WORKING_DIR/venv" - # activate environment - source "$WORKING_DIR/venv/bin/activate" - pip install --upgrade pip - - pip install packaging - pip install pyqt5 -else - echo "Error: Host $(uname -s) not supported." - exit 99 -fi - -echo $'\n\n:::::::::::::::: INSTALATION COMPLETE ::::::::::::::::\n' -echo $'\nTo run demo app write in terminal.' -read -rsp $'Press ANY KEY to finish and run the demo app...\n' -n1 key -echo "STARTING DEMO APP." -python3.10 "$WORKING_DIR/launcher/launcher.py" -r "$WORKING_DIR" - diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh new file mode 100755 index 000000000..64f50f027 --- /dev/null +++ b/docs/source/_static/install_depthai.sh @@ -0,0 +1,243 @@ +#!/bin/bash + +# create some variables +# TODO: CURRENT_DIR will not be needed in production +CURRENT_DIR="$(pwd)" +APP_NAME="depthai" +WORKING_DIR_NAME="Luxonis" +WORKING_DIR="$HOME/$WORKING_DIR_NAME" +mkdir "$WORKING_DIR" +install_path="" +path_correct="false" + +trap 'RET=$? ; echo -e >&2 "\n\x1b[31mFailed installing dependencies. Could be a bug in the installer or unsupported platform. Open a bug report over at https://github.com/luxonis/depthai - exited with status $RET at line $LINENO \x1b[0m\n" ; exit $RET' ERR + +while [ "$path_correct" = "false" ] +do + echo "" + read -p $'ENTER absolute installation path for depthai or leave empty and default path: $HOME will be used.\n' -r install_path + echo "" + + if [ "$install_path" = "" ]; then + echo "Using default installation path: $WORKING_DIR" + mkdir -p "$WORKING_DIR" + else + echo "Using given installation path: $install_path" + WORKING_DIR="$install_path" + fi +# find parent dir if [ -d "$(dirname "$WORKING_DIR")" ]; then + if [ -d "$WORKING_DIR" ]; then + echo "Directory: $WORKING_DIR is OK" + path_correct="true" + else + echo "Directory: $WORKING_DIR is not valid. Try again!" + fi +done + +DEPTHAI_DIR="$WORKING_DIR/$APP_NAME" +VENV_DIR="$WORKING_DIR/venv" +ENTRYPOINT_DIR="$WORKING_DIR/entrypoint" +mkdir -p "$ENTRYPOINT_DIR" + +# Get Python version or find out tha python 3.10 must be installed +python_executable=$(which python3) +python_chosen="false" +install_python="false" +python_version=$(python3 --version) +python_version_number="" +if [[ "$python_version" != 'Python'* ]]; then + python_version="" +fi +echo $'\n' + +# check default python version, offer it to the user or get another one +while [ "$python_chosen" = "false" ] +do + if [[ "$python_version" == "" ]]; then + echo "No python version found." + echo "Input path for python binary, version 3.8 or higher, or leave empty and python 3.10 will be installed for you." + read -p $'Press any key to continue\n' -r python_binary_path + # python not found and user wants to install python 3.10 + if [ "$python_binary_path" = "" ]; then + install_python="true" + python_chosen="true" + fi + else + # E.g Python 3.10 -> nr_1=3, nr_2=10, for Python 3.7.5 -> nr_1=r, nr_2=7 + nr_1="${python_version:7:1}" + nr_2=$(echo "${python_version:9:2}" | tr -d -c 0-9) + echo "Python version: $python_version found." + if [ "$nr_1" -gt 2 ] && [ "$nr_2" -gt 7 ]; then # first two digits of python version greater then 3.7 -> python version 3.8 or greater is allowed. + echo "If you want to use it for installation, press ANY key, otherwise input path to python binary." + read -p $'Press any key to continue\n' -r python_binary_path + # user wants to use already installed python whose version is high enough + if [ "$python_binary_path" = "" ]; then + python_chosen="true" + fi + else + echo "This python version is not supported by depthai. Enter path to python binary version et least 3.8, or leave empty and python 3.10 will be installed automatically." + read -p $'Press any key to continue\n' -r python_binary_path + # python version is too low and user wants to install python 3.10 + if [ "$python_binary_path" = "" ]; then + install_python="true" + python_chosen="true" + fi + fi + fi + # User entered some path that should lead to python binary, save python --version output and the rest is dealt in the while loop logic. + if [ "$python_binary_path" != "" ]; then + python_executable="$python_binary_path" + python_version=$($python_binary_path --version) + if [[ "$python_version" != 'Python'* ]]; then + python_version="" + fi + fi +done + + +write_in_file () { + # just make sure only strings are appended which are not in there yet + # first arg is text to write, second arg is the file path + if ! grep -Fxq "$1" "$2" + then + echo "$1" >> "$2" + fi +} + +# add depthai working dir to .bashrc if its not already there +COMMENT='# Entry point for Depthai demo app, enables to run in terminal' +BASHRC="$HOME/.bashrc" +ADD_ENTRYPOINT_TO_PATH='export PATH=$PATH'":$ENTRYPOINT_DIR" + +# add to .bashrc only if it is not in there already +write_in_file "$COMMENT" "$BASHRC" +write_in_file "$ADD_ENTRYPOINT_TO_PATH" "$BASHRC" + +# install global dependencies +echo "Installing global dependencies." +# sudo curl -fL https://docs.luxonis.com/install_dependencies.sh | bash +./install_dependencies.sh + + +if [[ $(uname -s) == "Darwin" ]]; then + echo _____________________________ + echo "Calling macOS_installer.sh" + echo _____________________________ + echo "Running macOS installer." + echo "Upgrading brew." + brew upgrade + + # clone depthai form git + if [ -d "$DEPTHAI_DIR" ]; then + echo "Demo app already downloaded. Checking out main and updating." + else + echo "Downloading demo app." + git clone https://github.com/luxonis/depthai.git "$DEPTHAI_DIR" + fi + cd "$DEPTHAI_DIR" + git fetch + git checkout demo_app_installation_v2 + git pull + + # install python 3.10 and python dependencies + brew update + + if [ "$install_python" == "true" ]; then + echo "installing python 3.10" + brew install python@3.10 + python_executable=$(which python3.10) + fi + + # pip does not have pyqt5 for arm + if [[ $(uname -m) == 'arm64' ]]; then + echo "Installing pyqt5 with homebrew." + brew install pyqt@5 + fi + + # create python virtual environment + echo "Creating python virtual environment in $VENV_DIR" + echo "$python_executable" + "$python_executable" -m venv "$VENV_DIR" + # activate environment + source "$VENV_DIR/bin/activate" + pip install --upgrade pip + + # install launcher dependencies + # only on mac silicon point PYTHONPATH to pyqt5 installation via homebrew, otherwise install pyqth5 with pip + if [[ $(uname -m) == 'arm64' ]]; then + #if [[ $(uname -s) == "Darwin" ]]; then + if [[ ":$PYTHONPATH:" == *":/opt/homebrew/lib/python3.10/site-packages:"* ]]; then + echo "/opt/homebrew/lib/python$nr_1.$nr_2/site-packages already in PYTHONPATH" + else + export "PYTHONPATH=/opt/homebrew/lib/python$nr_1.$nr_2/site-packages:"$PYTHONPATH + echo "/opt/homebrew/lib/pythonv$nr_1.$nr_2/site-packages added to PYTHONPATH" + fi + #fi + else + pip install pyqt5 + fi + + pip install packaging + +elif [[ $(uname -s) == "Linux" ]]; then + echo _____________________________ + echo "Calling linux_installer.sh" + echo _____________________________ + echo $'\nRunning Linux installer.' + echo "Updating sudo-apt." + + sudo apt-get update + + # clone depthai form git + + if [ -d "$DEPTHAI_DIR" ]; then + echo "Demo app already downloaded. Checking out main and updating." + + else + echo "Downloading demo app." + git clone https://github.com/luxonis/depthai.git "$DEPTHAI_DIR" + fi + + cd "$DEPTHAI_DIR" + git fetch + git checkout demo_app_installation_v2 + git pull + + # install python 3.10 + if [ "$install_python" == "true" ]; then + echo "installing python 3.10" + + sudo yes "" | sudo add-apt-repository ppa:deadsnakes/ppa + sudo apt -y install python3.10 + sudo apt -y install python3.10-venv + python_executable=$(which python3.10) + fi + + # create python virtual environment + echo "Creating python virtual environment in $VENV_DIR" + + "$python_executable" -m venv "$VENV_DIR" + # activate environment + source "$VENV_DIR/bin/activate" + pip install --upgrade pip + + pip install packaging + pip install pyqt5 +else + echo "Error: Host $(uname -s) not supported." + exit 99 +fi + + +# Create entrypoint, it is a small script. Maybe we can store it on server and download it? +echo "Installing global dependencies." +# TODO: in production this should work +#sudo curl -fL https://docs.luxonis.com/run_demo.sh +cp "$CURRENT_DIR/run_demo" "$ENTRYPOINT_DIR" + + +echo $'\n\n:::::::::::::::: INSTALATION COMPLETE ::::::::::::::::\n' +echo $'\nTo run demo app write in terminal.' +read -rsp $'Press ANY KEY to finish and run the demo app...\n' -n1 key +echo "STARTING DEMO APP." +python "$DEPTHAI_DIR/launcher/launcher.py" -r "$DEPTHAI_DIR" diff --git a/docs/source/_static/run_demo b/docs/source/_static/run_demo new file mode 100755 index 000000000..b914caa64 --- /dev/null +++ b/docs/source/_static/run_demo @@ -0,0 +1,46 @@ +#!/bin/bash + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +LUXONIS_DEPTHAI_HOME="$(dirname "$SCRIPT_DIR")" +DEPTHAI_DIR="$LUXONIS_DEPTHAI_HOME/depthai" +VENV_DIR="$LUXONIS_DEPTHAI_HOME/venv" +echo "running demo app" +echo "$SCRIPT_DIR" +pwd + +if [ "$LUXONIS_DEPTHAI_HOME" = "" ]; then + echo ':::::::::::::::::::::::::::::::::::::::' + echo "Something is worng." + echo "SCRIPT_DIR = $SCRIPT_DIR" + echo "LUXONIS_DEPTHAI_HOME = $LUXONIS_DEPTHAI_HOME" + echo '\nLSCRIPT_DIR is probably wrong, see:.\n' + echo 'Using following script to find the location of this script.' + echo 'SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )' + echo "https://stackoverflow.com/questions/59895/how-do-i-get-the-directory-where-a-bash-script-is-located-from-within-the-script" + echo ':::::::::::::::::::::::::::::::::::::::' + exit 1 +fi + +# check depthai directory exists +if [ -d "$DEPTHAI_DIR" ]; then + echo "OK. Depthai dir exists on $DEPTHAI_DIR." +else + echo "Depthai repo should be on $LUXONIS_DEPTHAI_HOME/$APP_NAME, but it does not exist." + echo "git clone depthai in this directory, or use the installation script again." + echo "git clone https://github.com/luxonis/depthai.git $LUXONIS_DEPTHAI_HOME/$APP_NAME" + exit 3 +fi + +# check venv exists +if [ -d "$VENV_DIR" ]; then + echo "OK. Virtual environment dir exists on $VENV_DIR." +else + echo "Virtual environment not found ok $VENV_DIR" + echo "Re-run the demo app installation script." + exit 3 +fi + +source "$VENV_DIR/bin/activate" +echo "running launcher with python version" +echo python --version +python "$DEPTHAI_DIR"/launcher/launcher.py From 545c2933ecc8662979692d61cfd0599fcaba7493 Mon Sep 17 00:00:00 2001 From: petrnovota Date: Tue, 15 Nov 2022 11:26:24 +0100 Subject: [PATCH 037/385] run_demo adds pyqt5 homebrew installation if on arm --- docs/source/_static/install_depthai.sh | 6 ++++++ docs/source/_static/run_demo | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh index 64f50f027..f4be86c55 100755 --- a/docs/source/_static/install_depthai.sh +++ b/docs/source/_static/install_depthai.sh @@ -107,12 +107,18 @@ write_in_file () { # add depthai working dir to .bashrc if its not already there COMMENT='# Entry point for Depthai demo app, enables to run in terminal' BASHRC="$HOME/.bashrc" +ZSHRC="$HOME/.zshrc" ADD_ENTRYPOINT_TO_PATH='export PATH=$PATH'":$ENTRYPOINT_DIR" # add to .bashrc only if it is not in there already write_in_file "$COMMENT" "$BASHRC" write_in_file "$ADD_ENTRYPOINT_TO_PATH" "$BASHRC" +if [ -f "$ZSHRC" ]; then + write_in_file "$COMMENT" "$ZSHRC" + write_in_file "$ADD_ENTRYPOINT_TO_PATH" "$ZSHRC" +fi + # install global dependencies echo "Installing global dependencies." # sudo curl -fL https://docs.luxonis.com/install_dependencies.sh | bash diff --git a/docs/source/_static/run_demo b/docs/source/_static/run_demo index b914caa64..e9ce4aa07 100755 --- a/docs/source/_static/run_demo +++ b/docs/source/_static/run_demo @@ -41,6 +41,22 @@ else fi source "$VENV_DIR/bin/activate" + +# add pyqt5 to pythonpath if on arm +python_version=$(python3 --version) +nr_1="${python_version:7:1}" +nr_2=$(echo "${python_version:9:2}" | tr -d -c 0-9) + +if [[ $(uname -m) == 'arm64' ]]; then + #if [[ $(uname -s) == "Darwin" ]]; then + if [[ ":$PYTHONPATH:" == *":/opt/homebrew/lib/python3.10/site-packages:"* ]]; then + echo "/opt/homebrew/lib/python$nr_1.$nr_2/site-packages already in PYTHONPATH" + else + export "PYTHONPATH=/opt/homebrew/lib/python$nr_1.$nr_2/site-packages:"$PYTHONPATH + echo "/opt/homebrew/lib/pythonv$nr_1.$nr_2/site-packages added to PYTHONPATH" + fi +fi + echo "running launcher with python version" echo python --version python "$DEPTHAI_DIR"/launcher/launcher.py From 0dea020b3f23ac9c18f6130203eceb3ff9606216 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 15 Nov 2022 18:29:08 +0800 Subject: [PATCH 038/385] updated docs on prebuilt mac m1 wheels --- docs/source/install.rst | 46 +---------------------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/docs/source/install.rst b/docs/source/install.rst index 2d7039c9f..181e84c6e 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -60,51 +60,7 @@ following: See the `Video preview window fails to appear on macOS `_ thread on our forum for more information. -M1 Mac build wheels natively ----------------------------- - -In order to run DepthAI natively on M1 Mac, you currently need to build the wheels locally. We will add pre-building M1 wheels -in Q2 of 2022, so this won't be needed anymore. - -This tutorial was provided by whab and tested on a MacBookPro M1 Pro running macOS Monterey 12.1 with a OAK-D-Lite. - -.. code-block:: bash - - # Install native M1 version of brew - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" - echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile - eval "$(/opt/homebrew/bin/brew shellenv)" - - # Install conda to create virtual environments for Python - brew install --cask miniconda - conda init zsh - - # Close and re-open a Terminal window - - # Install DepthAI by building a M1 wheel (inside ~/DepthAI/) - conda create --name DepthAIEnv39 python=3.9 - conda activate DepthAIEnv39 - python3 -m pip install -U pip - brew update - brew install cmake libusb - cd ~; mkdir DepthAI; cd DepthAI - git clone --recursive https://github.com/luxonis/depthai-python.git - cd depthai-python - mkdir build && cd build - # Build depthai-python - cmake .. - cmake --build . --parallel - cd .. - python3 -m pip wheel . -w wheelhouse - pip install wheelhouse/depthai-* - - # Test DepthAI with a OAK plugged to your new M1 Mac - cd examples - nano install_requirements.py - # Remove code of block (3 lines) starting with: if thisPlatform == "arm64" and platform.system() == "Darwin": - # Remove code of block (48 lines) starting with: if not args.skip_depthai: - python3 install_requirements.py - python3 ColorCamera/rgb_preview.py +We provide **Mac M1 prebuilt Python wheels** for depthai since the version ``2.17.3.1``. Ubuntu ****** From 28e81def316f107ace507a463191acd107277119 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 14:11:41 +0000 Subject: [PATCH 039/385] Bump pillow from 9.2.0 to 9.3.0 in /utilities Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.2.0 to 9.3.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/9.2.0...9.3.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- utilities/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/requirements.txt b/utilities/requirements.txt index b7b8c3381..dfdbfd329 100644 --- a/utilities/requirements.txt +++ b/utilities/requirements.txt @@ -1,2 +1,2 @@ PySimpleGUI==4.60.3 -Pillow==9.2.0 +Pillow==9.3.0 From 7ae3be30c49da961b639f1b17820f0ffc392417c Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Wed, 16 Nov 2022 00:21:07 +0100 Subject: [PATCH 040/385] Added windows and macOS --- .../workflows/test-install-dependencies.yml | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-install-dependencies.yml b/.github/workflows/test-install-dependencies.yml index 0c206f720..fa09cabaa 100644 --- a/.github/workflows/test-install-dependencies.yml +++ b/.github/workflows/test-install-dependencies.yml @@ -1,4 +1,4 @@ - name: Test dependencies and requirements installation + name: OS Support on: workflow_dispatch: @@ -12,7 +12,7 @@ - 'examples/install_requirements.py' jobs: - install_dependencies: + test_linux: runs-on: ubuntu-latest strategy: matrix: @@ -35,3 +35,30 @@ - name: Install example requirements run: | python3 examples/install_requirements.py + test_macos: + strategy: + matrix: + os: ["macos-11", "macos-12"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: Install dependencies + run: | + sed '/udevadm control --reload-rules && sudo udevadm trigger/d' docs/source/_static/install_dependencies.sh > tmp_script.sh + bash tmp_script.sh + - name: Install example requirements + run: | + python3 examples/install_requirements.py + test_windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: Download chocolatey + shell: pwsh + run: Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) + - name: Install dependencies + shell: pwsh + run: choco install cmake git python pycharm-community -y + - name: Install example requrirements + run: | + python examples/install_requirements.py From e103da7057370c2a407866a4ce9e1bac85daf432 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 16 Nov 2022 12:22:36 +0800 Subject: [PATCH 041/385] updated rgb-depth alignment limitation --- docs/source/components/nodes/stereo_depth.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/components/nodes/stereo_depth.rst b/docs/source/components/nodes/stereo_depth.rst index 6ae99ed25..ea136c358 100644 --- a/docs/source/components/nodes/stereo_depth.rst +++ b/docs/source/components/nodes/stereo_depth.rst @@ -168,6 +168,7 @@ Limitations =========== - Median filtering is disabled when subpixel mode is set to 4 or 5 bits. +- For RGB-depth alignment the RGB camera has to be placed on the same horizontal line as the stereo camera pair. Stereo depth FPS ================ From 10480718360c96cefc824cd71c7704cfcea24a74 Mon Sep 17 00:00:00 2001 From: PetrNovota <99871801+PetrNovota@users.noreply.github.com> Date: Wed, 23 Nov 2022 11:57:57 +0100 Subject: [PATCH 042/385] unintentionally deleted line added error handler added back --- docs/source/_static/install_dependencies.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/_static/install_dependencies.sh b/docs/source/_static/install_dependencies.sh index 7e9ff15c7..e5c860db7 100755 --- a/docs/source/_static/install_dependencies.sh +++ b/docs/source/_static/install_dependencies.sh @@ -1,5 +1,8 @@ #!/bin/bash +trap 'RET=$? ; echo -e >&2 "\n\x1b[31mFailed installing dependencies. Could be a bug in the installer or unsupported platform. Open a bug report over at https://github.com/luxonis/depthai - exited with status $RET at line $LINENO \x1b[0m\n" ; +exit $RET' ERR + readonly linux_pkgs=( python3 python3-pip From 399ac05e0bf675771e84fa897584381f0d549831 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 30 Nov 2022 12:49:51 +0700 Subject: [PATCH 043/385] added docs about spatial coordiante system --- .../images/components/spatial-coordinates.png | Bin 0 -> 4292 bytes .../mobilenet_spatial_detection_network.rst.rst | 2 ++ .../nodes/spatial_location_calculator.rst | 2 ++ .../nodes/yolo_spatial_detection_network.rst | 2 ++ docs/source/includes/spatial-coords.rst | 8 ++++++++ 5 files changed, 14 insertions(+) create mode 100644 docs/source/_static/images/components/spatial-coordinates.png create mode 100644 docs/source/includes/spatial-coords.rst diff --git a/docs/source/_static/images/components/spatial-coordinates.png b/docs/source/_static/images/components/spatial-coordinates.png new file mode 100644 index 0000000000000000000000000000000000000000..7aaaf3c6e447389cc1cffa55e5f59b39979f4987 GIT binary patch literal 4292 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. \ No newline at end of file From ba9bdfb7d66e0767b8199a8f7b08c54e3193e30b Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 1 Dec 2022 17:37:42 +0100 Subject: [PATCH 044/385] [FW] Fixed ImageManip + Subpixel issues and added FFC camera naming --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index d1661bf74..48f0214fa 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit d1661bf74cf3afe8b42370c00955f3033dcd7f30 +Subproject commit 48f0214fa6c50b10dd1982ddaf014803cf088645 From 00825c38646311555c0fb8a821afbb9ba642f35d Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 6 Dec 2022 18:49:45 +0700 Subject: [PATCH 045/385] Updated stereo depth docs wording --- docs/source/components/nodes/stereo_depth.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/components/nodes/stereo_depth.rst b/docs/source/components/nodes/stereo_depth.rst index ea136c358..0c7c3f83a 100644 --- a/docs/source/components/nodes/stereo_depth.rst +++ b/docs/source/components/nodes/stereo_depth.rst @@ -242,7 +242,7 @@ Examples of functionality ========================= - :ref:`Depth Preview` -- :ref:`RGB Depth alignment` +- :ref:`RGB Depth alignment` - align depth to color camera - :ref:`Mono & MobilenetSSD & Depth` - :ref:`RGB & MobilenetSSD with spatial data` @@ -460,12 +460,12 @@ on the following picture. Meaning of variables on the picture: -- :code:`BL [cm]` - Baseline of stereo cameras. -- :code:`Dv [cm]` - Minimum distace where both cameras see an object (thus where depth can be calculated). -- :code:`B [pixels]` - Width of the band where depth cannot be calculated. -- :code:`W [pixels]` - Width of mono in pixels camera or amount of horizontal pixels, also noted as :code:`HPixels` in other formulas. -- :code:`D [cm]` - Distance from the cameras to an object. -- :code:`F [cm]` - Width of image at the distance :code:`D`. +- ``BL [cm]`` - Baseline of stereo cameras. +- ``Dv [cm]`` - Minimum distace where both cameras see an object (thus where depth can be calculated). +- ``B [pixels]`` - Width of the band where depth cannot be calculated. +- ``W [pixels]`` - Width of mono in pixels camera or amount of horizontal pixels, also noted as :code:`HPixels` in other formulas. +- ``D [cm]`` - Distance from the **camera plane** to an object (see image :ref:`here `). +- ``F [cm]`` - Width of image at the distance ``D``. .. image:: https://user-images.githubusercontent.com/59799831/135310972-c37ba40b-20ad-4967-92a7-c71078bcef99.png From d170e006a0346a81b14e6fc98032208cec83fe82 Mon Sep 17 00:00:00 2001 From: petrnovota Date: Fri, 9 Dec 2022 11:37:55 +0100 Subject: [PATCH 046/385] - run_demo renamer to depthai_launcher and moved to depthai repo - installation of global dependencies is platform dependent and calls install_dependencies.sh from server --- docs/source/_static/install_depthai.sh | 45 +++++++++---------- docs/source/_static/run_demo | 62 -------------------------- 2 files changed, 21 insertions(+), 86 deletions(-) delete mode 100755 docs/source/_static/run_demo diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh index f4be86c55..cd791fce2 100755 --- a/docs/source/_static/install_depthai.sh +++ b/docs/source/_static/install_depthai.sh @@ -1,8 +1,6 @@ #!/bin/bash # create some variables -# TODO: CURRENT_DIR will not be needed in production -CURRENT_DIR="$(pwd)" APP_NAME="depthai" WORKING_DIR_NAME="Luxonis" WORKING_DIR="$HOME/$WORKING_DIR_NAME" @@ -36,8 +34,7 @@ done DEPTHAI_DIR="$WORKING_DIR/$APP_NAME" VENV_DIR="$WORKING_DIR/venv" -ENTRYPOINT_DIR="$WORKING_DIR/entrypoint" -mkdir -p "$ENTRYPOINT_DIR" +ENTRYPOINT_DIR="$DEPTHAI_DIR/entrypoint" # Get Python version or find out tha python 3.10 must be installed python_executable=$(which python3) @@ -105,31 +102,35 @@ write_in_file () { } # add depthai working dir to .bashrc if its not already there -COMMENT='# Entry point for Depthai demo app, enables to run in terminal' +COMMENT='# Entry point for Depthai demo app, enables to run in terminal' +COMMENT_DEPTHAI_HOME='# Depthai home directory' BASHRC="$HOME/.bashrc" ZSHRC="$HOME/.zshrc" ADD_ENTRYPOINT_TO_PATH='export PATH=$PATH'":$ENTRYPOINT_DIR" +ADD_DEPTHAI_HOME_TO_PATH='export PATH=$PATH'":$DEPTHAI_DIR" # add to .bashrc only if it is not in there already write_in_file "$COMMENT" "$BASHRC" write_in_file "$ADD_ENTRYPOINT_TO_PATH" "$BASHRC" +# write_in_file "$COMMENT_DEPTHAI_HOME" "$BASHRC" +# write_in_file "$ADD_DEPTHAI_HOME_TO_PATH" "$BASHRC" if [ -f "$ZSHRC" ]; then write_in_file "$COMMENT" "$ZSHRC" write_in_file "$ADD_ENTRYPOINT_TO_PATH" "$ZSHRC" + # write_in_file "$COMMENT_DEPTHAI_HOME" "$ZSHRC" + # write_in_file "$ADD_DEPTHAI_HOME_TO_PATH" "$ZSHRC" fi -# install global dependencies -echo "Installing global dependencies." -# sudo curl -fL https://docs.luxonis.com/install_dependencies.sh | bash -./install_dependencies.sh - - if [[ $(uname -s) == "Darwin" ]]; then echo _____________________________ echo "Calling macOS_installer.sh" echo _____________________________ echo "Running macOS installer." + + echo "Installing global dependencies." + bash -c "$(curl -fL https://docs.luxonis.com/install_dependencies.sh)" + echo "Upgrading brew." brew upgrade @@ -142,7 +143,7 @@ if [[ $(uname -s) == "Darwin" ]]; then fi cd "$DEPTHAI_DIR" git fetch - git checkout demo_app_installation_v2 + git checkout main git pull # install python 3.10 and python dependencies @@ -189,11 +190,15 @@ elif [[ $(uname -s) == "Linux" ]]; then echo _____________________________ echo "Calling linux_installer.sh" echo _____________________________ - echo $'\nRunning Linux installer.' - echo "Updating sudo-apt." + echo "Updating sudo-apt." sudo apt-get update + echo "Installing global dependencies." + sudo wget -qO- https://docs.luxonis.com/install_dependencies.sh | bash + + echo $'\nRunning Linux installer.' + # clone depthai form git if [ -d "$DEPTHAI_DIR" ]; then @@ -206,7 +211,7 @@ elif [[ $(uname -s) == "Linux" ]]; then cd "$DEPTHAI_DIR" git fetch - git checkout demo_app_installation_v2 + git checkout main git pull # install python 3.10 @@ -234,16 +239,8 @@ else exit 99 fi - -# Create entrypoint, it is a small script. Maybe we can store it on server and download it? -echo "Installing global dependencies." -# TODO: in production this should work -#sudo curl -fL https://docs.luxonis.com/run_demo.sh -cp "$CURRENT_DIR/run_demo" "$ENTRYPOINT_DIR" - - echo $'\n\n:::::::::::::::: INSTALATION COMPLETE ::::::::::::::::\n' -echo $'\nTo run demo app write in terminal.' +echo $'\nTo run demo app write in terminal.' read -rsp $'Press ANY KEY to finish and run the demo app...\n' -n1 key echo "STARTING DEMO APP." python "$DEPTHAI_DIR/launcher/launcher.py" -r "$DEPTHAI_DIR" diff --git a/docs/source/_static/run_demo b/docs/source/_static/run_demo deleted file mode 100755 index e9ce4aa07..000000000 --- a/docs/source/_static/run_demo +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -LUXONIS_DEPTHAI_HOME="$(dirname "$SCRIPT_DIR")" -DEPTHAI_DIR="$LUXONIS_DEPTHAI_HOME/depthai" -VENV_DIR="$LUXONIS_DEPTHAI_HOME/venv" -echo "running demo app" -echo "$SCRIPT_DIR" -pwd - -if [ "$LUXONIS_DEPTHAI_HOME" = "" ]; then - echo ':::::::::::::::::::::::::::::::::::::::' - echo "Something is worng." - echo "SCRIPT_DIR = $SCRIPT_DIR" - echo "LUXONIS_DEPTHAI_HOME = $LUXONIS_DEPTHAI_HOME" - echo '\nLSCRIPT_DIR is probably wrong, see:.\n' - echo 'Using following script to find the location of this script.' - echo 'SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )' - echo "https://stackoverflow.com/questions/59895/how-do-i-get-the-directory-where-a-bash-script-is-located-from-within-the-script" - echo ':::::::::::::::::::::::::::::::::::::::' - exit 1 -fi - -# check depthai directory exists -if [ -d "$DEPTHAI_DIR" ]; then - echo "OK. Depthai dir exists on $DEPTHAI_DIR." -else - echo "Depthai repo should be on $LUXONIS_DEPTHAI_HOME/$APP_NAME, but it does not exist." - echo "git clone depthai in this directory, or use the installation script again." - echo "git clone https://github.com/luxonis/depthai.git $LUXONIS_DEPTHAI_HOME/$APP_NAME" - exit 3 -fi - -# check venv exists -if [ -d "$VENV_DIR" ]; then - echo "OK. Virtual environment dir exists on $VENV_DIR." -else - echo "Virtual environment not found ok $VENV_DIR" - echo "Re-run the demo app installation script." - exit 3 -fi - -source "$VENV_DIR/bin/activate" - -# add pyqt5 to pythonpath if on arm -python_version=$(python3 --version) -nr_1="${python_version:7:1}" -nr_2=$(echo "${python_version:9:2}" | tr -d -c 0-9) - -if [[ $(uname -m) == 'arm64' ]]; then - #if [[ $(uname -s) == "Darwin" ]]; then - if [[ ":$PYTHONPATH:" == *":/opt/homebrew/lib/python3.10/site-packages:"* ]]; then - echo "/opt/homebrew/lib/python$nr_1.$nr_2/site-packages already in PYTHONPATH" - else - export "PYTHONPATH=/opt/homebrew/lib/python$nr_1.$nr_2/site-packages:"$PYTHONPATH - echo "/opt/homebrew/lib/pythonv$nr_1.$nr_2/site-packages added to PYTHONPATH" - fi -fi - -echo "running launcher with python version" -echo python --version -python "$DEPTHAI_DIR"/launcher/launcher.py From 01354a46d3b5e8c24709bdc59401016718a0beb1 Mon Sep 17 00:00:00 2001 From: Jacob Siverskog Date: Fri, 9 Dec 2022 15:31:38 +0100 Subject: [PATCH 047/385] fix argument typo --- src/pipeline/datatype/ImageManipConfigBindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipeline/datatype/ImageManipConfigBindings.cpp b/src/pipeline/datatype/ImageManipConfigBindings.cpp index 1ead6d0cc..e88f71b97 100644 --- a/src/pipeline/datatype/ImageManipConfigBindings.cpp +++ b/src/pipeline/datatype/ImageManipConfigBindings.cpp @@ -97,7 +97,7 @@ void bind_imagemanipconfig(pybind11::module& m, void* pCallstack){ imageManipConfig .def(py::init<>()) // setters - .def("setCropRect", static_cast(&ImageManipConfig::setCropRect), py::arg("xmin"), py::arg("ymin"), py::arg("xmax"), py::arg("xmax"), DOC(dai, ImageManipConfig, setCropRect)) + .def("setCropRect", static_cast(&ImageManipConfig::setCropRect), py::arg("xmin"), py::arg("ymin"), py::arg("xmax"), py::arg("ymax"), DOC(dai, ImageManipConfig, setCropRect)) .def("setCropRect", static_cast)>(&ImageManipConfig::setCropRect), py::arg("coordinates"), DOC(dai, ImageManipConfig, setCropRect, 2)) .def("setCropRotatedRect", &ImageManipConfig::setCropRotatedRect, py::arg("rr"), py::arg("normalizedCoords") = true, DOC(dai, ImageManipConfig, setCropRotatedRect)) .def("setCenterCrop", &ImageManipConfig::setCenterCrop, py::arg("ratio"), py::arg("whRatio")=1.0f, DOC(dai, ImageManipConfig, setCenterCrop)) From c623ff7be1c8ffb1f051bf6853de4aad191cb4e2 Mon Sep 17 00:00:00 2001 From: petrnovota Date: Mon, 12 Dec 2022 01:26:26 +0100 Subject: [PATCH 048/385] redundant comments deleted --- docs/source/_static/install_depthai.sh | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh index cd791fce2..f5498d708 100755 --- a/docs/source/_static/install_depthai.sh +++ b/docs/source/_static/install_depthai.sh @@ -1,6 +1,5 @@ #!/bin/bash -# create some variables APP_NAME="depthai" WORKING_DIR_NAME="Luxonis" WORKING_DIR="$HOME/$WORKING_DIR_NAME" @@ -23,7 +22,7 @@ do echo "Using given installation path: $install_path" WORKING_DIR="$install_path" fi -# find parent dir if [ -d "$(dirname "$WORKING_DIR")" ]; then + if [ -d "$WORKING_DIR" ]; then echo "Directory: $WORKING_DIR is OK" path_correct="true" @@ -36,7 +35,7 @@ DEPTHAI_DIR="$WORKING_DIR/$APP_NAME" VENV_DIR="$WORKING_DIR/venv" ENTRYPOINT_DIR="$DEPTHAI_DIR/entrypoint" -# Get Python version or find out tha python 3.10 must be installed +# Get Python version or find out that python 3.10 must be installed python_executable=$(which python3) python_chosen="false" install_python="false" @@ -101,25 +100,18 @@ write_in_file () { fi } -# add depthai working dir to .bashrc if its not already there COMMENT='# Entry point for Depthai demo app, enables to run in terminal' -COMMENT_DEPTHAI_HOME='# Depthai home directory' BASHRC="$HOME/.bashrc" ZSHRC="$HOME/.zshrc" ADD_ENTRYPOINT_TO_PATH='export PATH=$PATH'":$ENTRYPOINT_DIR" -ADD_DEPTHAI_HOME_TO_PATH='export PATH=$PATH'":$DEPTHAI_DIR" # add to .bashrc only if it is not in there already write_in_file "$COMMENT" "$BASHRC" write_in_file "$ADD_ENTRYPOINT_TO_PATH" "$BASHRC" -# write_in_file "$COMMENT_DEPTHAI_HOME" "$BASHRC" -# write_in_file "$ADD_DEPTHAI_HOME_TO_PATH" "$BASHRC" if [ -f "$ZSHRC" ]; then write_in_file "$COMMENT" "$ZSHRC" write_in_file "$ADD_ENTRYPOINT_TO_PATH" "$ZSHRC" - # write_in_file "$COMMENT_DEPTHAI_HOME" "$ZSHRC" - # write_in_file "$ADD_DEPTHAI_HOME_TO_PATH" "$ZSHRC" fi if [[ $(uname -s) == "Darwin" ]]; then @@ -170,16 +162,14 @@ if [[ $(uname -s) == "Darwin" ]]; then pip install --upgrade pip # install launcher dependencies - # only on mac silicon point PYTHONPATH to pyqt5 installation via homebrew, otherwise install pyqth5 with pip + # only on mac silicon point PYTHONPATH to pyqt5 installation via homebrew, otherwise install pyqt5 with pip if [[ $(uname -m) == 'arm64' ]]; then - #if [[ $(uname -s) == "Darwin" ]]; then if [[ ":$PYTHONPATH:" == *":/opt/homebrew/lib/python3.10/site-packages:"* ]]; then echo "/opt/homebrew/lib/python$nr_1.$nr_2/site-packages already in PYTHONPATH" else export "PYTHONPATH=/opt/homebrew/lib/python$nr_1.$nr_2/site-packages:"$PYTHONPATH echo "/opt/homebrew/lib/pythonv$nr_1.$nr_2/site-packages added to PYTHONPATH" fi - #fi else pip install pyqt5 fi @@ -200,7 +190,6 @@ elif [[ $(uname -s) == "Linux" ]]; then echo $'\nRunning Linux installer.' # clone depthai form git - if [ -d "$DEPTHAI_DIR" ]; then echo "Demo app already downloaded. Checking out main and updating." @@ -224,11 +213,10 @@ elif [[ $(uname -s) == "Linux" ]]; then python_executable=$(which python3.10) fi - # create python virtual environment echo "Creating python virtual environment in $VENV_DIR" "$python_executable" -m venv "$VENV_DIR" - # activate environment + source "$VENV_DIR/bin/activate" pip install --upgrade pip From fa263a8edeac5878e18aea57ad580466c7deb9b6 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Mon, 12 Dec 2022 20:01:54 +0100 Subject: [PATCH 049/385] Update core --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 48f0214fa..c95a06aac 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 48f0214fa6c50b10dd1982ddaf014803cf088645 +Subproject commit c95a06aac4b51bc062bed390ec83f5377fdea337 From d5b09fa3db477d2b7ec7644c326e840656e48f08 Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Mon, 12 Dec 2022 21:34:42 +0100 Subject: [PATCH 050/385] WIP: Camera node --- .gitignore | 1 + CMakeLists.txt | 1 + depthai-core | 2 +- examples/Camera/camera_rgb_preview.py | 46 +++++++ src/DeviceBindings.cpp | 2 +- src/pipeline/PipelineBindings.cpp | 1 + src/pipeline/node/CameraBindings.cpp | 189 ++++++++++++++++++++++++++ src/pipeline/node/NodeBindings.cpp | 2 + 8 files changed, 242 insertions(+), 2 deletions(-) create mode 100755 examples/Camera/camera_rgb_preview.py create mode 100644 src/pipeline/node/CameraBindings.cpp diff --git a/.gitignore b/.gitignore index c6d3c5cc3..98f9cb60a 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ wheelhouse/ .venv env/ venv/ +venv_*/ ENV/ env.bak/ venv.bak/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 50c01aa89..2183df54f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ pybind11_add_module(${TARGET_NAME} src/pipeline/node/XLinkInBindings.cpp src/pipeline/node/XLinkOutBindings.cpp src/pipeline/node/ColorCameraBindings.cpp + src/pipeline/node/CameraBindings.cpp src/pipeline/node/MonoCameraBindings.cpp src/pipeline/node/StereoDepthBindings.cpp src/pipeline/node/NeuralNetworkBindings.cpp diff --git a/depthai-core b/depthai-core index 48f0214fa..80eefc8a8 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 48f0214fa6c50b10dd1982ddaf014803cf088645 +Subproject commit 80eefc8a893628aca59315753ca74d1b32019809 diff --git a/examples/Camera/camera_rgb_preview.py b/examples/Camera/camera_rgb_preview.py new file mode 100755 index 000000000..8960651d0 --- /dev/null +++ b/examples/Camera/camera_rgb_preview.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 + +import cv2 +import depthai as dai + +# Create pipeline +pipeline = dai.Pipeline() + +# Define source and output +cam = pipeline.create(dai.node.Camera) +xoutRgb = pipeline.create(dai.node.XLinkOut) + +xoutRgb.setStreamName("camera") + +# Properties +cam.setPreviewSize(300, 300) +cam.setInterleaved(False) +cam.setColorOrder(dai.CameraProperties.ColorOrder.RGB) +cam.setBoardSocket(dai.CameraBoardSocket.LEFT) + +# Linking +cam.isp.link(xoutRgb.input) + +# Connect to device and start pipeline +with dai.Device(pipeline) as device: + + print('Connected cameras:', device.getConnectedCameraFeatures()) + # Print out usb speed + print('Usb speed:', device.getUsbSpeed().name) + # Bootloader version + if device.getBootloaderVersion() is not None: + print('Bootloader version:', device.getBootloaderVersion()) + # Device name + print('Device name:', device.getDeviceName()) + + # Output queue will be used to get the rgb frames from the output defined above + qRgb = device.getOutputQueue(name="camera", maxSize=4, blocking=False) + + while True: + inRgb = qRgb.get() # blocking call, will wait until a new data has arrived + + # Retrieve 'bgr' (opencv format) frame + cv2.imshow("camera", inRgb.getCvFrame()) + + if cv2.waitKey(1) == ord('q'): + break diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 43efada81..4e0cb6be8 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -544,7 +544,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def("flashFactoryEepromClear", [](DeviceBase& d) { py::gil_scoped_release release; d.flashFactoryEepromClear(); }, DOC(dai, DeviceBase, flashFactoryEepromClear)) .def("setTimesync", [](DeviceBase& d, std::chrono::milliseconds p, int s, bool r) { py::gil_scoped_release release; return d.setTimesync(p,s,r); }, DOC(dai, DeviceBase, setTimesync)) .def("setTimesync", [](DeviceBase& d, bool e) { py::gil_scoped_release release; return d.setTimesync(e); }, py::arg("enable"), DOC(dai, DeviceBase, setTimesync, 2)) - .def("getDeviceName", [](DeviceBase& d) { py::gil_scoped_release release; return d.getDeviceName(); }, DOC(dai, DeviceBase, getDeviceName)) + .def("getDeviceName", [](DeviceBase& d) { std::string name; { py::gil_scoped_release release; name = d.getDeviceName(); } return py::bytes(name).attr("decode")("utf-8", "replace"); }, DOC(dai, DeviceBase, getDeviceName)) ; diff --git a/src/pipeline/PipelineBindings.cpp b/src/pipeline/PipelineBindings.cpp index 22ea646dd..cfdd9f588 100644 --- a/src/pipeline/PipelineBindings.cpp +++ b/src/pipeline/PipelineBindings.cpp @@ -10,6 +10,7 @@ #include "depthai/pipeline/node/XLinkOut.hpp" #include "depthai/pipeline/node/NeuralNetwork.hpp" #include "depthai/pipeline/node/ColorCamera.hpp" +#include "depthai/pipeline/node/Camera.hpp" #include "depthai/pipeline/node/VideoEncoder.hpp" #include "depthai/pipeline/node/SPIOut.hpp" #include "depthai/pipeline/node/SPIIn.hpp" diff --git a/src/pipeline/node/CameraBindings.cpp b/src/pipeline/node/CameraBindings.cpp new file mode 100644 index 000000000..98624b0eb --- /dev/null +++ b/src/pipeline/node/CameraBindings.cpp @@ -0,0 +1,189 @@ +#include "NodeBindings.hpp" +#include "Common.hpp" + +#include "depthai/pipeline/Pipeline.hpp" +#include "depthai/pipeline/Node.hpp" +#include "depthai/pipeline/node/Camera.hpp" + +void bind_camera(pybind11::module& m, void* pCallstack){ + + using namespace dai; + using namespace dai::node; + + // Node and Properties declare upfront + py::class_ cameraProperties(m, "CameraProperties", DOC(dai, CameraProperties)); + // py::enum_ cameraPropertiesSensorResolution(cameraProperties, "SensorResolution", DOC(dai, CameraProperties, SensorResolution)); + py::enum_ cameraPropertiesColorOrder(cameraProperties, "ColorOrder", DOC(dai, CameraProperties, ColorOrder)); + auto camera = ADD_NODE(Camera); + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + // Call the rest of the type defines, then perform the actual bindings + Callstack* callstack = (Callstack*) pCallstack; + auto cb = callstack->top(); + callstack->pop(); + cb(m, pCallstack); + // Actual bindings + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + // // Camera Properties + // cameraPropertiesSensorResolution + // .value("THE_1080_P", CameraProperties::SensorResolution::THE_1080_P) + // .value("THE_1200_P", CameraProperties::SensorResolution::THE_1200_P) + // .value("THE_4_K", CameraProperties::SensorResolution::THE_4_K) + // .value("THE_5_MP", CameraProperties::SensorResolution::THE_5_MP) + // .value("THE_12_MP", CameraProperties::SensorResolution::THE_12_MP) + // .value("THE_4000X3000", CameraProperties::SensorResolution::THE_4000X3000) + // .value("THE_13_MP", CameraProperties::SensorResolution::THE_13_MP) + // .value("THE_5312X6000", CameraProperties::SensorResolution::THE_5312X6000) + // .value("THE_48_MP", CameraProperties::SensorResolution::THE_48_MP) + // .value("THE_720_P", CameraProperties::SensorResolution::THE_720_P) + // .value("THE_800_P", CameraProperties::SensorResolution::THE_800_P) + // ; + + cameraPropertiesColorOrder + .value("BGR", CameraProperties::ColorOrder::BGR) + .value("RGB", CameraProperties::ColorOrder::RGB) + ; + + cameraProperties + .def_readwrite("initialControl", &CameraProperties::initialControl) + .def_readwrite("boardSocket", &CameraProperties::boardSocket) + .def_readwrite("imageOrientation", &CameraProperties::imageOrientation) + .def_readwrite("colorOrder", &CameraProperties::colorOrder) + .def_readwrite("interleaved", &CameraProperties::interleaved) + .def_readwrite("fp16", &CameraProperties::fp16) + .def_readwrite("previewHeight", &CameraProperties::previewHeight) + .def_readwrite("previewWidth", &CameraProperties::previewWidth) + .def_readwrite("videoHeight", &CameraProperties::videoHeight) + .def_readwrite("videoWidth", &CameraProperties::videoWidth) + .def_readwrite("stillHeight", &CameraProperties::stillHeight) + .def_readwrite("stillWidth", &CameraProperties::stillWidth) + // .def_readwrite("resolution", &CameraProperties::resolution) + .def_readwrite("fps", &CameraProperties::fps) + .def_readwrite("sensorCropX", &CameraProperties::sensorCropX) + .def_readwrite("sensorCropY", &CameraProperties::sensorCropY) + .def_readwrite("previewKeepAspectRatio", &CameraProperties::previewKeepAspectRatio) + .def_readwrite("ispScale", &CameraProperties::ispScale) + .def_readwrite("numFramesPoolRaw", &CameraProperties::numFramesPoolRaw) + .def_readwrite("numFramesPoolIsp", &CameraProperties::numFramesPoolIsp) + .def_readwrite("numFramesPoolVideo", &CameraProperties::numFramesPoolVideo) + .def_readwrite("numFramesPoolPreview", &CameraProperties::numFramesPoolPreview) + .def_readwrite("numFramesPoolStill", &CameraProperties::numFramesPoolStill) + ; + + // Camera node + camera + .def_readonly("inputConfig", &Camera::inputConfig, DOC(dai, node, Camera, inputConfig)) + .def_readonly("inputControl", &Camera::inputControl, DOC(dai, node, Camera, inputControl)) + .def_readonly("initialControl", &Camera::initialControl, DOC(dai, node, Camera, initialControl)) + .def_readonly("video", &Camera::video, DOC(dai, node, Camera, video)) + .def_readonly("preview", &Camera::preview, DOC(dai, node, Camera, preview)) + .def_readonly("still", &Camera::still, DOC(dai, node, Camera, still)) + .def_readonly("isp", &Camera::isp, DOC(dai, node, Camera, isp)) + .def_readonly("raw", &Camera::raw, DOC(dai, node, Camera, raw)) + .def_readonly("frameEvent", &Camera::frameEvent, DOC(dai, node, Camera, frameEvent)) + .def("setCamId", [](Camera& c, int64_t id) { + // Issue an deprecation warning + PyErr_WarnEx(PyExc_DeprecationWarning, "setCamId() is deprecated, use setBoardSocket() instead.", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + c.setCamId(id); + HEDLEY_DIAGNOSTIC_POP + }) + .def("getCamId", [](Camera& c) { + // Issue an deprecation warning + PyErr_WarnEx(PyExc_DeprecationWarning, "getCamId() is deprecated, use getBoardSocket() instead.", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + return c.getCamId(); + HEDLEY_DIAGNOSTIC_POP + }) + .def("setBoardSocket", &Camera::setBoardSocket, py::arg("boardSocket"), DOC(dai, node, Camera, setBoardSocket)) + .def("getBoardSocket", &Camera::getBoardSocket, DOC(dai, node, Camera, getBoardSocket)) + .def("setImageOrientation", &Camera::setImageOrientation, py::arg("imageOrientation"), DOC(dai, node, Camera, setImageOrientation)) + .def("getImageOrientation", &Camera::getImageOrientation, DOC(dai, node, Camera, getImageOrientation)) + .def("setColorOrder", &Camera::setColorOrder, py::arg("colorOrder"), DOC(dai, node, Camera, setColorOrder)) + .def("getColorOrder", &Camera::getColorOrder, DOC(dai, node, Camera, getColorOrder)) + .def("setInterleaved", &Camera::setInterleaved, py::arg("interleaved"), DOC(dai, node, Camera, setInterleaved)) + .def("getInterleaved", &Camera::getInterleaved, DOC(dai, node, Camera, getInterleaved)) + .def("setFp16", &Camera::setFp16, py::arg("fp16"), DOC(dai, node, Camera, setFp16)) + .def("getFp16", &Camera::getFp16, DOC(dai, node, Camera, getFp16)) + .def("setPreviewSize", static_cast(&Camera::setPreviewSize), py::arg("width"), py::arg("height"), DOC(dai, node, Camera, setPreviewSize)) + .def("setPreviewSize", static_cast)>(&Camera::setPreviewSize), py::arg("size"), DOC(dai, node, Camera, setPreviewSize, 2)) + .def("setVideoSize", static_cast(&Camera::setVideoSize), py::arg("width"), py::arg("height"), DOC(dai, node, Camera, setVideoSize)) + .def("setVideoSize", static_cast)>(&Camera::setVideoSize), py::arg("size"), DOC(dai, node, Camera, setVideoSize, 2)) + .def("setStillSize", static_cast(&Camera::setStillSize), py::arg("width"), py::arg("height"), DOC(dai, node, Camera, setStillSize)) + .def("setStillSize", static_cast)>(&Camera::setStillSize), py::arg("size"), DOC(dai, node, Camera, setStillSize, 2)) + // .def("setResolution", &Camera::setResolution, py::arg("resolution"), DOC(dai, node, Camera, setResolution)) + // .def("getResolution", &Camera::getResolution, DOC(dai, node, Camera, getResolution)) + .def("setFps", &Camera::setFps, py::arg("fps"), DOC(dai, node, Camera, setFps)) + .def("getFps", &Camera::getFps, DOC(dai, node, Camera, getFps)) + .def("getPreviewSize", &Camera::getPreviewSize, DOC(dai, node, Camera, getPreviewSize)) + .def("getPreviewWidth", &Camera::getPreviewWidth, DOC(dai, node, Camera, getPreviewWidth)) + .def("getPreviewHeight", &Camera::getPreviewHeight, DOC(dai, node, Camera, getPreviewHeight)) + .def("getVideoSize", &Camera::getVideoSize, DOC(dai, node, Camera, getVideoSize)) + .def("getVideoWidth", &Camera::getVideoWidth, DOC(dai, node, Camera, getVideoWidth)) + .def("getVideoHeight", &Camera::getVideoHeight, DOC(dai, node, Camera, getVideoHeight)) + .def("getStillSize", &Camera::getStillSize, DOC(dai, node, Camera, getStillSize)) + .def("getStillWidth", &Camera::getStillWidth, DOC(dai, node, Camera, getStillWidth)) + .def("getStillHeight", &Camera::getStillHeight, DOC(dai, node, Camera, getStillHeight)) + .def("getResolutionSize", &Camera::getResolutionSize, DOC(dai, node, Camera, getResolutionSize)) + .def("getResolutionWidth", &Camera::getResolutionWidth, DOC(dai, node, Camera, getResolutionWidth)) + .def("getResolutionHeight", &Camera::getResolutionHeight, DOC(dai, node, Camera, getResolutionHeight)) + .def("sensorCenterCrop", &Camera::sensorCenterCrop, DOC(dai, node, Camera, sensorCenterCrop)) + .def("setSensorCrop", &Camera::setSensorCrop, py::arg("x"), py::arg("y"), DOC(dai, node, Camera, setSensorCrop)) + .def("getSensorCrop", &Camera::getSensorCrop, DOC(dai, node, Camera, getSensorCrop)) + .def("getSensorCropX", &Camera::getSensorCropX, DOC(dai, node, Camera, getSensorCropX)) + .def("getSensorCropY", &Camera::getSensorCropY, DOC(dai, node, Camera, getSensorCropY)) + + .def("setWaitForConfigInput", [](Camera& cam, bool wait){ + // Issue a deprecation warning + PyErr_WarnEx(PyExc_DeprecationWarning, "Use 'inputConfig.setWaitForMessage()' instead", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + cam.setWaitForConfigInput(wait); + HEDLEY_DIAGNOSTIC_POP + }, py::arg("wait"), DOC(dai, node, Camera, setWaitForConfigInput)) + + .def("getWaitForConfigInput", [](Camera& cam){ + // Issue a deprecation warning + PyErr_WarnEx(PyExc_DeprecationWarning, "Use 'inputConfig.setWaitForMessage()' instead", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + return cam.getWaitForConfigInput(); + HEDLEY_DIAGNOSTIC_POP + }, DOC(dai, node, Camera, getWaitForConfigInput)) + + .def("setPreviewKeepAspectRatio", &Camera::setPreviewKeepAspectRatio, py::arg("keep"), DOC(dai, node, Camera, setPreviewKeepAspectRatio)) + .def("getPreviewKeepAspectRatio", &Camera::getPreviewKeepAspectRatio, DOC(dai, node, Camera, getPreviewKeepAspectRatio)) + .def("setIspScale", static_cast(&Camera::setIspScale), py::arg("numerator"), py::arg("denominator"), DOC(dai, node, Camera, setIspScale)) + .def("setIspScale", static_cast)>(&Camera::setIspScale), py::arg("scale"), DOC(dai, node, Camera, setIspScale, 2)) + .def("setIspScale", static_cast(&Camera::setIspScale), py::arg("horizNum"), py::arg("horizDenom"), py::arg("vertNum"), py::arg("vertDenom"), DOC(dai, node, Camera, setIspScale, 3)) + .def("setIspScale", static_cast,std::tuple)>(&Camera::setIspScale), py::arg("horizScale"), py::arg("vertScale"), DOC(dai, node, Camera, setIspScale, 4)) + .def("getIspSize", &Camera::getIspSize, DOC(dai, node, Camera, getIspSize)) + .def("getIspWidth", &Camera::getIspWidth, DOC(dai, node, Camera, getIspWidth)) + .def("getIspHeight", &Camera::getIspHeight, DOC(dai, node, Camera, getIspHeight)) + + .def("setPreviewNumFramesPool", &Camera::setPreviewNumFramesPool, DOC(dai, node, Camera, setPreviewNumFramesPool)) + .def("setVideoNumFramesPool", &Camera::setVideoNumFramesPool, DOC(dai, node, Camera, setVideoNumFramesPool)) + .def("setStillNumFramesPool", &Camera::setStillNumFramesPool, DOC(dai, node, Camera, setStillNumFramesPool)) + .def("setRawNumFramesPool", &Camera::setRawNumFramesPool, DOC(dai, node, Camera, setRawNumFramesPool)) + .def("setIspNumFramesPool", &Camera::setIspNumFramesPool, DOC(dai, node, Camera, setIspNumFramesPool)) + .def("setNumFramesPool", &Camera::setNumFramesPool, py::arg("raw"), py::arg("isp"), py::arg("preview"), py::arg("video"), py::arg("still"), DOC(dai, node, Camera, setNumFramesPool)) + + .def("getPreviewNumFramesPool", &Camera::getPreviewNumFramesPool, DOC(dai, node, Camera, getPreviewNumFramesPool)) + .def("getVideoNumFramesPool", &Camera::getVideoNumFramesPool, DOC(dai, node, Camera, getVideoNumFramesPool)) + .def("getStillNumFramesPool", &Camera::getStillNumFramesPool, DOC(dai, node, Camera, getStillNumFramesPool)) + .def("getRawNumFramesPool", &Camera::getRawNumFramesPool, DOC(dai, node, Camera, getRawNumFramesPool)) + .def("getIspNumFramesPool", &Camera::getIspNumFramesPool, DOC(dai, node, Camera, getIspNumFramesPool)) + .def("setCamera", &Camera::setCamera, py::arg("name"), DOC(dai, node, Camera, setCamera)) + .def("getCamera", &Camera::getCamera, DOC(dai, node, Camera, getCamera)) + ; + // ALIAS + daiNodeModule.attr("Camera").attr("Properties") = cameraProperties; + +} diff --git a/src/pipeline/node/NodeBindings.cpp b/src/pipeline/node/NodeBindings.cpp index 21a591367..222ca997c 100644 --- a/src/pipeline/node/NodeBindings.cpp +++ b/src/pipeline/node/NodeBindings.cpp @@ -94,6 +94,7 @@ py::class_ bindNodeMap(py::handle scope, const std::string &na void bind_xlinkin(pybind11::module& m, void* pCallstack); void bind_xlinkout(pybind11::module& m, void* pCallstack); void bind_colorcamera(pybind11::module& m, void* pCallstack); +void bind_camera(pybind11::module& m, void* pCallstack); void bind_monocamera(pybind11::module& m, void* pCallstack); void bind_stereodepth(pybind11::module& m, void* pCallstack); void bind_neuralnetwork(pybind11::module& m, void* pCallstack); @@ -122,6 +123,7 @@ void NodeBindings::addToCallstack(std::deque& callstack) { callstack.push_front(bind_xlinkin); callstack.push_front(bind_xlinkout); callstack.push_front(bind_colorcamera); + callstack.push_front(bind_camera); callstack.push_front(bind_monocamera); callstack.push_front(bind_stereodepth); callstack.push_front(bind_neuralnetwork); From a4a1aada7fba2aa6e2ebefed4d0f8a0437132d93 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 13 Dec 2022 11:39:19 +0100 Subject: [PATCH 051/385] Starting to update bootloader docs --- docs/source/components/bootloader.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/components/bootloader.rst b/docs/source/components/bootloader.rst index 97abd163b..c148bdc78 100644 --- a/docs/source/components/bootloader.rst +++ b/docs/source/components/bootloader.rst @@ -60,6 +60,8 @@ Boot switches OAK-D-PoE with switches 2,4,5 ON, for the purpose of connecting to the device via USB. +On newever + API ### From c98132cde4881f41be3684ecf43dcf5a8af8ddb0 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 13 Dec 2022 11:46:36 +0100 Subject: [PATCH 052/385] Updated boot docs --- docs/source/components/bootloader.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/source/components/bootloader.rst b/docs/source/components/bootloader.rst index c148bdc78..86f80aaf7 100644 --- a/docs/source/components/bootloader.rst +++ b/docs/source/components/bootloader.rst @@ -56,11 +56,16 @@ Boot switches - **Boot from flash** - DIP switch: 0x03 (switches 5,6 ON) - used by OAK PoE and USB cameras when bootloader is installed. - **Recovery mode for USB** - DIP switch: 0x16 (switches 2,4,5 ON) - to boot directly into USB mode, so camera waits for the host to connect to it via USB. -.. image:: https://user-images.githubusercontent.com/18037362/154956812-c3fcc961-af46-4dfd-8080-e15c8c6b43f0.png +.. figure:: https://user-images.githubusercontent.com/18037362/154956812-c3fcc961-af46-4dfd-8080-e15c8c6b43f0.png -OAK-D-PoE with switches 2,4,5 ON, for the purpose of connecting to the device via USB. + OAK-D-PoE with switches 2,4,5 ON, for the purpose of connecting to the device via USB. -On newever +On newer versions of OAK devices we have 0 ohm resistors (see image below) instead of DIP switch, which means OAK will boot into flash by default. These new devices +have bootloader flashed, which handles the booting process. There's also an additional button on the baseboard that switches boot to recovery mode when +pressed, which can be useful if bootlaoder hasn't yet been flashed (eg. early access devices). You need to press this button when powering the device (when booting +happens). + +.. image:: https://user-images.githubusercontent.com/18037362/207295832-613fae0a-c0ae-411e-b03b-8a4736f1bfc7.png API ### From 54b7a876a27b25d87e9cf53a51197b0f06cda7b1 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 13 Dec 2022 13:31:12 +0100 Subject: [PATCH 053/385] Update grammar --- docs/source/components/bootloader.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/components/bootloader.rst b/docs/source/components/bootloader.rst index 86f80aaf7..2f90b22d1 100644 --- a/docs/source/components/bootloader.rst +++ b/docs/source/components/bootloader.rst @@ -60,9 +60,9 @@ Boot switches OAK-D-PoE with switches 2,4,5 ON, for the purpose of connecting to the device via USB. -On newer versions of OAK devices we have 0 ohm resistors (see image below) instead of DIP switch, which means OAK will boot into flash by default. These new devices -have bootloader flashed, which handles the booting process. There's also an additional button on the baseboard that switches boot to recovery mode when -pressed, which can be useful if bootlaoder hasn't yet been flashed (eg. early access devices). You need to press this button when powering the device (when booting +On newer versions of OAK devices we have 0 ohm resistors (see image below) instead of a DIP switch, which means OAK will boot into flash by default. These new devices +have the bootloader flashed, which handles the booting process. There's also an additional button on the baseboard that switches boot to recovery mode when +pressed, which can be useful if the bootloader hasn't yet been flashed (eg. early access devices). You need to press this button when powering the device (when booting happens). .. image:: https://user-images.githubusercontent.com/18037362/207295832-613fae0a-c0ae-411e-b03b-8a4736f1bfc7.png From 5bf03b71392352d5f65b0ece06b8983b45444388 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 13 Dec 2022 16:46:26 +0100 Subject: [PATCH 054/385] Update installation docs --- docs/source/install.rst | 107 +++++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 39 deletions(-) diff --git a/docs/source/install.rst b/docs/source/install.rst index 181e84c6e..11cc567e2 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -1,49 +1,81 @@ Installation ============ -Please install the necessary dependencies for your platform by :ref:`referring to the table below `. -Once installed, you can :ref:`install the DepthAI library `. +Please install the necessary dependencies for your platform by following the instructions below. -We are constantly striving to improve how we release our software to keep up -with countless platforms and the numerous ways to package it. If you do not -see a particular platform or package format listed below please reach out to -us on `Discord `__ -or on `Github `__. +.. tabs:: + + .. tab:: **Windows 10** + + Windows 10 users can either install DepthAI dependencies via `Windows Installer `__, + or follow :ref:`instructions below `. + + .. tab:: **macOS** + + Execute the script below to install macOS dependencies: + + .. code-block:: bash + + bash -c "$(curl -fL https://docs.luxonis.com/install_depthai.sh)" + + Please refer to :ref:`documentation below ` if any issues occur. + + .. tab:: **Linux** + + Execute the script below to install Linux dependencies: + + .. code-block:: bash + + sudo wget -qO- https://docs.luxonis.com/install_dependencies.sh | bash + + Please refer to :ref:`Supported Platforms` if any issues occur. + +Once installed, you can :ref:`install the DepthAI library `. Supported Platforms ################### -======================== ============================================== ================================================================================ -Platform Instructions Support -======================== ============================================== ================================================================================ -Windows 10 :ref:`Platform dependencies ` `Discord `__ -macOS :ref:`Platform dependencies ` `Discord `__ -Ubuntu :ref:`Platform dependencies ` `Discord `__ -Raspberry Pi OS :ref:`Platform dependencies ` `Discord `__ -Jestson Nano/Xavier :ref:`Platform dependencies ` `Discord `__ -======================== ============================================== ================================================================================ - -The following platforms are also supported by a combination of the community and Luxonis: - -====================== =========================================================================== ================================================================================ -Platform Instructions Support -====================== =========================================================================== ================================================================================ -Fedora `Discord `__ -Robot Operating System Follow tutorial at `depthai-ros `__ `Discord `__ -Windows 7 :ref:`WinUSB driver ` `Discord `__ -Docker :ref:`Pull and run official images ` `Discord `__ -Kernel Virtual Machine :ref:`Run on KVM ` `Discord `__ -VMware :ref:`Run on VMware ` `Discord `__ -Virtual Box :ref:`Run on Virtual Box ` `Discord `__ -WSL2 :ref:`Run on WSL2 ` / -====================== =========================================================================== ================================================================================ +See documentation below for other platforms or additional information. + +.. list-table:: + :header-rows: 1 + + * - Platform + - Instructions + * - Windows 10 + - :ref:`Platform dependencies ` + * - macOS + - :ref:`Platform dependencies ` + * - Ubuntu + - :ref:`Platform dependencies ` + * - Raspberry Pi OS + - :ref:`Platform dependencies ` + * - Jetson Nano/Xavier + - :ref:`Platform dependencies ` + * - ROS + - Follow tutorial at `depthai-ros `__ + * - Windows 7 + - :ref:`WinUSB driver ` + * - Docker + - :ref:`Pull and run official images ` + * - Kernel Virtual Machine + - :ref:`Run on KVM ` + * - VMware + - :ref:`Run on VMware ` + * - Virtual Box + - :ref:`Run on Virtual Box ` + * - WSL2 + - :ref:`Run on WSL2 ` + +If you do not see a particular platform or package format listed below please reach out to us on `Discord `__ +or on `Github `__. macOS ***** .. code-block:: bash - bash -c "$(curl -fL https://docs.luxonis.com/install_dependencies.sh)" + bash -c "$(curl -fL https://docs.luxonis.com/install_depthai.sh)" Close and re-open the terminal window after this command. @@ -51,8 +83,7 @@ The script also works on M1 Macs, Homebrew being installed under Rosetta 2, as s support. In case you already have Homebrew installed natively and things don't work, see `here `__ for some additional troubleshooting steps. -Note that if the video streaming window does not appear consider running the -following: +Note that if the video streaming window does not appear consider running the following: .. code-block:: bash @@ -80,15 +111,13 @@ Note! If opencv fails with illegal instruction after installing from PyPi, add: Raspberry Pi OS *************** - + .. code-block:: bash - - sudo curl -fL https://docs.luxonis.com/install_dependencies.sh | bash + sudo curl -fL https://docs.luxonis.com/install_dependencies.sh | bash We have also prepared `pre-configured RPi images `__ so you can get up & running faster. - Jetson ****** @@ -98,7 +127,7 @@ perform the following steps, after completing a fresh install and setup. On the This first step is optional: go to the *Software* (App Store) and delete the apps or software that you probably will not use. Open a terminal window and run the following commands: - + .. code-block:: bash sudo apt update && sudo apt upgrade From a076ff9ea0c21a17a98eaa8ab64264ac064686aa Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 13 Dec 2022 18:26:47 +0100 Subject: [PATCH 055/385] Updated install docs --- docs/source/index.rst | 5 +++-- docs/source/install.rst | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index fd203585f..3dc8e8fb2 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,8 +6,9 @@ DepthAI API Documentation ========================= -.. image:: https://github.com/luxonis/depthai-python/workflows/Python%20Wheel%20CI/badge.svg?branch=gen2_develop - :target: https://github.com/luxonis/depthai-python/actions?query=workflow%3A%22Python+Wheel+CI%22+branch%3A%22gen2_develop%22 +.. + .. image:: https://github.com/luxonis/depthai-python/workflows/Python%20Wheel%20CI/badge.svg?branch=gen2_develop + :target: https://github.com/luxonis/depthai-python/actions?query=workflow%3A%22Python+Wheel+CI%22+branch%3A%22gen2_develop%22 DepthAI API allows users to connect to, configure and communicate with their OAK devices. We support both :ref:`Python API ` and :ref:`C++ API `. diff --git a/docs/source/install.rst b/docs/source/install.rst index 11cc567e2..2994c0897 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -5,11 +5,6 @@ Please install the necessary dependencies for your platform by following the ins .. tabs:: - .. tab:: **Windows 10** - - Windows 10 users can either install DepthAI dependencies via `Windows Installer `__, - or follow :ref:`instructions below `. - .. tab:: **macOS** Execute the script below to install macOS dependencies: @@ -20,6 +15,11 @@ Please install the necessary dependencies for your platform by following the ins Please refer to :ref:`documentation below ` if any issues occur. + .. tab:: **Windows 10** + + Windows 10 users can either **install DepthAI dependencies** via `Windows Installer `__, + or follow :ref:`instructions below `. + .. tab:: **Linux** Execute the script below to install Linux dependencies: @@ -30,7 +30,7 @@ Please install the necessary dependencies for your platform by following the ins Please refer to :ref:`Supported Platforms` if any issues occur. -Once installed, you can :ref:`install the DepthAI library `. +Once installed, you can :ref:`install the DepthAI library ` from PyPI. Supported Platforms ################### From 13bd22c1331fd83f0b3ac389b1d461a4b9ec087e Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 14 Dec 2022 16:45:32 +0200 Subject: [PATCH 056/385] Update FW: Add missing python bindings for boundingBoxMapping --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index c95a06aac..3bac6a8d8 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit c95a06aac4b51bc062bed390ec83f5377fdea337 +Subproject commit 3bac6a8d80ee6d8aba69bd81058de12181e40007 From f20957502fa3d09d0d9fd66a5db0a4e23921f228 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Thu, 15 Dec 2022 22:37:23 +0100 Subject: [PATCH 057/385] Fix url for linux --- docs/source/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/install.rst b/docs/source/install.rst index 2994c0897..3140c1f4f 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -26,7 +26,7 @@ Please install the necessary dependencies for your platform by following the ins .. code-block:: bash - sudo wget -qO- https://docs.luxonis.com/install_dependencies.sh | bash + sudo wget -qO- https://docs.luxonis.com/install_depthai.sh | bash Please refer to :ref:`Supported Platforms` if any issues occur. From 3d5b514e4a3c199a93aced0293ec722a948a3bfc Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Fri, 16 Dec 2022 14:47:10 +0100 Subject: [PATCH 058/385] Updated NETWORK Bootloader with dual protocol capabilities --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 3bac6a8d8..0afad8f5c 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 3bac6a8d80ee6d8aba69bd81058de12181e40007 +Subproject commit 0afad8f5cf472f1609c91b0c7c26f27227f4fb93 From 3bd8b582541d04bc6e594a42648b8d47eab04257 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Fri, 16 Dec 2022 22:43:54 +0100 Subject: [PATCH 059/385] Added some bindings and modified camera preview example --- examples/Camera/camera_preview.py | 43 +++++++++++++++++++++++++ examples/Camera/camera_rgb_preview.py | 46 --------------------------- src/pipeline/node/CameraBindings.cpp | 4 +++ 3 files changed, 47 insertions(+), 46 deletions(-) create mode 100755 examples/Camera/camera_preview.py delete mode 100755 examples/Camera/camera_rgb_preview.py diff --git a/examples/Camera/camera_preview.py b/examples/Camera/camera_preview.py new file mode 100755 index 000000000..2e59f4535 --- /dev/null +++ b/examples/Camera/camera_preview.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +import cv2 +import depthai as dai + +# Connect to device and start pipeline +with dai.Device() as device: + print('Connected cameras:', device.getConnectedCameraFeatures()) + # Print out usb speed + print('Usb speed:', device.getUsbSpeed().name) + + # Create pipeline + pipeline = dai.Pipeline() + cams = device.getConnectedCameraFeatures() + streams = [] + for cam in cams: + c = pipeline.create(dai.node.Camera) + x = pipeline.create(dai.node.XLinkOut) + c.preview.link(x.input) + stream = str(cam.socket) + x.setStreamName(stream) + streams.append(stream) + + # Bootloader version + if device.getBootloaderVersion() is not None: + print('Bootloader version:', device.getBootloaderVersion()) + # Device name + print('Device name:', device.getDeviceName()) + + # Start pipeline + device.startPipeline(pipeline) + # Clear queue events + device.getQueueEvents() + while True: + + queueName = device.getQueueEvent(streams) + message = device.getOutputQueue(queueName).get() + # Display arrived frames + if type(message) == dai.ImgFrame: + cv2.imshow(queueName, message.getCvFrame()) + + if cv2.waitKey(1) == ord('q'): + break diff --git a/examples/Camera/camera_rgb_preview.py b/examples/Camera/camera_rgb_preview.py deleted file mode 100755 index 8960651d0..000000000 --- a/examples/Camera/camera_rgb_preview.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 - -import cv2 -import depthai as dai - -# Create pipeline -pipeline = dai.Pipeline() - -# Define source and output -cam = pipeline.create(dai.node.Camera) -xoutRgb = pipeline.create(dai.node.XLinkOut) - -xoutRgb.setStreamName("camera") - -# Properties -cam.setPreviewSize(300, 300) -cam.setInterleaved(False) -cam.setColorOrder(dai.CameraProperties.ColorOrder.RGB) -cam.setBoardSocket(dai.CameraBoardSocket.LEFT) - -# Linking -cam.isp.link(xoutRgb.input) - -# Connect to device and start pipeline -with dai.Device(pipeline) as device: - - print('Connected cameras:', device.getConnectedCameraFeatures()) - # Print out usb speed - print('Usb speed:', device.getUsbSpeed().name) - # Bootloader version - if device.getBootloaderVersion() is not None: - print('Bootloader version:', device.getBootloaderVersion()) - # Device name - print('Device name:', device.getDeviceName()) - - # Output queue will be used to get the rgb frames from the output defined above - qRgb = device.getOutputQueue(name="camera", maxSize=4, blocking=False) - - while True: - inRgb = qRgb.get() # blocking call, will wait until a new data has arrived - - # Retrieve 'bgr' (opencv format) frame - cv2.imshow("camera", inRgb.getCvFrame()) - - if cv2.waitKey(1) == ord('q'): - break diff --git a/src/pipeline/node/CameraBindings.cpp b/src/pipeline/node/CameraBindings.cpp index 98624b0eb..042e5a91f 100644 --- a/src/pipeline/node/CameraBindings.cpp +++ b/src/pipeline/node/CameraBindings.cpp @@ -182,6 +182,10 @@ void bind_camera(pybind11::module& m, void* pCallstack){ .def("getIspNumFramesPool", &Camera::getIspNumFramesPool, DOC(dai, node, Camera, getIspNumFramesPool)) .def("setCamera", &Camera::setCamera, py::arg("name"), DOC(dai, node, Camera, setCamera)) .def("getCamera", &Camera::getCamera, DOC(dai, node, Camera, getCamera)) + + .def("setSensorSize", static_cast(&Camera::setSensorSize), py::arg("width"), py::arg("height"), DOC(dai, node, Camera, setSensorSize)) + .def("setSensorSize", static_cast)>(&Camera::setSensorSize), py::arg("size"), DOC(dai, node, Camera, setSensorSize, 2)) + ; // ALIAS daiNodeModule.attr("Camera").attr("Properties") = cameraProperties; From 997fd8f86012243ee9be02c457989fcdfd1f4752 Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Sat, 17 Dec 2022 02:47:16 +0100 Subject: [PATCH 060/385] [FW] Fixed a bug in board downselection. OAK-D S2/Pro camera enumeration fix. --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 3bac6a8d8..744674523 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 3bac6a8d80ee6d8aba69bd81058de12181e40007 +Subproject commit 74467452363fa90fe9354135e2e6ced0bba7e5b8 From bae849d70627b3df62d8822b644acc36b86652d8 Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Sat, 17 Dec 2022 04:15:16 +0100 Subject: [PATCH 061/385] Updated camera_preview example --- examples/Camera/camera_preview.py | 37 +++++++++++-------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/examples/Camera/camera_preview.py b/examples/Camera/camera_preview.py index 2e59f4535..397ebf516 100755 --- a/examples/Camera/camera_preview.py +++ b/examples/Camera/camera_preview.py @@ -1,43 +1,32 @@ #!/usr/bin/env python3 - import cv2 import depthai as dai -# Connect to device and start pipeline +# Connect to device with dai.Device() as device: - print('Connected cameras:', device.getConnectedCameraFeatures()) - # Print out usb speed - print('Usb speed:', device.getUsbSpeed().name) - # Create pipeline pipeline = dai.Pipeline() cams = device.getConnectedCameraFeatures() streams = [] for cam in cams: + print(str(cam), str(cam.socket), cam.socket) c = pipeline.create(dai.node.Camera) x = pipeline.create(dai.node.XLinkOut) - c.preview.link(x.input) + c.isp.link(x.input) + c.setBoardSocket(cam.socket) stream = str(cam.socket) + if cam.name: + stream = f'{cam.name} ({stream})' x.setStreamName(stream) streams.append(stream) - - # Bootloader version - if device.getBootloaderVersion() is not None: - print('Bootloader version:', device.getBootloaderVersion()) - # Device name - print('Device name:', device.getDeviceName()) - # Start pipeline device.startPipeline(pipeline) - # Clear queue events - device.getQueueEvents() - while True: - - queueName = device.getQueueEvent(streams) - message = device.getOutputQueue(queueName).get() - # Display arrived frames - if type(message) == dai.ImgFrame: - cv2.imshow(queueName, message.getCvFrame()) - + while not device.isClosed(): + queueNames = device.getQueueEvents(streams) + for q in queueNames: + message = device.getOutputQueue(q).get() + # Display arrived frames + if type(message) == dai.ImgFrame: + cv2.imshow(q, message.getCvFrame()) if cv2.waitKey(1) == ord('q'): break From 3b3766f7539de37365b2c1f67b919f02378cc90f Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Sat, 17 Dec 2022 12:35:12 +0100 Subject: [PATCH 062/385] [FW] Added support for Mono video/preview in Camera node --- depthai-core | 2 +- examples/Camera/camera_preview.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depthai-core b/depthai-core index 26ba3000c..de4db9aa5 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 26ba3000c853d085e1ac35a9d9cf898ce546b6d0 +Subproject commit de4db9aa54fb5f17fc37eef2930efc8105661990 diff --git a/examples/Camera/camera_preview.py b/examples/Camera/camera_preview.py index 397ebf516..7e91f6017 100755 --- a/examples/Camera/camera_preview.py +++ b/examples/Camera/camera_preview.py @@ -12,7 +12,7 @@ print(str(cam), str(cam.socket), cam.socket) c = pipeline.create(dai.node.Camera) x = pipeline.create(dai.node.XLinkOut) - c.isp.link(x.input) + c.preview.link(x.input) c.setBoardSocket(cam.socket) stream = str(cam.socket) if cam.name: From 8f23f967df25dce484bf205fe058e6f4b941c494 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Sun, 18 Dec 2022 21:52:38 +0100 Subject: [PATCH 063/385] Added workflow notify for HIL tests --- .github/workflows/main.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e4145a19d..e6a02d277 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -538,3 +538,15 @@ jobs: repository: luxonis/robothub-apps event-type: depthai-python-release client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' + + notify_hil_workflow_linux_x86_64: + needs: [build-linux-x86_64] + runs-on: ubuntu-latest + steps: + - name: Repository Dispatch + uses: peter-evans/repository-dispatch@v2 + with: + token: ${{ secrets.HIL_CORE_DISPATCH_TOKEN }} + repository: luxonis/depthai-core-hil-tests + event-type: python-hil-event + client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' \ No newline at end of file From e5e841c9730a1e0477ab2364c9035686b64866a0 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Mon, 19 Dec 2022 14:49:52 +0100 Subject: [PATCH 064/385] Updated dual BL to v0.0.23 temporary build --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 0afad8f5c..56cb5b2f5 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 0afad8f5cf472f1609c91b0c7c26f27227f4fb93 +Subproject commit 56cb5b2f5498facbfcb4afd74c2897ed0f221361 From f26cb3b61a345ff9e522132ca6afee54ab72633b Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Mon, 19 Dec 2022 19:23:45 +0100 Subject: [PATCH 065/385] Added OAK-D-LR support. WIP: Orientation capability --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 5ebfc2ee9..401e41162 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 5ebfc2ee9a9c51d34085aa34aa8a2e0c92b8e073 +Subproject commit 401e4116246d5e46451c379c4b5fb5c7a5457773 From a9ece1ff08aac4830a625869b89f8fea7a07cafa Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Mon, 19 Dec 2022 22:13:24 +0100 Subject: [PATCH 066/385] [FW/XLink] Explicitly limited to single connection --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 6e945e428..e30a8b9b8 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 6e945e428aae604f9714ee2a1c7c7ec353ad65b4 +Subproject commit e30a8b9b8bf4e278a3ff0f0c7e0ac528641177f9 From 531a38495039538726713175d39c780bda88a03b Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 19 Dec 2022 22:51:41 +0100 Subject: [PATCH 067/385] Add python bindings for frame event --- depthai-core | 2 +- src/pipeline/node/ColorCameraBindings.cpp | 3 +++ src/pipeline/node/MonoCameraBindings.cpp | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 744674523..4e324ba00 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 74467452363fa90fe9354135e2e6ced0bba7e5b8 +Subproject commit 4e324ba0036017a2d04963695fc9e587993c0dd3 diff --git a/src/pipeline/node/ColorCameraBindings.cpp b/src/pipeline/node/ColorCameraBindings.cpp index 48956281f..cd39b8cc3 100644 --- a/src/pipeline/node/ColorCameraBindings.cpp +++ b/src/pipeline/node/ColorCameraBindings.cpp @@ -73,6 +73,7 @@ void bind_colorcamera(pybind11::module& m, void* pCallstack){ .def_readwrite("numFramesPoolVideo", &ColorCameraProperties::numFramesPoolVideo) .def_readwrite("numFramesPoolPreview", &ColorCameraProperties::numFramesPoolPreview) .def_readwrite("numFramesPoolStill", &ColorCameraProperties::numFramesPoolStill) + .def_readwrite("eventFilter", &ColorCameraProperties::eventFilter) ; // ColorCamera node @@ -122,6 +123,8 @@ void bind_colorcamera(pybind11::module& m, void* pCallstack){ .def("getResolution", &ColorCamera::getResolution, DOC(dai, node, ColorCamera, getResolution)) .def("setFps", &ColorCamera::setFps, py::arg("fps"), DOC(dai, node, ColorCamera, setFps)) .def("getFps", &ColorCamera::getFps, DOC(dai, node, ColorCamera, getFps)) + .def("setFrameEventFilter", &ColorCamera::setFrameEventFilter, py::arg("events"), DOC(dai, node, ColorCamera, setFrameEventFilter)) + .def("getFrameEventFilter", &ColorCamera::getFrameEventFilter, DOC(dai, node, ColorCamera, getFrameEventFilter)) .def("getPreviewSize", &ColorCamera::getPreviewSize, DOC(dai, node, ColorCamera, getPreviewSize)) .def("getPreviewWidth", &ColorCamera::getPreviewWidth, DOC(dai, node, ColorCamera, getPreviewWidth)) .def("getPreviewHeight", &ColorCamera::getPreviewHeight, DOC(dai, node, ColorCamera, getPreviewHeight)) diff --git a/src/pipeline/node/MonoCameraBindings.cpp b/src/pipeline/node/MonoCameraBindings.cpp index b25acaf6d..5ff0bf685 100644 --- a/src/pipeline/node/MonoCameraBindings.cpp +++ b/src/pipeline/node/MonoCameraBindings.cpp @@ -45,6 +45,7 @@ void bind_monocamera(pybind11::module& m, void* pCallstack){ .def_readwrite("fps", &MonoCameraProperties::fps) .def_readwrite("numFramesPool", &MonoCameraProperties::numFramesPool) .def_readwrite("numFramesPoolRaw", &MonoCameraProperties::numFramesPoolRaw) + .def_readwrite("eventFilter", &MonoCameraProperties::eventFilter) ; // Node @@ -76,6 +77,8 @@ void bind_monocamera(pybind11::module& m, void* pCallstack){ .def("getImageOrientation", &MonoCamera::getImageOrientation, DOC(dai, node, MonoCamera, getImageOrientation)) .def("setResolution", &MonoCamera::setResolution, py::arg("resolution"), DOC(dai, node, MonoCamera, setResolution)) .def("getResolution", &MonoCamera::getResolution, DOC(dai, node, MonoCamera, getResolution)) + .def("setFrameEventFilter", &MonoCamera::setFrameEventFilter, py::arg("events"), DOC(dai, node, MonoCamera, setFrameEventFilter)) + .def("getFrameEventFilter", &MonoCamera::getFrameEventFilter, DOC(dai, node, MonoCamera, getFrameEventFilter)) .def("setFps", &MonoCamera::setFps, py::arg("fps"), DOC(dai, node, MonoCamera, setFps)) .def("getFps", &MonoCamera::getFps, DOC(dai, node, MonoCamera, getFps)) .def("getResolutionSize", &MonoCamera::getResolutionSize, DOC(dai, node, MonoCamera, getResolutionSize)) From 646d52e91329348b3ca96ea976b97857706edf93 Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Wed, 21 Dec 2022 17:13:37 +0100 Subject: [PATCH 068/385] ImageManip added colormap capability. TODO min/max range selection --- depthai-core | 2 +- examples/StereoDepth/depth_colormap.py | 62 +++++++++++++++++++ src/pipeline/CommonBindings.cpp | 28 +++++++++ .../datatype/ImageManipConfigBindings.cpp | 4 +- 4 files changed, 94 insertions(+), 2 deletions(-) create mode 100755 examples/StereoDepth/depth_colormap.py diff --git a/depthai-core b/depthai-core index de4db9aa5..89665e38d 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit de4db9aa54fb5f17fc37eef2930efc8105661990 +Subproject commit 89665e38d1192c90d25d03950828670bcc289d6d diff --git a/examples/StereoDepth/depth_colormap.py b/examples/StereoDepth/depth_colormap.py new file mode 100755 index 000000000..b334ffb34 --- /dev/null +++ b/examples/StereoDepth/depth_colormap.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +import cv2 +import depthai as dai +import numpy as np + +# Closer-in minimum depth, disparity range is doubled (from 95 to 190): +extended_disparity = False +# Better accuracy for longer distance, fractional disparity 32-levels: +subpixel = False +# Better handling for occlusions: +lr_check = True + +# Create pipeline +pipeline = dai.Pipeline() + +# Define sources and outputs +monoLeft = pipeline.create(dai.node.MonoCamera) +monoRight = pipeline.create(dai.node.MonoCamera) +depth = pipeline.create(dai.node.StereoDepth) +xout = pipeline.create(dai.node.XLinkOut) + +xout.setStreamName("disparity") + +# Properties +monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) +monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) +monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) + +# Create a node that will produce the depth map (using disparity output as it's easier to visualize depth this way) +depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) +# Options: MEDIAN_OFF, KERNEL_3x3, KERNEL_5x5, KERNEL_7x7 (default) +depth.initialConfig.setMedianFilter(dai.MedianFilter.KERNEL_7x7) +depth.setLeftRightCheck(lr_check) +depth.setExtendedDisparity(extended_disparity) +depth.setSubpixel(subpixel) + +# Create a colormap +colormap = pipeline.create(dai.node.ImageManip) +colormap.initialConfig.setColormap(dai.Colormap.TURBO, 0, 95) +colormap.initialConfig.setFrameType(dai.ImgFrame.Type.NV12) + +# Linking +monoLeft.out.link(depth.left) +monoRight.out.link(depth.right) +depth.disparity.link(colormap.inputImage) +colormap.out.link(xout.input) + +# Connect to device and start pipeline +with dai.Device(pipeline) as device: + + # Output queue will be used to get the disparity frames from the outputs defined above + q = device.getOutputQueue(name="disparity", maxSize=4, blocking=False) + + while True: + inDisparity = q.get() # blocking call, will wait until a new data has arrived + frame = inDisparity.getCvFrame() + cv2.imshow("disparity", frame) + + if cv2.waitKey(1) == ord('q'): + break \ No newline at end of file diff --git a/src/pipeline/CommonBindings.cpp b/src/pipeline/CommonBindings.cpp index 2f8c346bc..9c1602f74 100644 --- a/src/pipeline/CommonBindings.cpp +++ b/src/pipeline/CommonBindings.cpp @@ -19,6 +19,7 @@ #include "depthai-shared/common/DetectionParserOptions.hpp" #include "depthai-shared/common/RotatedRect.hpp" #include "depthai-shared/common/Rect.hpp" +#include "depthai-shared/common/Colormap.hpp" // depthai #include "depthai/common/CameraFeatures.hpp" @@ -50,6 +51,7 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ py::class_ detectionParserOptions(m, "DetectionParserOptions", DOC(dai, DetectionParserOptions)); py::class_ rotatedRect(m, "RotatedRect", DOC(dai, RotatedRect)); py::class_ rect(m, "Rect", DOC(dai, Rect)); + py::enum_ colormap(m, "Colormap", DOC(dai, Colormap)); /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// @@ -290,4 +292,30 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("iouThreshold", &DetectionParserOptions::iouThreshold) ; + colormap + .value("NONE", Colormap::NONE) + .value("AUTUMN", Colormap::AUTUMN) + .value("BONE", Colormap::BONE) + .value("JET", Colormap::JET) + .value("WINTER", Colormap::WINTER) + .value("RAINBOW", Colormap::RAINBOW) + .value("OCEAN", Colormap::OCEAN) + .value("SUMMER", Colormap::SUMMER) + .value("SPRING", Colormap::SPRING) + .value("COOL", Colormap::COOL) + .value("HSV", Colormap::HSV) + .value("PINK", Colormap::PINK) + .value("HOT", Colormap::HOT) + .value("PARULA", Colormap::PARULA) + .value("MAGMA", Colormap::MAGMA) + .value("INFERNO", Colormap::INFERNO) + .value("PLASMA", Colormap::PLASMA) + .value("VIRIDIS", Colormap::VIRIDIS) + .value("CIVIDIS", Colormap::CIVIDIS) + .value("TWILIGHT", Colormap::TWILIGHT) + .value("TWILIGHT_SHIFTED", Colormap::TWILIGHT_SHIFTED) + .value("TURBO", Colormap::TURBO) + .value("DEEPGREEN", Colormap::DEEPGREEN) + ; + } diff --git a/src/pipeline/datatype/ImageManipConfigBindings.cpp b/src/pipeline/datatype/ImageManipConfigBindings.cpp index 1ead6d0cc..05b3f38c2 100644 --- a/src/pipeline/datatype/ImageManipConfigBindings.cpp +++ b/src/pipeline/datatype/ImageManipConfigBindings.cpp @@ -111,7 +111,8 @@ void bind_imagemanipconfig(pybind11::module& m, void* pCallstack){ .def("setResize", static_cast)>(&ImageManipConfig::setResize), py::arg("size"), DOC(dai, ImageManipConfig, setResize, 2)) .def("setResizeThumbnail", static_cast(&ImageManipConfig::setResizeThumbnail), py::arg("w"), py::arg("h"), py::arg("bgRed")=0, py::arg("bgGreen")=0, py::arg("bgBlue")=0, DOC(dai, ImageManipConfig, setResizeThumbnail)) .def("setResizeThumbnail", static_cast, int, int, int)>(&ImageManipConfig::setResizeThumbnail), py::arg("size"), py::arg("bgRed")=0, py::arg("bgGreen")=0, py::arg("bgBlue")=0, DOC(dai, ImageManipConfig, setResizeThumbnail, 2)) - .def("setFrameType", &ImageManipConfig::setFrameType, py::arg("name"), DOC(dai, ImageManipConfig, setFrameType)) + .def("setFrameType", &ImageManipConfig::setFrameType, py::arg("type"), DOC(dai, ImageManipConfig, setFrameType)) + .def("setColormap", &ImageManipConfig::setColormap, py::arg("colormap"), py::arg("min") = 0, py::arg("max") = 255, DOC(dai, ImageManipConfig, setColormap)) .def("setHorizontalFlip", &ImageManipConfig::setHorizontalFlip, py::arg("flip"), DOC(dai, ImageManipConfig, setHorizontalFlip)) .def("setVerticalFlip", &ImageManipConfig::setVerticalFlip, py::arg("flip"), DOC(dai, ImageManipConfig, setVerticalFlip)) .def("setReusePreviousImage", &ImageManipConfig::setReusePreviousImage, py::arg("reuse"), DOC(dai, ImageManipConfig, setReusePreviousImage)) @@ -129,6 +130,7 @@ void bind_imagemanipconfig(pybind11::module& m, void* pCallstack){ .def("getResizeConfig", &ImageManipConfig::getResizeConfig, DOC(dai, ImageManipConfig, getResizeConfig)) .def("getFormatConfig", &ImageManipConfig::getFormatConfig, DOC(dai, ImageManipConfig, getFormatConfig)) .def("isResizeThumbnail", &ImageManipConfig::isResizeThumbnail, DOC(dai, ImageManipConfig, isResizeThumbnail)) + .def("getColormap", &ImageManipConfig::getColormap, DOC(dai, ImageManipConfig, getColormap)) ; From 6cfdd5675852dac373922a303526d7195bbb4178 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Thu, 22 Dec 2022 20:24:57 +0200 Subject: [PATCH 069/385] Add option to override baseline and/or focal length for disparity to depth conversion --- depthai-core | 2 +- src/pipeline/node/StereoDepthBindings.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index bc8dce62a..9e199d526 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit bc8dce62acbf47194fe904e10cf4fe40ecb2db74 +Subproject commit 9e199d526914d7b7cdad39c29684feb73b749d45 diff --git a/src/pipeline/node/StereoDepthBindings.cpp b/src/pipeline/node/StereoDepthBindings.cpp index de473accd..1ed9e43cb 100644 --- a/src/pipeline/node/StereoDepthBindings.cpp +++ b/src/pipeline/node/StereoDepthBindings.cpp @@ -166,6 +166,8 @@ void bind_stereodepth(pybind11::module& m, void* pCallstack){ }, DOC(dai, node, StereoDepth, setFocalLengthFromCalibration)) .def("useHomographyRectification", &StereoDepth::useHomographyRectification, DOC(dai, node, StereoDepth, useHomographyRectification)) .def("enableDistortionCorrection", &StereoDepth::enableDistortionCorrection, DOC(dai, node, StereoDepth, enableDistortionCorrection)) + .def("setBaseline", &StereoDepth::setBaseline, DOC(dai, node, StereoDepth, setBaseline)) + .def("setFocalLength", &StereoDepth::setFocalLength, DOC(dai, node, StereoDepth, setFocalLength)) ; // ALIAS daiNodeModule.attr("StereoDepth").attr("Properties") = stereoDepthProperties; From 518c575d7b050a91cc76f0e42bec0d5c1bc72063 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Thu, 22 Dec 2022 20:30:30 +0100 Subject: [PATCH 070/385] Fix Update bootloader button --- utilities/device_manager.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index ef2a4664e..728df5b5d 100755 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -103,16 +103,13 @@ def __init__(self, text): self.ok = False layout = [ [sg.Text(text)], - [sg.Submit(button_text="Yes"), sg.Cancel(button_text="No")], + [sg.Submit(), sg.Cancel()], ] self.window = sg.Window("Are You Sure?", layout, size=(450,150), modal=True, finalize=True) def wait(self): event, values = self.window.Read() self.window.close() - if values is not None: - return str(event) == "Submit" - else: - return False + return str(event) == "Submit" class SelectIP: @@ -188,8 +185,8 @@ def wait(self) -> dai.DeviceInfo: return deviceSelected def flashBootloader(bl: dai.DeviceBootloader, device: dai.DeviceInfo, type: dai.DeviceBootloader.Type): - factoryBlWarningMessage = """Main Bootloader type or version doesn't support User Bootloader flashing. -Main (factory) bootloader will be updated instead. + factoryBlWarningMessage = """Factory Bootloader type or version doesn't support User Bootloader flashing. +Factory bootloader will be updated instead. Proceed with caution """ @@ -407,7 +404,7 @@ def deviceStateTxt(state: dai.XLinkDeviceState) -> str: [sg.HSeparator()], [ sg.Text("", size=(7, 2)), - sg.Button("Flash Newest Bootloader", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, + sg.Button("Update Bootloader", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFA500'), ] ] @@ -614,7 +611,7 @@ def run(self) -> None: if self.bl is None: continue self.getConfigs() self.unlockConfig() - elif event == "Flash Newest Bootloader": + elif event == "Update Bootloader": # Use current type if flashBootloader(self.bl, self.device, self.bl.getType()): # Device will reboot, close previous and reset GUI @@ -826,7 +823,7 @@ def unlockConfig(self): for el in CONF_TEXT_USB: self.window[el].update(text_color="black") - self.window['Flash Newest Bootloader'].update(disabled=False) + self.window['Update Bootloader'].update(disabled=False) self.window['flashFactoryBootloader'].update(disabled=False) self.window['Flash configuration'].update(disabled=False) self.window['Clear configuration'].update(disabled=False) @@ -851,7 +848,7 @@ def resetGui(self): for el in conf: self.window[el].update(text_color="gray") - self.window['Flash Newest Bootloader'].update(disabled=True) + self.window['Update Bootloader'].update(disabled=True) self.window['flashFactoryBootloader'].update(disabled=True) self.window['Flash configuration'].update(disabled=True) self.window['Clear configuration'].update(disabled=True) From fb04df87c5068b26b501ead531dba90e30751e74 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Fri, 23 Dec 2022 02:47:29 +0100 Subject: [PATCH 071/385] [FW] OAK-D-LR - Fixed default image orientation and added depth preview example --- depthai-core | 2 +- examples/StereoDepth/depth_preview_lr.py | 64 ++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100755 examples/StereoDepth/depth_preview_lr.py diff --git a/depthai-core b/depthai-core index bc8dce62a..1eb01248f 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit bc8dce62acbf47194fe904e10cf4fe40ecb2db74 +Subproject commit 1eb01248fb7e994b51ceec91984bff46e0a5d5f2 diff --git a/examples/StereoDepth/depth_preview_lr.py b/examples/StereoDepth/depth_preview_lr.py new file mode 100755 index 000000000..82caea026 --- /dev/null +++ b/examples/StereoDepth/depth_preview_lr.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 + +import cv2 +import depthai as dai +import numpy as np + +# Closer-in minimum depth, disparity range is doubled (from 95 to 190): +extended_disparity = False +# Better accuracy for longer distance, fractional disparity 32-levels: +subpixel = False +# Better handling for occlusions: +lr_check = True + +# Create pipeline +pipeline = dai.Pipeline() + +# Define sources and outputs +left = pipeline.create(dai.node.ColorCamera) +right = pipeline.create(dai.node.ColorCamera) +depth = pipeline.create(dai.node.StereoDepth) +xout = pipeline.create(dai.node.XLinkOut) +xoutl = pipeline.create(dai.node.XLinkOut) +xoutr = pipeline.create(dai.node.XLinkOut) + +xout.setStreamName("disparity") +xoutl.setStreamName("rectifiedLeft") +xoutr.setStreamName("rectifiedRight") + +# Properties +left.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1200_P) +left.setBoardSocket(dai.CameraBoardSocket.LEFT) +right.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1200_P) +right.setBoardSocket(dai.CameraBoardSocket.RIGHT) +right.setIspScale(2, 3) +left.setIspScale(2, 3) + + +# Create a node that will produce the depth map (using disparity output as it's easier to visualize depth this way) +depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) +# Options: MEDIAN_OFF, KERNEL_3x3, KERNEL_5x5, KERNEL_7x7 (default) +depth.initialConfig.setMedianFilter(dai.MedianFilter.KERNEL_7x7) +depth.setInputResolution(1280, 800) +depth.setLeftRightCheck(lr_check) +depth.setExtendedDisparity(extended_disparity) +depth.setSubpixel(subpixel) + +# Linking +left.isp.link(depth.left) +right.isp.link(depth.right) +depth.disparity.link(xout.input) +depth.rectifiedLeft.link(xoutl.input) +depth.rectifiedRight.link(xoutr.input) + +# Connect to device and start pipeline +with dai.Device(pipeline) as device: + while not device.isClosed(): + queueNames = device.getQueueEvents() + for q in queueNames: + message = device.getOutputQueue(q).get() + # Display arrived frames + if type(message) == dai.ImgFrame: + cv2.imshow(q, message.getCvFrame()) + if cv2.waitKey(1) == ord('q'): + break \ No newline at end of file From 1057277bfd7f0fc95912b03002754a69d5c5cc4f Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Fri, 23 Dec 2022 03:09:11 +0100 Subject: [PATCH 072/385] Added additional comment to device_manager --- utilities/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utilities/README.md b/utilities/README.md index 2f91deb7a..0f87c2e34 100644 --- a/utilities/README.md +++ b/utilities/README.md @@ -22,3 +22,5 @@ To build standalone executable issue the following command: ```sh pyinstaller --onefile -w --icon=assets/icon.ico --add-data="assets/icon.ico;assets" --add-data="assets/icon.png;assets" device_manager.py ``` + +Optionally, append `--runtime-tmpdir [path or .]` to modify where the temporary directory should be created when launched. From cb490957e8dd790f66245efe28bb8b88a493fa6f Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 16 Dec 2022 23:37:31 +0100 Subject: [PATCH 073/385] yet another installation docs update --- docs/source/install.rst | 134 ++++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/docs/source/install.rst b/docs/source/install.rst index 3140c1f4f..960eecb9e 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -1,7 +1,10 @@ Installation ============ -Please install the necessary dependencies for your platform by following the instructions below. +Refer to `DepthAI installer documentation `__ to install +full `DepthAI demo `__ and its dependencies and requirements. + +Follow the steps below to install dependencies for your platform. .. tabs:: @@ -11,13 +14,13 @@ Please install the necessary dependencies for your platform by following the ins .. code-block:: bash - bash -c "$(curl -fL https://docs.luxonis.com/install_depthai.sh)" + bash -c "$(curl -fL https://docs.luxonis.com/install_dependencies.sh)" Please refer to :ref:`documentation below ` if any issues occur. .. tab:: **Windows 10** - Windows 10 users can either **install DepthAI dependencies** via `Windows Installer `__, + Windows 10 users can either **install DepthAI dependencies** via `Windows Installer `__, or follow :ref:`instructions below `. .. tab:: **Linux** @@ -429,7 +432,8 @@ And then for each additional depthai/OAK device you would like to pass through, Install from PyPI ################# -Our packages are distributed `via PyPi `__, to install it in your environment use +After installing depthai dependencies, you can either refer to depthai-core for C++ development, or download the depthai **Python library** +`via PyPi `__: .. code-block:: bash @@ -497,102 +501,100 @@ Other installation methods To get the latest and yet unreleased features from our source code, you can go ahead and compile depthai package manually. -Dependencies to build from source -********************************* +.. tabs:: -- CMake > 3.2.0 -- Generation tool (Ninja, make, ...) -- C/C++ compiler -- libusb1 development package + .. tab:: **Build from source** -.. _raspbian: + **Dependencies to build from source** -Ubuntu, Raspberry Pi OS, ... (Debian based systems) ---------------------------------------------------- + - CMake > 3.2.0 + - Generation tool (Ninja, make, ...) + - C/C++ compiler + - libusb1 development package -On Debian based systems (Raspberry Pi OS, Ubuntu, ...) these can be acquired by running: + .. tabs:: -.. code-block:: bash + .. tab:: Debian/Ubuntu/RPi OS - sudo apt-get -y install cmake libusb-1.0-0-dev build-essential + On Debian based systems (Raspberry Pi OS, Ubuntu, ...) these can be acquired by running: -macOS (Mac OS X) ----------------- + .. code-block:: bash -Assuming a stock Mac OS X install, `depthai-python `__ library needs following dependencies + sudo apt-get -y install cmake libusb-1.0-0-dev build-essential -- Homebrew (If it's not installed already) + .. tab:: maxOS - .. code-block:: bash + Assuming a stock Mac OS X install, `depthai-python `__ library needs following dependencies - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" + - Homebrew (If it's not installed already) -- Python, :code:`libusb`, CMake, :code:`wget` + .. code-block:: bash - .. code-block:: bash + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" - brew install coreutils python3 cmake libusb wget + - Python, :code:`libusb`, CMake, :code:`wget` -And now you're ready to clone the `depthai-python `__ from Github and build it for Mac OS X. + .. code-block:: bash -Install using GitHub commit -*************************** + brew install coreutils python3 cmake libusb wget -Pip allows users to install the packages from specific commits, even if they are not yet released on PyPi. + And now you're ready to clone the `depthai-python `__ from Github and build it for Mac OS X. -To do so, use the command below - and be sure to replace the :code:`` with the correct commit hash `from here `__ + .. tab:: **Install from commit** -.. code-block:: bash + Pip allows users to install the packages from specific commits, even if they are not yet released on PyPi. - python3 -m pip install git+https://github.com/luxonis/depthai-python.git@ + To do so, use the command below - and be sure to replace the :code:`` with the correct commit hash `from here `__ -Using/Testing a Specific Branch/PR -********************************** + .. code-block:: bash -From time to time, it may be of interest to use a specific branch. This may occur, for example, -because we have listened to your feature request and implemented a quick implementation in a branch. -Or it could be to get early access to a feature that is soaking in our :code:`develop` for stability purposes before being merged into :code:`main` -(:code:`develop` is the branch we use to soak new features before merging them into :code:`main`): + python3 -m pip install git+https://github.com/luxonis/depthai-python.git@ -So when working in the `depthai-python `__ repository, using a branch can be accomplished -with the following commands. + .. tab:: **Using specific branch/PR** -Prior to running the following, you can either clone the repository independently -(for not over-writing any of your local changes) or simply do a :code:`git pull` first. + From time to time, it may be of interest to use a specific branch. This may occur, for example, + because we have listened to your feature request and implemented a quick implementation in a branch. + Or it could be to get early access to a feature that is soaking in our :code:`develop` for stability purposes before being merged into :code:`main` + (:code:`develop` is the branch we use to soak new features before merging them into :code:`main`): -.. code-block:: bash + So when working in the `depthai-python `__ repository, using a branch can be accomplished + with the following commands. - git checkout - git submodule update --init --recursive - python3 setup.py develop + Prior to running the following, you can either clone the repository independently + (for not over-writing any of your local changes) or simply do a :code:`git pull` first. -Install from source -******************* + .. code-block:: bash -If desired, you can also install the package from the source code itself - it will allow you to make the changes -to the API and see them live in action. + git checkout + git submodule update --init --recursive + python3 setup.py develop -To do so, first download the repository and then add the package to your python interpreter in development mode + .. tab:: **Install from source** -.. code-block:: bash + If desired, you can also install the package from the source code itself - it will allow you to make the changes + to the API and see them live in action. - git clone https://github.com/luxonis/depthai-python.git - cd depthai-python - git submodule update --init --recursive - python3 setup.py develop # you may need to add sudo if using system interpreter instead of virtual environment + To do so, first download the repository and then add the package to your python interpreter in development mode -If you want to use other branch (e.g. :code:`develop`) than default (:code:`main`), you can do so by typing + .. code-block:: bash -.. code-block:: bash + git clone https://github.com/luxonis/depthai-python.git + cd depthai-python + git submodule update --init --recursive + python3 setup.py develop # you may need to add sudo if using system interpreter instead of virtual environment - git checkout develop # replace the "develop" with a desired branch name - git submodule update --recursive - python3 setup.py develop + If you want to use other branch (e.g. :code:`develop`) than default (:code:`main`), you can do so by typing -Or, if you want to checkout a specific commit, type + .. code-block:: bash -.. code-block:: bash + git checkout develop # replace the "develop" with a desired branch name + git submodule update --recursive + python3 setup.py develop + + Or, if you want to checkout a specific commit, type + + .. code-block:: bash - git checkout - git submodule update --recursive - python3 setup.py develop + git checkout + git submodule update --recursive + python3 setup.py develop From 7ef595ba468c574081fe12100723e5608a498960 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 23 Dec 2022 14:42:35 +0100 Subject: [PATCH 074/385] Added note to refer people to installer usage --- docs/source/install.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/source/install.rst b/docs/source/install.rst index 960eecb9e..020b76b61 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -1,10 +1,12 @@ Installation ============ -Refer to `DepthAI installer documentation `__ to install -full `DepthAI demo `__ and its dependencies and requirements. +.. note:: + + Refer to `DepthAI installer documentation `__ to install + full `DepthAI demo `__ and its dependencies and requirements. -Follow the steps below to install dependencies for your platform. +Follow the steps below to just install depthai api library dependencies for your platform. .. tabs:: From 0804b7ccac8fd32b4fe9173d31f06a72e2189c60 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 27 Dec 2022 12:39:44 +0100 Subject: [PATCH 075/385] Modified LR depth example --- examples/StereoDepth/depth_preview_lr.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/StereoDepth/depth_preview_lr.py b/examples/StereoDepth/depth_preview_lr.py index 82caea026..7d62b0302 100755 --- a/examples/StereoDepth/depth_preview_lr.py +++ b/examples/StereoDepth/depth_preview_lr.py @@ -5,9 +5,9 @@ import numpy as np # Closer-in minimum depth, disparity range is doubled (from 95 to 190): -extended_disparity = False +extended_disparity = True # Better accuracy for longer distance, fractional disparity 32-levels: -subpixel = False +subpixel = True # Better handling for occlusions: lr_check = True @@ -43,6 +43,7 @@ depth.setLeftRightCheck(lr_check) depth.setExtendedDisparity(extended_disparity) depth.setSubpixel(subpixel) +depth.setInputResolution(1280, 800) # Linking left.isp.link(depth.left) @@ -59,6 +60,13 @@ message = device.getOutputQueue(q).get() # Display arrived frames if type(message) == dai.ImgFrame: - cv2.imshow(q, message.getCvFrame()) + frame = message.getCvFrame() + if 'disparity' in q: + maxDisp = depth.initialConfig.getMaxDisparity() + disp = (frame * (255.0 / maxDisp)).astype(np.uint8) + disp = cv2.applyColorMap(disp, cv2.COLORMAP_JET) + cv2.imshow(q, disp) + else: + cv2.imshow(q, frame) if cv2.waitKey(1) == ord('q'): break \ No newline at end of file From a0aa61903a15aa93f8e5728ebbe6066bc8af0abd Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 27 Dec 2022 15:50:13 +0100 Subject: [PATCH 076/385] Start working on motion blur docs --- docs/source/tutorials/image_quality.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/source/tutorials/image_quality.rst b/docs/source/tutorials/image_quality.rst index 4216bc442..fc1a7d400 100644 --- a/docs/source/tutorials/image_quality.rst +++ b/docs/source/tutorials/image_quality.rst @@ -75,5 +75,16 @@ To tune your own camera sensors, one would need Intel's software, for which a li .. image:: https://user-images.githubusercontent.com/18037362/149826169-3b92901d-3367-460b-afbf-c33d8dc9d118.jpeg +Motion blur +########### + +`Motion blur `__ appears when the camera shutter is open for a longer time, and the object is moving during that time. + +.. image:: + +Potential workarounds: + +- Decrease the shutter (exposure) time - this will decrease the motion blur, but will also decrease the light that reaches the sensor, so the image will be darker. You could either use larger sensor (so more photons hit the sensor), or use a higher ISO (sensitivity) value. +- If the motion blur negatively affects your model's accuracy, you could fine-tune it to be more robust to motion blur by including motion blur images in your training dataset. .. include:: /includes/footer-short.rst \ No newline at end of file From ff29a7765410fe4b88f4f927de06831d65420ccf Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 27 Dec 2022 15:58:39 +0100 Subject: [PATCH 077/385] Updated image --- docs/source/tutorials/image_quality.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/source/tutorials/image_quality.rst b/docs/source/tutorials/image_quality.rst index fc1a7d400..d01d1b6d8 100644 --- a/docs/source/tutorials/image_quality.rst +++ b/docs/source/tutorials/image_quality.rst @@ -80,7 +80,10 @@ Motion blur `Motion blur `__ appears when the camera shutter is open for a longer time, and the object is moving during that time. -.. image:: +.. image:: https://user-images.githubusercontent.com/18037362/209683640-2a640794-8422-4119-9d78-6c23690418a1.jpg + +On the image above the right foot moved about 50 pixels during the exposure time, which results in a blurry image in that region. +The left foot was on the ground the whole time of the exposure, so it's not blurry. Potential workarounds: From e99ef336439b14529e2c31ec932694ba642b4f17 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 27 Dec 2022 16:48:18 +0100 Subject: [PATCH 078/385] Fixed typos --- docs/source/tutorials/image_quality.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/tutorials/image_quality.rst b/docs/source/tutorials/image_quality.rst index d01d1b6d8..870e151d4 100644 --- a/docs/source/tutorials/image_quality.rst +++ b/docs/source/tutorials/image_quality.rst @@ -78,16 +78,16 @@ To tune your own camera sensors, one would need Intel's software, for which a li Motion blur ########### -`Motion blur `__ appears when the camera shutter is open for a longer time, and the object is moving during that time. +`Motion blur `__ appears when the camera shutter is opened for a longer time, and the object is moving during that time. .. image:: https://user-images.githubusercontent.com/18037362/209683640-2a640794-8422-4119-9d78-6c23690418a1.jpg -On the image above the right foot moved about 50 pixels during the exposure time, which results in a blurry image in that region. +In the image above the right foot moved about 50 pixels during the exposure time, which results in a blurry image in that region. The left foot was on the ground the whole time of the exposure, so it's not blurry. -Potential workarounds: +**Potential workarounds:** -- Decrease the shutter (exposure) time - this will decrease the motion blur, but will also decrease the light that reaches the sensor, so the image will be darker. You could either use larger sensor (so more photons hit the sensor), or use a higher ISO (sensitivity) value. +- Decrease the shutter (exposure) time - this will decrease the motion blur, but will also decrease the light that reaches the sensor, so the image will be darker. You could either use a larger sensor (so more photons hit the sensor) or use a higher ISO (sensitivity) value. - If the motion blur negatively affects your model's accuracy, you could fine-tune it to be more robust to motion blur by including motion blur images in your training dataset. .. include:: /includes/footer-short.rst \ No newline at end of file From c7e4a29f5e79ae6cbe3ac19462c71db959d4def4 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Wed, 28 Dec 2022 20:02:58 +0100 Subject: [PATCH 079/385] Fixed image_manip_warp_mesh.py example --- examples/ImageManip/image_manip_warp_mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ImageManip/image_manip_warp_mesh.py b/examples/ImageManip/image_manip_warp_mesh.py index 3eeaa43da..eadff4c77 100644 --- a/examples/ImageManip/image_manip_warp_mesh.py +++ b/examples/ImageManip/image_manip_warp_mesh.py @@ -12,7 +12,7 @@ maxFrameSize = camRgb.getPreviewWidth() * camRgb.getPreviewHeight() * 3 # Warp preview frame 1 -manip1 = pipeline.create(dai.node.Warp) +manip1 = pipeline.create(dai.node.ImageManip) # Create a custom warp mesh tl = dai.Point2f(20, 20) tr = dai.Point2f(460, 20) From 1c868cff81fad929357ac9ed3f69b792afb3e417 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 29 Dec 2022 16:16:03 +0100 Subject: [PATCH 080/385] Updated FW with Camera changes and warp capabilities. Modified camera_preview example --- depthai-core | 2 +- examples/Camera/camera_preview.py | 44 +++++++++++++++--- src/pipeline/node/CameraBindings.cpp | 67 +++------------------------- 3 files changed, 43 insertions(+), 70 deletions(-) diff --git a/depthai-core b/depthai-core index 89665e38d..baa24cead 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 89665e38d1192c90d25d03950828670bcc289d6d +Subproject commit baa24cead9524c10f7dada7cc28db60a5751737f diff --git a/examples/Camera/camera_preview.py b/examples/Camera/camera_preview.py index 7e91f6017..5516b19a8 100755 --- a/examples/Camera/camera_preview.py +++ b/examples/Camera/camera_preview.py @@ -1,9 +1,21 @@ #!/usr/bin/env python3 + import cv2 import depthai as dai +import time + +# Connect to device and start pipeline +with dai.Device(dai.OpenVINO.VERSION_2021_1, dai.UsbSpeed.SUPER_PLUS) as device: + # Device name + print('Device name:', device.getDeviceName()) + # Bootloader version + if device.getBootloaderVersion() is not None: + print('Bootloader version:', device.getBootloaderVersion()) + # Print out usb speed + print('Usb speed:', device.getUsbSpeed().name) + # Connected cameras + print('Connected cameras:', device.getConnectedCameraFeatures()) -# Connect to device -with dai.Device() as device: # Create pipeline pipeline = dai.Pipeline() cams = device.getConnectedCameraFeatures() @@ -19,14 +31,32 @@ stream = f'{cam.name} ({stream})' x.setStreamName(stream) streams.append(stream) + # Start pipeline device.startPipeline(pipeline) + fpsCounter = {} + lastFpsCount = {} + tfps = time.time() while not device.isClosed(): queueNames = device.getQueueEvents(streams) - for q in queueNames: - message = device.getOutputQueue(q).get() - # Display arrived frames - if type(message) == dai.ImgFrame: - cv2.imshow(q, message.getCvFrame()) + for stream in queueNames: + messages = device.getOutputQueue(stream).tryGetAll() + fpsCounter[stream] = fpsCounter.get(stream, 0.0) + len(messages) + for message in messages: + # Display arrived frames + if type(message) == dai.ImgFrame: + # render fps + fps = lastFpsCount.get(stream, 0) + frame = message.getCvFrame() + cv2.putText(frame, "Fps: {:.2f}".format(fps), (10, 10), cv2.FONT_HERSHEY_TRIPLEX, 0.4, (255,255,255)) + cv2.imshow(stream, frame) + + if time.time() - tfps >= 1.0: + scale = time.time() - tfps + for stream in fpsCounter.keys(): + lastFpsCount[stream] = fpsCounter[stream] / scale + fpsCounter = {} + tfps = time.time() + if cv2.waitKey(1) == ord('q'): break diff --git a/src/pipeline/node/CameraBindings.cpp b/src/pipeline/node/CameraBindings.cpp index 042e5a91f..0e5c935be 100644 --- a/src/pipeline/node/CameraBindings.cpp +++ b/src/pipeline/node/CameraBindings.cpp @@ -86,32 +86,10 @@ void bind_camera(pybind11::module& m, void* pCallstack){ .def_readonly("isp", &Camera::isp, DOC(dai, node, Camera, isp)) .def_readonly("raw", &Camera::raw, DOC(dai, node, Camera, raw)) .def_readonly("frameEvent", &Camera::frameEvent, DOC(dai, node, Camera, frameEvent)) - .def("setCamId", [](Camera& c, int64_t id) { - // Issue an deprecation warning - PyErr_WarnEx(PyExc_DeprecationWarning, "setCamId() is deprecated, use setBoardSocket() instead.", 1); - HEDLEY_DIAGNOSTIC_PUSH - HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED - c.setCamId(id); - HEDLEY_DIAGNOSTIC_POP - }) - .def("getCamId", [](Camera& c) { - // Issue an deprecation warning - PyErr_WarnEx(PyExc_DeprecationWarning, "getCamId() is deprecated, use getBoardSocket() instead.", 1); - HEDLEY_DIAGNOSTIC_PUSH - HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED - return c.getCamId(); - HEDLEY_DIAGNOSTIC_POP - }) .def("setBoardSocket", &Camera::setBoardSocket, py::arg("boardSocket"), DOC(dai, node, Camera, setBoardSocket)) .def("getBoardSocket", &Camera::getBoardSocket, DOC(dai, node, Camera, getBoardSocket)) .def("setImageOrientation", &Camera::setImageOrientation, py::arg("imageOrientation"), DOC(dai, node, Camera, setImageOrientation)) .def("getImageOrientation", &Camera::getImageOrientation, DOC(dai, node, Camera, getImageOrientation)) - .def("setColorOrder", &Camera::setColorOrder, py::arg("colorOrder"), DOC(dai, node, Camera, setColorOrder)) - .def("getColorOrder", &Camera::getColorOrder, DOC(dai, node, Camera, getColorOrder)) - .def("setInterleaved", &Camera::setInterleaved, py::arg("interleaved"), DOC(dai, node, Camera, setInterleaved)) - .def("getInterleaved", &Camera::getInterleaved, DOC(dai, node, Camera, getInterleaved)) - .def("setFp16", &Camera::setFp16, py::arg("fp16"), DOC(dai, node, Camera, setFp16)) - .def("getFp16", &Camera::getFp16, DOC(dai, node, Camera, getFp16)) .def("setPreviewSize", static_cast(&Camera::setPreviewSize), py::arg("width"), py::arg("height"), DOC(dai, node, Camera, setPreviewSize)) .def("setPreviewSize", static_cast)>(&Camera::setPreviewSize), py::arg("size"), DOC(dai, node, Camera, setPreviewSize, 2)) .def("setVideoSize", static_cast(&Camera::setVideoSize), py::arg("width"), py::arg("height"), DOC(dai, node, Camera, setVideoSize)) @@ -131,60 +109,25 @@ void bind_camera(pybind11::module& m, void* pCallstack){ .def("getStillSize", &Camera::getStillSize, DOC(dai, node, Camera, getStillSize)) .def("getStillWidth", &Camera::getStillWidth, DOC(dai, node, Camera, getStillWidth)) .def("getStillHeight", &Camera::getStillHeight, DOC(dai, node, Camera, getStillHeight)) - .def("getResolutionSize", &Camera::getResolutionSize, DOC(dai, node, Camera, getResolutionSize)) - .def("getResolutionWidth", &Camera::getResolutionWidth, DOC(dai, node, Camera, getResolutionWidth)) - .def("getResolutionHeight", &Camera::getResolutionHeight, DOC(dai, node, Camera, getResolutionHeight)) + .def("getSize", &Camera::getSize, DOC(dai, node, Camera, getSize)) + .def("getWidth", &Camera::getWidth, DOC(dai, node, Camera, getWidth)) + .def("getHeight", &Camera::getHeight, DOC(dai, node, Camera, getHeight)) .def("sensorCenterCrop", &Camera::sensorCenterCrop, DOC(dai, node, Camera, sensorCenterCrop)) .def("setSensorCrop", &Camera::setSensorCrop, py::arg("x"), py::arg("y"), DOC(dai, node, Camera, setSensorCrop)) .def("getSensorCrop", &Camera::getSensorCrop, DOC(dai, node, Camera, getSensorCrop)) .def("getSensorCropX", &Camera::getSensorCropX, DOC(dai, node, Camera, getSensorCropX)) .def("getSensorCropY", &Camera::getSensorCropY, DOC(dai, node, Camera, getSensorCropY)) - .def("setWaitForConfigInput", [](Camera& cam, bool wait){ - // Issue a deprecation warning - PyErr_WarnEx(PyExc_DeprecationWarning, "Use 'inputConfig.setWaitForMessage()' instead", 1); - HEDLEY_DIAGNOSTIC_PUSH - HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED - cam.setWaitForConfigInput(wait); - HEDLEY_DIAGNOSTIC_POP - }, py::arg("wait"), DOC(dai, node, Camera, setWaitForConfigInput)) - - .def("getWaitForConfigInput", [](Camera& cam){ - // Issue a deprecation warning - PyErr_WarnEx(PyExc_DeprecationWarning, "Use 'inputConfig.setWaitForMessage()' instead", 1); - HEDLEY_DIAGNOSTIC_PUSH - HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED - return cam.getWaitForConfigInput(); - HEDLEY_DIAGNOSTIC_POP - }, DOC(dai, node, Camera, getWaitForConfigInput)) - - .def("setPreviewKeepAspectRatio", &Camera::setPreviewKeepAspectRatio, py::arg("keep"), DOC(dai, node, Camera, setPreviewKeepAspectRatio)) - .def("getPreviewKeepAspectRatio", &Camera::getPreviewKeepAspectRatio, DOC(dai, node, Camera, getPreviewKeepAspectRatio)) .def("setIspScale", static_cast(&Camera::setIspScale), py::arg("numerator"), py::arg("denominator"), DOC(dai, node, Camera, setIspScale)) .def("setIspScale", static_cast)>(&Camera::setIspScale), py::arg("scale"), DOC(dai, node, Camera, setIspScale, 2)) .def("setIspScale", static_cast(&Camera::setIspScale), py::arg("horizNum"), py::arg("horizDenom"), py::arg("vertNum"), py::arg("vertDenom"), DOC(dai, node, Camera, setIspScale, 3)) .def("setIspScale", static_cast,std::tuple)>(&Camera::setIspScale), py::arg("horizScale"), py::arg("vertScale"), DOC(dai, node, Camera, setIspScale, 4)) - .def("getIspSize", &Camera::getIspSize, DOC(dai, node, Camera, getIspSize)) - .def("getIspWidth", &Camera::getIspWidth, DOC(dai, node, Camera, getIspWidth)) - .def("getIspHeight", &Camera::getIspHeight, DOC(dai, node, Camera, getIspHeight)) - - .def("setPreviewNumFramesPool", &Camera::setPreviewNumFramesPool, DOC(dai, node, Camera, setPreviewNumFramesPool)) - .def("setVideoNumFramesPool", &Camera::setVideoNumFramesPool, DOC(dai, node, Camera, setVideoNumFramesPool)) - .def("setStillNumFramesPool", &Camera::setStillNumFramesPool, DOC(dai, node, Camera, setStillNumFramesPool)) - .def("setRawNumFramesPool", &Camera::setRawNumFramesPool, DOC(dai, node, Camera, setRawNumFramesPool)) - .def("setIspNumFramesPool", &Camera::setIspNumFramesPool, DOC(dai, node, Camera, setIspNumFramesPool)) - .def("setNumFramesPool", &Camera::setNumFramesPool, py::arg("raw"), py::arg("isp"), py::arg("preview"), py::arg("video"), py::arg("still"), DOC(dai, node, Camera, setNumFramesPool)) - .def("getPreviewNumFramesPool", &Camera::getPreviewNumFramesPool, DOC(dai, node, Camera, getPreviewNumFramesPool)) - .def("getVideoNumFramesPool", &Camera::getVideoNumFramesPool, DOC(dai, node, Camera, getVideoNumFramesPool)) - .def("getStillNumFramesPool", &Camera::getStillNumFramesPool, DOC(dai, node, Camera, getStillNumFramesPool)) - .def("getRawNumFramesPool", &Camera::getRawNumFramesPool, DOC(dai, node, Camera, getRawNumFramesPool)) - .def("getIspNumFramesPool", &Camera::getIspNumFramesPool, DOC(dai, node, Camera, getIspNumFramesPool)) .def("setCamera", &Camera::setCamera, py::arg("name"), DOC(dai, node, Camera, setCamera)) .def("getCamera", &Camera::getCamera, DOC(dai, node, Camera, getCamera)) - .def("setSensorSize", static_cast(&Camera::setSensorSize), py::arg("width"), py::arg("height"), DOC(dai, node, Camera, setSensorSize)) - .def("setSensorSize", static_cast)>(&Camera::setSensorSize), py::arg("size"), DOC(dai, node, Camera, setSensorSize, 2)) + .def("setSize", static_cast(&Camera::setSize), py::arg("width"), py::arg("height"), DOC(dai, node, Camera, setSize)) + .def("setSize", static_cast)>(&Camera::setSize), py::arg("size"), DOC(dai, node, Camera, setSize, 2)) ; // ALIAS From 33744cbad5658fecbba9764078489b7ab15644fc Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Fri, 30 Dec 2022 18:15:33 +0100 Subject: [PATCH 081/385] Updated FW with Camera warp capabilities --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index baa24cead..cba0cdae8 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit baa24cead9524c10f7dada7cc28db60a5751737f +Subproject commit cba0cdae8ca53d1da33d83aa97196c59cc5b7021 From 32533fda9f107c9088fe348f4e7549fe20a06109 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Mon, 2 Jan 2023 20:18:38 +0100 Subject: [PATCH 082/385] Added span bindings --- src/utility/SpanBindings.hpp | 170 +++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/utility/SpanBindings.hpp diff --git a/src/utility/SpanBindings.hpp b/src/utility/SpanBindings.hpp new file mode 100644 index 000000000..859f1df31 --- /dev/null +++ b/src/utility/SpanBindings.hpp @@ -0,0 +1,170 @@ +#pragma once + +#include +#include + +#include "depthai/utility/span.hpp" + +namespace pybind11 { +namespace detail { + +template +struct span_name_maker { + template + static constexpr auto make(const T &t) { + return concat(t, span_name_maker::make(t)); + } +}; + +template <> +struct span_name_maker<1> { + template + static constexpr auto make(const T &t) { + return t; + } +}; + +// span with fixed size converts to a tuple +template struct type_caster> { + using span_type = typename dai::span; + using value_conv = make_caster; + using value_type = typename std::remove_cv::type; + + value_type backing_array[Extent] = {}; + + PYBIND11_TYPE_CASTER(span_type, _("Tuple[") + span_name_maker::make(value_conv::name) + _("]")); + + type_caster() : value(backing_array) {} + + bool load(handle src, bool convert) { + if (!isinstance(src) || isinstance(src)) + return false; + auto s = reinterpret_borrow(src); + if (s.size() != Extent) + return false; + size_t i = 0; + for (auto it : s) { + value_conv conv; + if (!conv.load(it, convert)) + return false; + backing_array[i] = cast_op(std::move(conv)); + i++; + } + return true; + } + +public: + template + static handle cast(T &&src, return_value_policy policy, handle parent) { + if (!std::is_lvalue_reference::value) + policy = return_value_policy_override::policy(policy); + tuple l(Extent); + size_t index = 0; + for (auto &&value : src) { + auto value_ = reinterpret_steal( + value_conv::cast(forward_like(value), policy, parent)); + if (!value_) + return handle(); + PyTuple_SET_ITEM(l.ptr(), (ssize_t)index++, + value_.release().ptr()); // steals a reference + } + return l.release(); + } +}; + + +// span with dynamic extent +template struct type_caster> { + using span_type = typename dai::span; + using value_conv = make_caster; + using value_type = typename std::remove_cv::type; + PYBIND11_TYPE_CASTER(span_type, _("List[") + value_conv::name + _("]")); + + std::vector vec; + bool load(handle src, bool convert) { + if (!isinstance(src) || isinstance(src)) + return false; + auto s = reinterpret_borrow(src); + vec.reserve(s.size()); + for (auto it : s) { + value_conv conv; + if (!conv.load(it, convert)) + return false; + vec.push_back(cast_op(std::move(conv))); + } + value = span_type(vec.data(), vec.size()); + return true; + } + +public: + template + static handle cast(T &&src, return_value_policy policy, handle parent) { + if (!std::is_lvalue_reference::value) + policy = return_value_policy_override::policy(policy); + list l(src.size()); + size_t index = 0; + for (auto &&value : src) { + auto value_ = reinterpret_steal( + value_conv::cast(forward_like(value), policy, parent)); + if (!value_) + return handle(); + PyList_SET_ITEM(l.ptr(), (ssize_t)index++, + value_.release().ptr()); // steals a reference + } + return l.release(); + } +}; + +// span specialization: accepts any readonly buffers +template <> struct type_caster> { + using span_type = typename dai::span; + PYBIND11_TYPE_CASTER(span_type, _("buffer")); + + bool load(handle src, bool convert) { + if (!isinstance(src)) + return false; + auto buf = reinterpret_borrow(src); + auto req = buf.request(); + if (req.ndim != 1) { + return false; + } + + value = span_type((const uint8_t*)req.ptr, req.size*req.itemsize); + return true; + } + +public: + template + static handle cast(T &&src, return_value_policy policy, handle parent) { + return bytes((char*)src.data(), src.size()).release(); + } +}; + +// span specialization: writeable buffer +template <> struct type_caster> { + using span_type = typename dai::span; + PYBIND11_TYPE_CASTER(dai::span, _("buffer")); + + bool load(handle src, bool convert) { + if (!isinstance(src)) + return false; + auto buf = reinterpret_borrow(src); + auto req = buf.request(true); // buffer must be writeable + if (req.ndim != 1) { + return false; + } + + value = dai::span((uint8_t*)req.ptr, req.size*req.itemsize); + return true; + } + +public: + template + static handle cast(T &&src, return_value_policy policy, handle parent) { + // TODO: should this be a memoryview instead? + return bytes((char*)src.data(), src.size()).release(); + } +}; + +} // namespace detail +} // namespace pybind11 \ No newline at end of file From 0516f9273392d6ac829a53fb6441b9480f54a3da Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Mon, 2 Jan 2023 20:18:51 +0100 Subject: [PATCH 083/385] Added 'Camera' related bindings --- depthai-core | 2 +- src/pipeline/node/CameraBindings.cpp | 29 +++++++++++++++++++++++++++- src/pybind11_common.hpp | 1 + 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/depthai-core b/depthai-core index cba0cdae8..4dedea1e2 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit cba0cdae8ca53d1da33d83aa97196c59cc5b7021 +Subproject commit 4dedea1e295008ecb6bc6c1fb4bde02ea62f6d8f diff --git a/src/pipeline/node/CameraBindings.cpp b/src/pipeline/node/CameraBindings.cpp index 0e5c935be..194a123cb 100644 --- a/src/pipeline/node/CameraBindings.cpp +++ b/src/pipeline/node/CameraBindings.cpp @@ -12,7 +12,7 @@ void bind_camera(pybind11::module& m, void* pCallstack){ // Node and Properties declare upfront py::class_ cameraProperties(m, "CameraProperties", DOC(dai, CameraProperties)); - // py::enum_ cameraPropertiesSensorResolution(cameraProperties, "SensorResolution", DOC(dai, CameraProperties, SensorResolution)); + py::enum_ cameraPropertiesWarpMeshSource(cameraProperties, "WarpMeshSource", DOC(dai, CameraProperties, WarpMeshSource)); py::enum_ cameraPropertiesColorOrder(cameraProperties, "ColorOrder", DOC(dai, CameraProperties, ColorOrder)); auto camera = ADD_NODE(Camera); @@ -44,6 +44,14 @@ void bind_camera(pybind11::module& m, void* pCallstack){ // .value("THE_800_P", CameraProperties::SensorResolution::THE_800_P) // ; + // Camera Properties - WarpMeshSource + cameraPropertiesWarpMeshSource + .value("AUTO", CameraProperties::WarpMeshSource::AUTO) + .value("NONE", CameraProperties::WarpMeshSource::NONE) + .value("CALIBRATION", CameraProperties::WarpMeshSource::CALIBRATION) + .value("URI", CameraProperties::WarpMeshSource::URI) + ; + cameraPropertiesColorOrder .value("BGR", CameraProperties::ColorOrder::BGR) .value("RGB", CameraProperties::ColorOrder::RGB) @@ -68,11 +76,20 @@ void bind_camera(pybind11::module& m, void* pCallstack){ .def_readwrite("sensorCropY", &CameraProperties::sensorCropY) .def_readwrite("previewKeepAspectRatio", &CameraProperties::previewKeepAspectRatio) .def_readwrite("ispScale", &CameraProperties::ispScale) + .def_readwrite("numFramesPoolRaw", &CameraProperties::numFramesPoolRaw) .def_readwrite("numFramesPoolIsp", &CameraProperties::numFramesPoolIsp) .def_readwrite("numFramesPoolVideo", &CameraProperties::numFramesPoolVideo) .def_readwrite("numFramesPoolPreview", &CameraProperties::numFramesPoolPreview) .def_readwrite("numFramesPoolStill", &CameraProperties::numFramesPoolStill) + + .def_readwrite("warpMeshSource", &CameraProperties::warpMeshSource) + .def_readwrite("warpMeshUri", &CameraProperties::warpMeshUri) + .def_readwrite("warpMeshWidth", &CameraProperties::warpMeshWidth) + .def_readwrite("warpMeshHeight", &CameraProperties::warpMeshHeight) + .def_readwrite("calibAlpha", &CameraProperties::calibAlpha) + .def_readwrite("warpMeshStepWidth", &CameraProperties::warpMeshStepWidth) + .def_readwrite("warpMeshStepHeight", &CameraProperties::warpMeshStepHeight) ; // Camera node @@ -129,6 +146,16 @@ void bind_camera(pybind11::module& m, void* pCallstack){ .def("setSize", static_cast(&Camera::setSize), py::arg("width"), py::arg("height"), DOC(dai, node, Camera, setSize)) .def("setSize", static_cast)>(&Camera::setSize), py::arg("size"), DOC(dai, node, Camera, setSize, 2)) + + .def("setMeshSource", &Camera::setMeshSource, py::arg("source"), DOC(dai, node, Camera, setMeshSource)) + .def("getMeshSource", &Camera::getMeshSource, DOC(dai, node, Camera, getMeshSource)) + .def("loadMeshFile", &Camera::loadMeshFile, py::arg("warpMesh"), DOC(dai, node, Camera, loadMeshFile)) + .def("loadMeshData", &Camera::loadMeshData, py::arg("warpMesh"), DOC(dai, node, Camera, loadMeshData)) + .def("setMeshStep", &Camera::setMeshStep, py::arg("width"), py::arg("height"), DOC(dai, node, Camera, setMeshStep)) + .def("getMeshStep", &Camera::getMeshStep, DOC(dai, node, Camera, getMeshStep)) + .def("setCalibrationAlpha", &Camera::setCalibrationAlpha, py::arg("alpha"), DOC(dai, node, Camera, setCalibrationAlpha)) + .def("getCalibrationAlpha", &Camera::getCalibrationAlpha, DOC(dai, node, Camera, getCalibrationAlpha)) + ; // ALIAS daiNodeModule.attr("Camera").attr("Properties") = cameraProperties; diff --git a/src/pybind11_common.hpp b/src/pybind11_common.hpp index 6dbc0967b..6478ae5e2 100644 --- a/src/pybind11_common.hpp +++ b/src/pybind11_common.hpp @@ -13,6 +13,7 @@ #include #include #include +#include // Include docstring file #include "docstring.hpp" From b7556a9141e4339034a3adcb0f6372c8dca7de45 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 3 Jan 2023 13:42:30 +0200 Subject: [PATCH 084/385] Update core --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index cea13d673..84e163df1 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit cea13d673d15712d8c058ae2d69095f1c5d66a1a +Subproject commit 84e163df17121cf2879ac9fe39ff877d2fd4054d From 9ca3b408ea1b51be27d246fb250349c574200134 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 5 Jan 2023 14:38:57 +0100 Subject: [PATCH 085/385] FW - Modifed watchdog to do a graceful reset instead --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 84e163df1..b2da50897 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 84e163df17121cf2879ac9fe39ff877d2fd4054d +Subproject commit b2da50897224ca1bcd5d6ad8ae0d04b798904fad From 0a45ec92aa927eaaf238f2b022db1669713f44d5 Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Sun, 8 Jan 2023 10:37:57 +0100 Subject: [PATCH 086/385] Added additional API to retrieve timestamps at various exposure points --- depthai-core | 2 +- src/pipeline/datatype/ImgFrameBindings.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 84e163df1..06b7befea 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 84e163df17121cf2879ac9fe39ff877d2fd4054d +Subproject commit 06b7befeaa4d5424b5511100e3042db3723f5a4b diff --git a/src/pipeline/datatype/ImgFrameBindings.cpp b/src/pipeline/datatype/ImgFrameBindings.cpp index ee867395b..bf5ccbea3 100644 --- a/src/pipeline/datatype/ImgFrameBindings.cpp +++ b/src/pipeline/datatype/ImgFrameBindings.cpp @@ -119,6 +119,10 @@ void bind_imgframe(pybind11::module& m, void* pCallstack){ // getters .def("getTimestamp", &ImgFrame::getTimestamp, DOC(dai, ImgFrame, getTimestamp)) .def("getTimestampDevice", &ImgFrame::getTimestampDevice, DOC(dai, ImgFrame, getTimestampDevice)) + .def("getTimestampExposureMiddle", &ImgFrame::getTimestampExposureMiddle, DOC(dai, ImgFrame, getTimestampExposureMiddle)) + .def("getTimestampDeviceExposureMiddle", &ImgFrame::getTimestampDeviceExposureMiddle, DOC(dai, ImgFrame, getTimestampDeviceExposureMiddle)) + .def("getTimestampExposureStart", &ImgFrame::getTimestampExposureStart, DOC(dai, ImgFrame, getTimestampExposureStart)) + .def("getTimestampDeviceExposureStart", &ImgFrame::getTimestampDeviceExposureStart, DOC(dai, ImgFrame, getTimestampDeviceExposureStart)) .def("getInstanceNum", &ImgFrame::getInstanceNum, DOC(dai, ImgFrame, getInstanceNum)) .def("getCategory", &ImgFrame::getCategory, DOC(dai, ImgFrame, getCategory)) .def("getSequenceNum", &ImgFrame::getSequenceNum, DOC(dai, ImgFrame, getSequenceNum)) From 89574aa79c97367c5cf35d00e1394af9125f36ae Mon Sep 17 00:00:00 2001 From: Petr Novota <99871801+PetrNovota@users.noreply.github.com> Date: Sun, 8 Jan 2023 11:42:22 +0100 Subject: [PATCH 087/385] brew upgrade changed to brew update --- docs/source/_static/install_depthai.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh index f5498d708..a1fae2203 100755 --- a/docs/source/_static/install_depthai.sh +++ b/docs/source/_static/install_depthai.sh @@ -124,7 +124,7 @@ if [[ $(uname -s) == "Darwin" ]]; then bash -c "$(curl -fL https://docs.luxonis.com/install_dependencies.sh)" echo "Upgrading brew." - brew upgrade + brew update # clone depthai form git if [ -d "$DEPTHAI_DIR" ]; then From af42e293e9cf524d91764f40290115efe840602a Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 10 Jan 2023 21:18:53 +0100 Subject: [PATCH 088/385] Fixed documentation on ObjectTracker::setMaxObjectsToTrack --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 690b6a637..2ee9ebf6b 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 690b6a637942cd56959c549c8e4be07aee676385 +Subproject commit 2ee9ebf6b1638250c77b0354a12cd1edbafc98dc From a7d775377cd818f43ad5151dbc6ac5de57fbccd4 Mon Sep 17 00:00:00 2001 From: "Raymond Tunstill (Kirk)" Date: Wed, 11 Jan 2023 11:27:49 +0000 Subject: [PATCH 089/385] Fix 'bash: line 231: cho: command not found' On bash based shells the install_depthai.sh script fails to execute due to superfluous dollar signs in the echo statements. --- docs/source/_static/install_depthai.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh index a1fae2203..2fe62a090 100755 --- a/docs/source/_static/install_depthai.sh +++ b/docs/source/_static/install_depthai.sh @@ -227,8 +227,8 @@ else exit 99 fi -echo $'\n\n:::::::::::::::: INSTALATION COMPLETE ::::::::::::::::\n' -echo $'\nTo run demo app write in terminal.' +echo '\n\n:::::::::::::::: INSTALATION COMPLETE ::::::::::::::::\n' +echo '\nTo run demo app write in terminal.' read -rsp $'Press ANY KEY to finish and run the demo app...\n' -n1 key echo "STARTING DEMO APP." python "$DEPTHAI_DIR/launcher/launcher.py" -r "$DEPTHAI_DIR" From 1d25275f743b942c2b3707d1576f9ef45c38253b Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Thu, 12 Jan 2023 02:48:47 +0100 Subject: [PATCH 090/385] WIP: mockIsp capabilities --- depthai-core | 2 +- examples/Camera/camera_preview.py | 2 +- .../disparity_colormap_encoding.py | 62 +++++++++++++++++++ src/pipeline/node/CameraBindings.cpp | 1 + 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100755 examples/VideoEncoder/disparity_colormap_encoding.py diff --git a/depthai-core b/depthai-core index 4dedea1e2..fec218ecf 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 4dedea1e295008ecb6bc6c1fb4bde02ea62f6d8f +Subproject commit fec218ecf14930b271a7a29aa60af08147d0fcf1 diff --git a/examples/Camera/camera_preview.py b/examples/Camera/camera_preview.py index 5516b19a8..ba9aa108a 100755 --- a/examples/Camera/camera_preview.py +++ b/examples/Camera/camera_preview.py @@ -5,7 +5,7 @@ import time # Connect to device and start pipeline -with dai.Device(dai.OpenVINO.VERSION_2021_1, dai.UsbSpeed.SUPER_PLUS) as device: +with dai.Device(dai.OpenVINO.DEFAULT_VERSION, dai.UsbSpeed.SUPER_PLUS) as device: # Device name print('Device name:', device.getDeviceName()) # Bootloader version diff --git a/examples/VideoEncoder/disparity_colormap_encoding.py b/examples/VideoEncoder/disparity_colormap_encoding.py new file mode 100755 index 000000000..5f69dba5c --- /dev/null +++ b/examples/VideoEncoder/disparity_colormap_encoding.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +import depthai as dai + +# Create pipeline +pipeline = dai.Pipeline() + +# Create left/right mono cameras for Stereo depth +monoLeft = pipeline.create(dai.node.MonoCamera) +monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) +monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) + +monoRight = pipeline.create(dai.node.MonoCamera) +monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) +monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) + +# Create a node that will produce the depth map +depth = pipeline.create(dai.node.StereoDepth) +depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) +depth.initialConfig.setMedianFilter(dai.MedianFilter.KERNEL_7x7) +depth.setLeftRightCheck(False) +depth.setExtendedDisparity(False) +# Subpixel disparity is of UINT16 format, which is unsupported by VideoEncoder +depth.setSubpixel(False) +monoLeft.out.link(depth.left) +monoRight.out.link(depth.right) + +# Colormap +colormap = pipeline.create(dai.node.ImageManip) +colormap.initialConfig.setColormap(dai.Colormap.TURBO, 0, 95) +colormap.initialConfig.setFrameType(dai.ImgFrame.Type.NV12) + +videoEnc = pipeline.create(dai.node.VideoEncoder) +# Depth resolution/FPS will be the same as mono resolution/FPS +videoEnc.setDefaultProfilePreset(monoLeft.getFps(), dai.VideoEncoderProperties.Profile.H264_HIGH) + +# Link +depth.disparity.link(colormap.inputImage) +colormap.out.link(videoEnc.input) + +xout = pipeline.create(dai.node.XLinkOut) +xout.setStreamName("enc") +videoEnc.bitstream.link(xout.input) + +# Connect to device and start pipeline +with dai.Device(pipeline) as device: + + # Output queue will be used to get the encoded data from the output defined above + q = device.getOutputQueue(name="enc") + + # The .h265 file is a raw stream file (not playable yet) + with open('disparity.h264', 'wb') as videoFile: + print("Press Ctrl+C to stop encoding...") + try: + while True: + videoFile.write(q.get().getData()) + except KeyboardInterrupt: + # Keyboard interrupt (Ctrl + C) detected + pass + + print("To view the encoded data, convert the stream file (.mjpeg) into a video file (.mp4) using a command below:") + print("ffmpeg -framerate 30 -i disparity.mjpeg -c copy video.mp4") diff --git a/src/pipeline/node/CameraBindings.cpp b/src/pipeline/node/CameraBindings.cpp index 194a123cb..60a7640b8 100644 --- a/src/pipeline/node/CameraBindings.cpp +++ b/src/pipeline/node/CameraBindings.cpp @@ -103,6 +103,7 @@ void bind_camera(pybind11::module& m, void* pCallstack){ .def_readonly("isp", &Camera::isp, DOC(dai, node, Camera, isp)) .def_readonly("raw", &Camera::raw, DOC(dai, node, Camera, raw)) .def_readonly("frameEvent", &Camera::frameEvent, DOC(dai, node, Camera, frameEvent)) + .def_readonly("mockIsp", &Camera::mockIsp, DOC(dai, node, Camera, mockIsp)) .def("setBoardSocket", &Camera::setBoardSocket, py::arg("boardSocket"), DOC(dai, node, Camera, setBoardSocket)) .def("getBoardSocket", &Camera::getBoardSocket, DOC(dai, node, Camera, getBoardSocket)) .def("setImageOrientation", &Camera::setImageOrientation, py::arg("imageOrientation"), DOC(dai, node, Camera, setImageOrientation)) From 87ff67c838f9c981d7ceb117a0af938eb64d4751 Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Thu, 12 Jan 2023 03:21:40 +0100 Subject: [PATCH 091/385] [FW] Fix for CAM_C not being detected --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index d60786147..efb3f4ddc 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit d607861477507c28bcdee21198372a4cdc9a374d +Subproject commit efb3f4ddc8bc378974853e1dd341a8b3251553a4 From ef31c11057532e9f3289acd7f1a4b4d48c81657a Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Fri, 13 Jan 2023 21:41:46 +0100 Subject: [PATCH 092/385] Device - Added non exclusive boot option --- depthai-core | 2 +- examples/device/device_all_boot_bootloader.py | 6 ++++++ examples/device/device_boot_non_exclusive.py | 10 ++++++++++ src/DeviceBindings.cpp | 1 + 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 examples/device/device_all_boot_bootloader.py create mode 100644 examples/device/device_boot_non_exclusive.py diff --git a/depthai-core b/depthai-core index efb3f4ddc..85e90ccce 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit efb3f4ddc8bc378974853e1dd341a8b3251553a4 +Subproject commit 85e90ccce390dae4d65e21fde11856ede7e71641 diff --git a/examples/device/device_all_boot_bootloader.py b/examples/device/device_all_boot_bootloader.py new file mode 100644 index 000000000..4c2e9a56b --- /dev/null +++ b/examples/device/device_all_boot_bootloader.py @@ -0,0 +1,6 @@ +import depthai as dai + +devices = dai.Device.getAllConnectedDevices() + +for device in devices: + dai.XLinkConnection.bootBootloader(device) diff --git a/examples/device/device_boot_non_exclusive.py b/examples/device/device_boot_non_exclusive.py new file mode 100644 index 000000000..898b292f6 --- /dev/null +++ b/examples/device/device_boot_non_exclusive.py @@ -0,0 +1,10 @@ +import depthai as dai +import time + +cfg = dai.Device.Config() +cfg.nonExclusiveMode = True + +with dai.Device(cfg) as device: + while not device.isClosed(): + print('CPU usage:',device.getLeonCssCpuUsage().average) + time.sleep(1) \ No newline at end of file diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 43efada81..85179e279 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -459,6 +459,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def(py::init<>()) .def_readwrite("version", &Device::Config::version) .def_readwrite("board", &Device::Config::board) + .def_readwrite("nonExclusiveMode", &Device::Config::nonExclusiveMode) ; // Bind constructors From 164e6cc414bf2c453f752de2654b8112abb66918 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Sun, 15 Jan 2023 20:06:33 +0100 Subject: [PATCH 093/385] Slight Colormap API improvements --- depthai-core | 2 +- examples/StereoDepth/depth_colormap.py | 2 +- .../disparity_colormap_encoding.py | 2 +- src/pipeline/CommonBindings.cpp | 42 ++++++++++--------- .../datatype/ImageManipConfigBindings.cpp | 4 +- 5 files changed, 28 insertions(+), 24 deletions(-) diff --git a/depthai-core b/depthai-core index fec218ecf..66c8065cd 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit fec218ecf14930b271a7a29aa60af08147d0fcf1 +Subproject commit 66c8065cd84607a1c9dff706dab6710458e81703 diff --git a/examples/StereoDepth/depth_colormap.py b/examples/StereoDepth/depth_colormap.py index b334ffb34..05d86f85c 100755 --- a/examples/StereoDepth/depth_colormap.py +++ b/examples/StereoDepth/depth_colormap.py @@ -38,7 +38,7 @@ # Create a colormap colormap = pipeline.create(dai.node.ImageManip) -colormap.initialConfig.setColormap(dai.Colormap.TURBO, 0, 95) +colormap.initialConfig.setColormap(dai.Colormap.STEREO_TURBO, depth.initialConfig.getMaxDisparity()) colormap.initialConfig.setFrameType(dai.ImgFrame.Type.NV12) # Linking diff --git a/examples/VideoEncoder/disparity_colormap_encoding.py b/examples/VideoEncoder/disparity_colormap_encoding.py index 5f69dba5c..09d602b10 100755 --- a/examples/VideoEncoder/disparity_colormap_encoding.py +++ b/examples/VideoEncoder/disparity_colormap_encoding.py @@ -27,7 +27,7 @@ # Colormap colormap = pipeline.create(dai.node.ImageManip) -colormap.initialConfig.setColormap(dai.Colormap.TURBO, 0, 95) +colormap.initialConfig.setColormap(dai.Colormap.TURBO, depth.initialConfig.getMaxDisparity()) colormap.initialConfig.setFrameType(dai.ImgFrame.Type.NV12) videoEnc = pipeline.create(dai.node.VideoEncoder) diff --git a/src/pipeline/CommonBindings.cpp b/src/pipeline/CommonBindings.cpp index 9c1602f74..4c3c47512 100644 --- a/src/pipeline/CommonBindings.cpp +++ b/src/pipeline/CommonBindings.cpp @@ -294,28 +294,30 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ colormap .value("NONE", Colormap::NONE) - .value("AUTUMN", Colormap::AUTUMN) - .value("BONE", Colormap::BONE) .value("JET", Colormap::JET) - .value("WINTER", Colormap::WINTER) - .value("RAINBOW", Colormap::RAINBOW) - .value("OCEAN", Colormap::OCEAN) - .value("SUMMER", Colormap::SUMMER) - .value("SPRING", Colormap::SPRING) - .value("COOL", Colormap::COOL) - .value("HSV", Colormap::HSV) - .value("PINK", Colormap::PINK) - .value("HOT", Colormap::HOT) - .value("PARULA", Colormap::PARULA) - .value("MAGMA", Colormap::MAGMA) - .value("INFERNO", Colormap::INFERNO) - .value("PLASMA", Colormap::PLASMA) - .value("VIRIDIS", Colormap::VIRIDIS) - .value("CIVIDIS", Colormap::CIVIDIS) - .value("TWILIGHT", Colormap::TWILIGHT) - .value("TWILIGHT_SHIFTED", Colormap::TWILIGHT_SHIFTED) .value("TURBO", Colormap::TURBO) - .value("DEEPGREEN", Colormap::DEEPGREEN) + .value("STEREO_JET", Colormap::STEREO_JET) + .value("STEREO_TURBO", Colormap::STEREO_TURBO) + // .value("AUTUMN", Colormap::AUTUMN) + // .value("BONE", Colormap::BONE) + // .value("WINTER", Colormap::WINTER) + // .value("RAINBOW", Colormap::RAINBOW) + // .value("OCEAN", Colormap::OCEAN) + // .value("SUMMER", Colormap::SUMMER) + // .value("SPRING", Colormap::SPRING) + // .value("COOL", Colormap::COOL) + // .value("HSV", Colormap::HSV) + // .value("PINK", Colormap::PINK) + // .value("HOT", Colormap::HOT) + // .value("PARULA", Colormap::PARULA) + // .value("MAGMA", Colormap::MAGMA) + // .value("INFERNO", Colormap::INFERNO) + // .value("PLASMA", Colormap::PLASMA) + // .value("VIRIDIS", Colormap::VIRIDIS) + // .value("CIVIDIS", Colormap::CIVIDIS) + // .value("TWILIGHT", Colormap::TWILIGHT) + // .value("TWILIGHT_SHIFTED", Colormap::TWILIGHT_SHIFTED) + // .value("DEEPGREEN", Colormap::DEEPGREEN) ; } diff --git a/src/pipeline/datatype/ImageManipConfigBindings.cpp b/src/pipeline/datatype/ImageManipConfigBindings.cpp index 05b3f38c2..847d69d5a 100644 --- a/src/pipeline/datatype/ImageManipConfigBindings.cpp +++ b/src/pipeline/datatype/ImageManipConfigBindings.cpp @@ -112,7 +112,9 @@ void bind_imagemanipconfig(pybind11::module& m, void* pCallstack){ .def("setResizeThumbnail", static_cast(&ImageManipConfig::setResizeThumbnail), py::arg("w"), py::arg("h"), py::arg("bgRed")=0, py::arg("bgGreen")=0, py::arg("bgBlue")=0, DOC(dai, ImageManipConfig, setResizeThumbnail)) .def("setResizeThumbnail", static_cast, int, int, int)>(&ImageManipConfig::setResizeThumbnail), py::arg("size"), py::arg("bgRed")=0, py::arg("bgGreen")=0, py::arg("bgBlue")=0, DOC(dai, ImageManipConfig, setResizeThumbnail, 2)) .def("setFrameType", &ImageManipConfig::setFrameType, py::arg("type"), DOC(dai, ImageManipConfig, setFrameType)) - .def("setColormap", &ImageManipConfig::setColormap, py::arg("colormap"), py::arg("min") = 0, py::arg("max") = 255, DOC(dai, ImageManipConfig, setColormap)) + .def("setColormap", static_cast(&ImageManipConfig::setColormap), py::arg("colormap"), py::arg("min"), py::arg("max"), DOC(dai, ImageManipConfig, setColormap)) + .def("setColormap", static_cast(&ImageManipConfig::setColormap), py::arg("colormap"), py::arg("max") = 255, DOC(dai, ImageManipConfig, setColormap)) + .def("setColormap", static_cast(&ImageManipConfig::setColormap), py::arg("colormap"), py::arg("max") = 255.0f, DOC(dai, ImageManipConfig, setColormap)) .def("setHorizontalFlip", &ImageManipConfig::setHorizontalFlip, py::arg("flip"), DOC(dai, ImageManipConfig, setHorizontalFlip)) .def("setVerticalFlip", &ImageManipConfig::setVerticalFlip, py::arg("flip"), DOC(dai, ImageManipConfig, setVerticalFlip)) .def("setReusePreviousImage", &ImageManipConfig::setReusePreviousImage, py::arg("reuse"), DOC(dai, ImageManipConfig, setReusePreviousImage)) From 9c663fbcb9fde1860b1d31ba707e08f2459a77ff Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Mon, 16 Jan 2023 23:22:23 +0100 Subject: [PATCH 094/385] Updated install_dependencies.sh for Ubuntu 22.10 --- .github/workflows/test-install-dependencies.yml | 2 +- docs/source/_static/install_dependencies.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-install-dependencies.yml b/.github/workflows/test-install-dependencies.yml index fa09cabaa..851af5558 100644 --- a/.github/workflows/test-install-dependencies.yml +++ b/.github/workflows/test-install-dependencies.yml @@ -16,7 +16,7 @@ runs-on: ubuntu-latest strategy: matrix: - container_image: ["fedora:34", "fedora:35", "fedora:36", "ubuntu:18.04", "ubuntu:20.04", "ubuntu:22.04"] + container_image: ["fedora:34", "fedora:35", "fedora:36", "ubuntu:18.04", "ubuntu:20.04", "ubuntu:22.04", "ubuntu:22.10"] container: image: ${{ matrix.container_image }} steps: diff --git a/docs/source/_static/install_dependencies.sh b/docs/source/_static/install_dependencies.sh index e5c860db7..9b25c7a08 100755 --- a/docs/source/_static/install_dependencies.sh +++ b/docs/source/_static/install_dependencies.sh @@ -51,7 +51,7 @@ readonly ubuntu_pkgs_pre22_04=( libdc1394-22-dev ) -readonly ubuntu_pkgs_22_04=( +readonly ubuntu_pkgs_post22_04=( "${ubuntu_pkgs[@]}" libdc1394-dev ) @@ -112,8 +112,8 @@ elif [ -f /etc/os-release ]; then if [[ "$ID" == "ubuntu" || "$ID" == "debian" || "$ID_LIKE" == "ubuntu" || "$ID_LIKE" == "debian" || "$ID_LIKE" == "ubuntu debian" ]]; then if [[ ! $(uname -m) =~ ^arm* ]]; then sudo apt-get update - if [[ "$VERSION_ID" == "22.04" ]]; then - sudo apt-get install -y "${ubuntu_pkgs_22_04[@]}" + if [[ "$VERSION_ID" > "22.04" || "$VERSION_ID" == "22.04" ]]; then + sudo apt-get install -y "${ubuntu_pkgs_post22_04[@]}" else sudo apt-get install -y "${ubuntu_pkgs_pre22_04[@]}" fi From 98801478d90acdc38a30884415a5f10a23d1b62f Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 17 Jan 2023 14:21:48 +0100 Subject: [PATCH 095/385] Added DeviceBase convinience constructors taking name or deviceid as string --- depthai-core | 2 +- src/DeviceBindings.cpp | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/depthai-core b/depthai-core index d60786147..dfecb75d4 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit d607861477507c28bcdee21198372a4cdc9a374d +Subproject commit dfecb75d4fcc5f84a9fa7ee832582dbc1c5c36af diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 43efada81..5bfb8126d 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -289,14 +289,24 @@ static void bindConstructors(ARG& arg){ }), py::arg("config"), py::arg("deviceInfo"), DOC(dai, DeviceBase, DeviceBase, 19)) // DeviceInfo version - .def(py::init([](const DeviceInfo& deviceInfo){ + .def(py::init([](const DeviceInfo& deviceInfo){ py::gil_scoped_release release; return std::make_unique(deviceInfo); }), py::arg("deviceInfo"), DOC(dai, DeviceBase, DeviceBase, 20)) - .def(py::init([](const DeviceInfo& deviceInfo, UsbSpeed maxUsbSpeed){ + .def(py::init([](const DeviceInfo& deviceInfo, UsbSpeed maxUsbSpeed){ py::gil_scoped_release release; return std::make_unique(deviceInfo, maxUsbSpeed); }), py::arg("deviceInfo"), py::arg("maxUsbSpeed"), DOC(dai, DeviceBase, DeviceBase, 21)) + + // name or device id version + .def(py::init([](std::string nameOrDeviceId){ + py::gil_scoped_release release; + return std::make_unique(std::move(nameOrDeviceId)); + }), py::arg("nameOrDeviceId"), DOC(dai, DeviceBase, DeviceBase, 22)) + .def(py::init([](std::string nameOrDeviceId, UsbSpeed maxUsbSpeed){ + py::gil_scoped_release release; + return std::make_unique(std::move(nameOrDeviceId), maxUsbSpeed); + }), py::arg("nameOrDeviceId"), py::arg("maxUsbSpeed"), DOC(dai, DeviceBase, DeviceBase, 23)) ; } From f130fc2fdf3e12789f0ae3afbf1bf4f15729875e Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 17 Jan 2023 23:17:14 +0200 Subject: [PATCH 096/385] Add mode and median support to SLC --- depthai-core | 2 +- .../spatial_location_calculator.py | 21 +++++++++++++++++++ ...patialLocationCalculatorConfigBindings.cpp | 3 +++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 85e90ccce..3dfe7ced7 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 85e90ccce390dae4d65e21fde11856ede7e71641 +Subproject commit 3dfe7ced703678da08dd630f9089fd87f8306cd3 diff --git a/examples/SpatialDetection/spatial_location_calculator.py b/examples/SpatialDetection/spatial_location_calculator.py index fe7a13aa1..4a7cb9af1 100755 --- a/examples/SpatialDetection/spatial_location_calculator.py +++ b/examples/SpatialDetection/spatial_location_calculator.py @@ -44,6 +44,7 @@ config = dai.SpatialLocationCalculatorConfigData() config.depthThresholds.lowerThreshold = 100 config.depthThresholds.upperThreshold = 10000 +calculationAlgorithm = dai.SpatialLocationCalculatorAlgorithm.MEDIAN config.roi = dai.Rect(topLeft, bottomRight) spatialLocationCalculator.inputConfig.setWaitForMessage(False) @@ -123,6 +124,26 @@ topLeft.x += stepSize bottomRight.x += stepSize newConfig = True + elif key == ord('1'): + calculationAlgorithm = dai.SpatialLocationCalculatorAlgorithm.MEAN + print('Switching calculation algorithm to MEAN!') + newConfig = True + elif key == ord('2'): + calculationAlgorithm = dai.SpatialLocationCalculatorAlgorithm.MIN + print('Switching calculation algorithm to MIN!') + newConfig = True + elif key == ord('3'): + calculationAlgorithm = dai.SpatialLocationCalculatorAlgorithm.MAX + print('Switching calculation algorithm to MAX!') + newConfig = True + elif key == ord('4'): + calculationAlgorithm = dai.SpatialLocationCalculatorAlgorithm.MODE + print('Switching calculation algorithm to MODE!') + newConfig = True + elif key == ord('5'): + calculationAlgorithm = dai.SpatialLocationCalculatorAlgorithm.MEDIAN + print('Switching calculation algorithm to MEDIAN!') + newConfig = True if newConfig: config.roi = dai.Rect(topLeft, bottomRight) diff --git a/src/pipeline/datatype/SpatialLocationCalculatorConfigBindings.cpp b/src/pipeline/datatype/SpatialLocationCalculatorConfigBindings.cpp index 9d9b9a61f..9486259ba 100644 --- a/src/pipeline/datatype/SpatialLocationCalculatorConfigBindings.cpp +++ b/src/pipeline/datatype/SpatialLocationCalculatorConfigBindings.cpp @@ -43,8 +43,11 @@ void bind_spatiallocationcalculatorconfig(pybind11::module& m, void* pCallstack) spatialLocationCalculatorAlgorithm .value("AVERAGE", SpatialLocationCalculatorAlgorithm::AVERAGE) + .value("MEAN", SpatialLocationCalculatorAlgorithm::MEAN) .value("MIN", SpatialLocationCalculatorAlgorithm::MIN) .value("MAX", SpatialLocationCalculatorAlgorithm::MAX) + .value("MODE", SpatialLocationCalculatorAlgorithm::MODE) + .value("AVERAGE", SpatialLocationCalculatorAlgorithm::AVERAGE) ; spatialLocationCalculatorConfigData From f8599fff15d3fec0420df8ba6b85c8a7e2f88317 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 17 Jan 2023 23:18:01 +0200 Subject: [PATCH 097/385] Fix binding for MEDIAN --- .../datatype/SpatialLocationCalculatorConfigBindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pipeline/datatype/SpatialLocationCalculatorConfigBindings.cpp b/src/pipeline/datatype/SpatialLocationCalculatorConfigBindings.cpp index 9486259ba..84b6a4851 100644 --- a/src/pipeline/datatype/SpatialLocationCalculatorConfigBindings.cpp +++ b/src/pipeline/datatype/SpatialLocationCalculatorConfigBindings.cpp @@ -47,7 +47,7 @@ void bind_spatiallocationcalculatorconfig(pybind11::module& m, void* pCallstack) .value("MIN", SpatialLocationCalculatorAlgorithm::MIN) .value("MAX", SpatialLocationCalculatorAlgorithm::MAX) .value("MODE", SpatialLocationCalculatorAlgorithm::MODE) - .value("AVERAGE", SpatialLocationCalculatorAlgorithm::AVERAGE) + .value("MEDIAN", SpatialLocationCalculatorAlgorithm::MEDIAN) ; spatialLocationCalculatorConfigData From 352a2ddfbb855d2041886bbb937e29e640c3f4f7 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 18 Jan 2023 15:11:07 +0100 Subject: [PATCH 098/385] Updated Bootloader and VideoEncoder docs, added TCP/MQTT comms to Script node docs --- docs/source/components/bootloader.rst | 7 +++++++ docs/source/components/nodes/script.rst | 2 ++ docs/source/components/nodes/video_encoder.rst | 11 ++++++----- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/source/components/bootloader.rst b/docs/source/components/bootloader.rst index 2f90b22d1..1cee9e809 100644 --- a/docs/source/components/bootloader.rst +++ b/docs/source/components/bootloader.rst @@ -102,4 +102,11 @@ Depthai application package (**.dap**) consists of: - Assets structure (section “assets”) - Asset storage (section “asset_storage”) +MAC address +########### + +All OAK PoE cameras have a unique MAC address which is used to identify the device on the network. It is calculated from the +MxID of the device, see `logic here `__. +The MAC address is stored in the DeviceBootloader configuration. + .. include:: ../includes/footer-short.rst diff --git a/docs/source/components/nodes/script.rst b/docs/source/components/nodes/script.rst index 45efcf6db..96564fa37 100644 --- a/docs/source/components/nodes/script.rst +++ b/docs/source/components/nodes/script.rst @@ -217,6 +217,8 @@ Examples of functionality - :ref:`Script camera control` - Controlling the camera - :ref:`Script get local IP` - Get local IP - :ref:`Script HTTP client` - Send HTTP request +- `Script TCP streaming `__ - TCP communication from within Script node, either in host or client mode +- `Script MQTT publishing `__ - MQTT publishing from within Script node - :ref:`Script HTTP server` - still image over HTTP - :ref:`Script MJPEG server` - MJPEG video stream over HTTP - :ref:`Script NNData example` - Constructs :ref:`NNData` diff --git a/docs/source/components/nodes/video_encoder.rst b/docs/source/components/nodes/video_encoder.rst index 89fb48f0b..b2a1e3116 100644 --- a/docs/source/components/nodes/video_encoder.rst +++ b/docs/source/components/nodes/video_encoder.rst @@ -1,7 +1,8 @@ VideoEncoder ============ -VideoEncoder node is used to encode :ref:`image frames ` into H264/H265/JPEG. +VideoEncoder node is used to encode :ref:`ImgFrame`s into either H264, H265, or MJPEG streams. Only NV12 or GRAY8 (which gets converted to NV12) format is +supported as an input. .. include:: /includes/container-encoding.rst @@ -36,7 +37,7 @@ Inputs and Outputs **Message types** -- :code:`input` - :ref:`ImgFrame` +- :code:`input` - :ref:`ImgFrame` (NV12/GRAY8) - :code:`bitstream` - :ref:`ImgFrame` Usage @@ -55,7 +56,7 @@ Usage # Create MJPEG encoding for still images stillEncoder = pipeline.create(dai.node.VideoEncoder) - stillEncoder.setDefaultProfilePreset(cam.getStillSize(), 1, dai.VideoEncoderProperties.Profile.MJPEG) + stillEncoder.setDefaultProfilePreset(1, dai.VideoEncoderProperties.Profile.MJPEG) cam.still.link(stillEncoder.input) cam.video.link(videoEncoder.input) @@ -67,11 +68,11 @@ Usage // Create ColorCamera beforehand // Set H265 encoding for the ColorCamera video output auto videoEncoder = pipeline.create(); - videoEncoder->setDefaultProfilePreset(cam->getVideoSize(), cam->getFps(), dai::VideoEncoderProperties::Profile::H265_MAIN); + videoEncoder->setDefaultProfilePreset(cam->getFps(), dai::VideoEncoderProperties::Profile::H265_MAIN); // Create MJPEG encoding for still images stillEncoder = pipeline.create(dai.node.VideoEncoder); - stillEncoder->setDefaultProfilePreset(cam->getStillSize(), 1, dai::VideoEncoderProperties::Profile::MJPEG); + stillEncoder->setDefaultProfilePreset(1, dai::VideoEncoderProperties::Profile::MJPEG); cam->still.link(stillEncoder->input); cam->video.link(videoEncoder->input); From cf3297c4fbb6eec1fb617414c143151b87ad2589 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 18 Jan 2023 16:03:47 +0100 Subject: [PATCH 099/385] Fix docs building --- docs/source/components/nodes/video_encoder.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/components/nodes/video_encoder.rst b/docs/source/components/nodes/video_encoder.rst index b2a1e3116..060d36c1c 100644 --- a/docs/source/components/nodes/video_encoder.rst +++ b/docs/source/components/nodes/video_encoder.rst @@ -1,7 +1,7 @@ VideoEncoder ============ -VideoEncoder node is used to encode :ref:`ImgFrame`s into either H264, H265, or MJPEG streams. Only NV12 or GRAY8 (which gets converted to NV12) format is +VideoEncoder node is used to encode :ref:`ImgFrame` into either H264, H265, or MJPEG streams. Only NV12 or GRAY8 (which gets converted to NV12) format is supported as an input. .. include:: /includes/container-encoding.rst From f90eed9a3af09a0c43c36e0383c095ef4786fe4e Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Wed, 18 Jan 2023 16:04:58 +0100 Subject: [PATCH 100/385] Camera - Disabled some of the functionality for now --- depthai-core | 2 +- examples/Camera/camera_isp.py | 62 ++++++++++++++++++++++++++++ src/pipeline/node/CameraBindings.cpp | 22 +++++----- 3 files changed, 74 insertions(+), 12 deletions(-) create mode 100755 examples/Camera/camera_isp.py diff --git a/depthai-core b/depthai-core index 66c8065cd..1e9d2124b 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 66c8065cd84607a1c9dff706dab6710458e81703 +Subproject commit 1e9d2124b168b04ef9f00b4ca9321aabd57a305e diff --git a/examples/Camera/camera_isp.py b/examples/Camera/camera_isp.py new file mode 100755 index 000000000..5173d56e9 --- /dev/null +++ b/examples/Camera/camera_isp.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +import cv2 +import depthai as dai +import time + +# Connect to device and start pipeline +with dai.Device() as device: + # Device name + print('Device name:', device.getDeviceName()) + # Bootloader version + if device.getBootloaderVersion() is not None: + print('Bootloader version:', device.getBootloaderVersion()) + # Print out usb speed + print('Usb speed:', device.getUsbSpeed().name) + # Connected cameras + print('Connected cameras:', device.getConnectedCameraFeatures()) + + # Create pipeline + pipeline = dai.Pipeline() + cams = device.getConnectedCameraFeatures() + streams = [] + for cam in cams: + print(str(cam), str(cam.socket), cam.socket) + c = pipeline.create(dai.node.Camera) + x = pipeline.create(dai.node.XLinkOut) + c.isp.link(x.input) + c.setBoardSocket(cam.socket) + stream = str(cam.socket) + if cam.name: + stream = f'{cam.name} ({stream})' + x.setStreamName(stream) + streams.append(stream) + + # Start pipeline + device.startPipeline(pipeline) + fpsCounter = {} + lastFpsCount = {} + tfps = time.time() + while not device.isClosed(): + queueNames = device.getQueueEvents(streams) + for stream in queueNames: + messages = device.getOutputQueue(stream).tryGetAll() + fpsCounter[stream] = fpsCounter.get(stream, 0.0) + len(messages) + for message in messages: + # Display arrived frames + if type(message) == dai.ImgFrame: + # render fps + fps = lastFpsCount.get(stream, 0) + frame = message.getCvFrame() + cv2.putText(frame, "Fps: {:.2f}".format(fps), (10, 10), cv2.FONT_HERSHEY_TRIPLEX, 0.4, (255,255,255)) + cv2.imshow(stream, frame) + + if time.time() - tfps >= 1.0: + scale = time.time() - tfps + for stream in fpsCounter.keys(): + lastFpsCount[stream] = fpsCounter[stream] / scale + fpsCounter = {} + tfps = time.time() + + if cv2.waitKey(1) == ord('q'): + break diff --git a/src/pipeline/node/CameraBindings.cpp b/src/pipeline/node/CameraBindings.cpp index 60a7640b8..421d0a7b9 100644 --- a/src/pipeline/node/CameraBindings.cpp +++ b/src/pipeline/node/CameraBindings.cpp @@ -103,7 +103,7 @@ void bind_camera(pybind11::module& m, void* pCallstack){ .def_readonly("isp", &Camera::isp, DOC(dai, node, Camera, isp)) .def_readonly("raw", &Camera::raw, DOC(dai, node, Camera, raw)) .def_readonly("frameEvent", &Camera::frameEvent, DOC(dai, node, Camera, frameEvent)) - .def_readonly("mockIsp", &Camera::mockIsp, DOC(dai, node, Camera, mockIsp)) + // .def_readonly("mockIsp", &Camera::mockIsp, DOC(dai, node, Camera, mockIsp)) .def("setBoardSocket", &Camera::setBoardSocket, py::arg("boardSocket"), DOC(dai, node, Camera, setBoardSocket)) .def("getBoardSocket", &Camera::getBoardSocket, DOC(dai, node, Camera, getBoardSocket)) .def("setImageOrientation", &Camera::setImageOrientation, py::arg("imageOrientation"), DOC(dai, node, Camera, setImageOrientation)) @@ -130,16 +130,16 @@ void bind_camera(pybind11::module& m, void* pCallstack){ .def("getSize", &Camera::getSize, DOC(dai, node, Camera, getSize)) .def("getWidth", &Camera::getWidth, DOC(dai, node, Camera, getWidth)) .def("getHeight", &Camera::getHeight, DOC(dai, node, Camera, getHeight)) - .def("sensorCenterCrop", &Camera::sensorCenterCrop, DOC(dai, node, Camera, sensorCenterCrop)) - .def("setSensorCrop", &Camera::setSensorCrop, py::arg("x"), py::arg("y"), DOC(dai, node, Camera, setSensorCrop)) - .def("getSensorCrop", &Camera::getSensorCrop, DOC(dai, node, Camera, getSensorCrop)) - .def("getSensorCropX", &Camera::getSensorCropX, DOC(dai, node, Camera, getSensorCropX)) - .def("getSensorCropY", &Camera::getSensorCropY, DOC(dai, node, Camera, getSensorCropY)) - - .def("setIspScale", static_cast(&Camera::setIspScale), py::arg("numerator"), py::arg("denominator"), DOC(dai, node, Camera, setIspScale)) - .def("setIspScale", static_cast)>(&Camera::setIspScale), py::arg("scale"), DOC(dai, node, Camera, setIspScale, 2)) - .def("setIspScale", static_cast(&Camera::setIspScale), py::arg("horizNum"), py::arg("horizDenom"), py::arg("vertNum"), py::arg("vertDenom"), DOC(dai, node, Camera, setIspScale, 3)) - .def("setIspScale", static_cast,std::tuple)>(&Camera::setIspScale), py::arg("horizScale"), py::arg("vertScale"), DOC(dai, node, Camera, setIspScale, 4)) + // .def("sensorCenterCrop", &Camera::sensorCenterCrop, DOC(dai, node, Camera, sensorCenterCrop)) + // .def("setSensorCrop", &Camera::setSensorCrop, py::arg("x"), py::arg("y"), DOC(dai, node, Camera, setSensorCrop)) + // .def("getSensorCrop", &Camera::getSensorCrop, DOC(dai, node, Camera, getSensorCrop)) + // .def("getSensorCropX", &Camera::getSensorCropX, DOC(dai, node, Camera, getSensorCropX)) + // .def("getSensorCropY", &Camera::getSensorCropY, DOC(dai, node, Camera, getSensorCropY)) + + // .def("setIspScale", static_cast(&Camera::setIspScale), py::arg("numerator"), py::arg("denominator"), DOC(dai, node, Camera, setIspScale)) + // .def("setIspScale", static_cast)>(&Camera::setIspScale), py::arg("scale"), DOC(dai, node, Camera, setIspScale, 2)) + // .def("setIspScale", static_cast(&Camera::setIspScale), py::arg("horizNum"), py::arg("horizDenom"), py::arg("vertNum"), py::arg("vertDenom"), DOC(dai, node, Camera, setIspScale, 3)) + // .def("setIspScale", static_cast,std::tuple)>(&Camera::setIspScale), py::arg("horizScale"), py::arg("vertScale"), DOC(dai, node, Camera, setIspScale, 4)) .def("setCamera", &Camera::setCamera, py::arg("name"), DOC(dai, node, Camera, setCamera)) .def("getCamera", &Camera::getCamera, DOC(dai, node, Camera, getCamera)) From 920c739fd21083b04b32653196a63ca37cfa6467 Mon Sep 17 00:00:00 2001 From: Petr Novota <99871801+PetrNovota@users.noreply.github.com> Date: Wed, 18 Jan 2023 18:00:47 +0100 Subject: [PATCH 101/385] installer patchl, output text formatting Console output text did not show properly for lines 230, 231 --- docs/source/_static/install_depthai.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh index 2fe62a090..a1fae2203 100755 --- a/docs/source/_static/install_depthai.sh +++ b/docs/source/_static/install_depthai.sh @@ -227,8 +227,8 @@ else exit 99 fi -echo '\n\n:::::::::::::::: INSTALATION COMPLETE ::::::::::::::::\n' -echo '\nTo run demo app write in terminal.' +echo $'\n\n:::::::::::::::: INSTALATION COMPLETE ::::::::::::::::\n' +echo $'\nTo run demo app write in terminal.' read -rsp $'Press ANY KEY to finish and run the demo app...\n' -n1 key echo "STARTING DEMO APP." python "$DEPTHAI_DIR/launcher/launcher.py" -r "$DEPTHAI_DIR" From f8990a237f70698ece425f7e112e6f3cff94c424 Mon Sep 17 00:00:00 2001 From: Petr Novota <99871801+PetrNovota@users.noreply.github.com> Date: Wed, 18 Jan 2023 18:21:47 +0100 Subject: [PATCH 102/385] better way to evaluate backslash chars --- docs/source/_static/install_depthai.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh index a1fae2203..af4ed3dca 100755 --- a/docs/source/_static/install_depthai.sh +++ b/docs/source/_static/install_depthai.sh @@ -187,7 +187,7 @@ elif [[ $(uname -s) == "Linux" ]]; then echo "Installing global dependencies." sudo wget -qO- https://docs.luxonis.com/install_dependencies.sh | bash - echo $'\nRunning Linux installer.' + echo -e '\nRunning Linux installer.' # clone depthai form git if [ -d "$DEPTHAI_DIR" ]; then @@ -227,8 +227,8 @@ else exit 99 fi -echo $'\n\n:::::::::::::::: INSTALATION COMPLETE ::::::::::::::::\n' -echo $'\nTo run demo app write in terminal.' +echo -e '\n\n:::::::::::::::: INSTALATION COMPLETE ::::::::::::::::\n' +echo -e '\nTo run demo app write in terminal.' read -rsp $'Press ANY KEY to finish and run the demo app...\n' -n1 key echo "STARTING DEMO APP." python "$DEPTHAI_DIR/launcher/launcher.py" -r "$DEPTHAI_DIR" From 7ff599b97fc513caeb716037435d513991efc572 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Fri, 20 Jan 2023 14:09:14 +0100 Subject: [PATCH 103/385] Tweaked getTimestamp & exposure offset API --- depthai-core | 2 +- src/pipeline/CommonBindings.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 7cb60d4e3..c12ce3335 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 7cb60d4e3fa5abe6f5a4f55b684fcd81493959b6 +Subproject commit c12ce33350c2dacd9d300756676c0fa6ad20ff52 diff --git a/src/pipeline/CommonBindings.cpp b/src/pipeline/CommonBindings.cpp index 2f8c346bc..1d1558ec6 100644 --- a/src/pipeline/CommonBindings.cpp +++ b/src/pipeline/CommonBindings.cpp @@ -22,6 +22,7 @@ // depthai #include "depthai/common/CameraFeatures.hpp" +#include "depthai/common/CameraExposureOffset.hpp" void CommonBindings::bind(pybind11::module& m, void* pCallstack){ @@ -50,6 +51,7 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ py::class_ detectionParserOptions(m, "DetectionParserOptions", DOC(dai, DetectionParserOptions)); py::class_ rotatedRect(m, "RotatedRect", DOC(dai, RotatedRect)); py::class_ rect(m, "Rect", DOC(dai, Rect)); + py::enum_ cameraExposureOffset(m, "CameraExposureOffset"); /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// @@ -290,4 +292,9 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("iouThreshold", &DetectionParserOptions::iouThreshold) ; + cameraExposureOffset + .value("START", CameraExposureOffset::START) + .value("MIDDLE", CameraExposureOffset::MIDDLE) + .value("END", CameraExposureOffset::END) + ; } From 9740db602a7b616780e448ae383fd1aea2cbafda Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Tue, 13 Dec 2022 05:39:33 +0200 Subject: [PATCH 104/385] FW: IMX296 support, add THE_1440x1080 resolution --- depthai-core | 2 +- src/pipeline/node/ColorCameraBindings.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 4031f85ed..de8136f02 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 4031f85edcc9012e9275ac433fd180c492c62e7f +Subproject commit de8136f02865f15c01fbf10e3bfe2de7d2c4dfc2 diff --git a/src/pipeline/node/ColorCameraBindings.cpp b/src/pipeline/node/ColorCameraBindings.cpp index cd39b8cc3..143e3c5ff 100644 --- a/src/pipeline/node/ColorCameraBindings.cpp +++ b/src/pipeline/node/ColorCameraBindings.cpp @@ -42,6 +42,7 @@ void bind_colorcamera(pybind11::module& m, void* pCallstack){ .value("THE_48_MP", ColorCameraProperties::SensorResolution::THE_48_MP) .value("THE_720_P", ColorCameraProperties::SensorResolution::THE_720_P) .value("THE_800_P", ColorCameraProperties::SensorResolution::THE_800_P) + .value("THE_1440X1080", ColorCameraProperties::SensorResolution::THE_1440X1080) ; colorCameraPropertiesColorOrder From b04d2dfae4adfc6eba0a22c87a133619ec87ba26 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Thu, 10 Nov 2022 18:36:29 +0200 Subject: [PATCH 105/385] cam_test.py: add `-tun`/`--camera-tuning` option --- utilities/cam_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 36456584a..58fb20bc8 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -41,6 +41,7 @@ import collections import time from itertools import cycle +from pathlib import Path def socket_type_pair(arg): socket, type = arg.split(',') @@ -66,6 +67,8 @@ def socket_type_pair(arg): help="Downscale the ISP output by this factor") parser.add_argument('-rs', '--resizable-windows', action='store_true', help="Make OpenCV windows resizable. Note: may introduce some artifacts") +parser.add_argument('-tun', '--camera-tuning', type=Path, + help="Path to custom camera tuning database") args = parser.parse_args() cam_list = [] @@ -174,11 +177,8 @@ def get(self): cam[c].setImageOrientation(dai.CameraImageOrientation.ROTATE_180_DEG) cam[c].setFps(args.fps) -if 0: - print("=== Using custom camera tuning, and limiting RGB FPS to 10") - pipeline.setCameraTuningBlobPath("/home/user/Downloads/tuning_color_low_light.bin") - # TODO: change sensor driver to make FPS automatic (based on requested exposure time) - cam['rgb'].setFps(10) +if args.camera_tuning: + pipeline.setCameraTuningBlobPath(str(args.camera_tuning)) # Pipeline is defined, now we can connect to the device with dai.Device(pipeline) as device: From 125b025cf89ea7ca549f0909d29768c7745d3bca Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Fri, 20 Jan 2023 19:06:03 +0200 Subject: [PATCH 106/385] FW: IMX296 Camera node, IMX378 1080p limited to 60fps --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index de8136f02..34c4b2fe6 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit de8136f02865f15c01fbf10e3bfe2de7d2c4dfc2 +Subproject commit 34c4b2fe6d497f70dd3d4551b5e20abd0a3ae466 From 8e5b54741c8ef506a1542f987230def70847ed2e Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Sat, 21 Jan 2023 00:04:14 +0100 Subject: [PATCH 107/385] Bump version to 2.20.0.0 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 34c4b2fe6..48484a573 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 34c4b2fe6d497f70dd3d4551b5e20abd0a3ae466 +Subproject commit 48484a5731b99699461c6ca34317063ba6ea2608 From b4ca86bdc3ccce3e9abd277fa177ddc607fb2d23 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Sat, 21 Jan 2023 18:37:54 +0100 Subject: [PATCH 108/385] Updated core, prepare for 2.20.0.0 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index d84f65b78..49a036445 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit d84f65b78a333e2ed29ba77f15fe5790ab958794 +Subproject commit 49a036445d3a750477cf32ff4b23ed718e14d3b1 From bd5ad071ddc2f7e24c36bf880bb6091b96b1c439 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 24 Jan 2023 00:08:44 +0200 Subject: [PATCH 109/385] Update FW --- depthai-core | 2 +- examples/SpatialDetection/spatial_location_calculator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depthai-core b/depthai-core index 4fbedf1b7..b4f49e423 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 4fbedf1b701dc629ad9a16e14af66a8b2b722461 +Subproject commit b4f49e423b25f95e72a76223a4375d2e3aaf7162 diff --git a/examples/SpatialDetection/spatial_location_calculator.py b/examples/SpatialDetection/spatial_location_calculator.py index 4a7cb9af1..8e30f0a3c 100755 --- a/examples/SpatialDetection/spatial_location_calculator.py +++ b/examples/SpatialDetection/spatial_location_calculator.py @@ -147,7 +147,7 @@ if newConfig: config.roi = dai.Rect(topLeft, bottomRight) - config.calculationAlgorithm = dai.SpatialLocationCalculatorAlgorithm.AVERAGE + config.calculationAlgorithm = calculationAlgorithm cfg = dai.SpatialLocationCalculatorConfig() cfg.addROI(config) spatialCalcConfigInQueue.send(cfg) From d92b874f9f28804476dc2583bff2c0ce5fad6aff Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 24 Jan 2023 13:21:03 +0200 Subject: [PATCH 110/385] Modified OpenVINO::VERSION_UNIVERSAL --- depthai-core | 2 +- examples/Camera/camera_preview.py | 2 +- examples/calibration/calibration_flash.py | 3 +-- src/DeviceBindings.cpp | 4 ++-- src/openvino/OpenVINOBindings.cpp | 1 + src/pipeline/PipelineBindings.cpp | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/depthai-core b/depthai-core index 49a036445..b4cb5378c 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 49a036445d3a750477cf32ff4b23ed718e14d3b1 +Subproject commit b4cb5378cab41c1965a166f5ae3e8ec9302ae92c diff --git a/examples/Camera/camera_preview.py b/examples/Camera/camera_preview.py index ba9aa108a..88c5cc3c3 100755 --- a/examples/Camera/camera_preview.py +++ b/examples/Camera/camera_preview.py @@ -5,7 +5,7 @@ import time # Connect to device and start pipeline -with dai.Device(dai.OpenVINO.DEFAULT_VERSION, dai.UsbSpeed.SUPER_PLUS) as device: +with dai.Device() as device: # Device name print('Device name:', device.getDeviceName()) # Bootloader version diff --git a/examples/calibration/calibration_flash.py b/examples/calibration/calibration_flash.py index 34a552d50..569fe4201 100755 --- a/examples/calibration/calibration_flash.py +++ b/examples/calibration/calibration_flash.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 from pathlib import Path -import cv2 import depthai as dai import argparse @@ -13,7 +12,7 @@ args = parser.parse_args() # Connect device -with dai.Device(dai.OpenVINO.VERSION_2021_4, dai.UsbSpeed.HIGH) as device: +with dai.Device(dai.UsbSpeed.HIGH) as device: deviceCalib = device.readCalibration() deviceCalib.eepromToJsonFile(calibBackUpFile) diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index a61e3e770..976354b8b 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -250,7 +250,7 @@ static void bindConstructors(ARG& arg){ auto dev = deviceSearchHelper(); py::gil_scoped_release release; return std::make_unique(version, dev); - }), py::arg("version") = OpenVINO::DEFAULT_VERSION, DOC(dai, DeviceBase, DeviceBase, 10)) + }), py::arg("version") = OpenVINO::VERSION_UNIVERSAL, DOC(dai, DeviceBase, DeviceBase, 10)) .def(py::init([](OpenVINO::Version version, bool usb2Mode){ auto dev = deviceSearchHelper(); py::gil_scoped_release release; @@ -491,7 +491,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_static("getAnyAvailableDevice", [](){ return DeviceBase::getAnyAvailableDevice(); }, DOC(dai, DeviceBase, getAnyAvailableDevice, 2)) .def_static("getFirstAvailableDevice", &DeviceBase::getFirstAvailableDevice, py::arg("skipInvalidDevices") = true, DOC(dai, DeviceBase, getFirstAvailableDevice)) .def_static("getAllAvailableDevices", &DeviceBase::getAllAvailableDevices, DOC(dai, DeviceBase, getAllAvailableDevices)) - .def_static("getEmbeddedDeviceBinary", py::overload_cast(&DeviceBase::getEmbeddedDeviceBinary), py::arg("usb2Mode"), py::arg("version") = OpenVINO::DEFAULT_VERSION, DOC(dai, DeviceBase, getEmbeddedDeviceBinary)) + .def_static("getEmbeddedDeviceBinary", py::overload_cast(&DeviceBase::getEmbeddedDeviceBinary), py::arg("usb2Mode"), py::arg("version") = OpenVINO::VERSION_UNIVERSAL, DOC(dai, DeviceBase, getEmbeddedDeviceBinary)) .def_static("getEmbeddedDeviceBinary", py::overload_cast(&DeviceBase::getEmbeddedDeviceBinary), py::arg("config"), DOC(dai, DeviceBase, getEmbeddedDeviceBinary, 2)) .def_static("getDeviceByMxId", &DeviceBase::getDeviceByMxId, py::arg("mxId"), DOC(dai, DeviceBase, getDeviceByMxId)) .def_static("getAllConnectedDevices", &DeviceBase::getAllConnectedDevices, DOC(dai, DeviceBase, getAllConnectedDevices)) diff --git a/src/openvino/OpenVINOBindings.cpp b/src/openvino/OpenVINOBindings.cpp index e2b9e1451..8ebd32198 100644 --- a/src/openvino/OpenVINOBindings.cpp +++ b/src/openvino/OpenVINOBindings.cpp @@ -52,6 +52,7 @@ void OpenVINOBindings::bind(pybind11::module& m, void* pCallstack){ .value("VERSION_2021_3", OpenVINO::Version::VERSION_2021_3) .value("VERSION_2021_4", OpenVINO::Version::VERSION_2021_4) .value("VERSION_2022_1", OpenVINO::Version::VERSION_2022_1) + .value("VERSION_UNIVERSAL", OpenVINO::Version::VERSION_UNIVERSAL) .export_values() ; // DEFAULT_VERSION binding diff --git a/src/pipeline/PipelineBindings.cpp b/src/pipeline/PipelineBindings.cpp index cfdd9f588..12eb4ee07 100644 --- a/src/pipeline/PipelineBindings.cpp +++ b/src/pipeline/PipelineBindings.cpp @@ -94,7 +94,7 @@ void PipelineBindings::bind(pybind11::module& m, void* pCallstack){ .def("unlink", &Pipeline::unlink, DOC(dai, Pipeline, unlink), DOC(dai, Pipeline, unlink)) .def("getAssetManager", static_cast(&Pipeline::getAssetManager), py::return_value_policy::reference_internal, DOC(dai, Pipeline, getAssetManager)) .def("getAssetManager", static_cast(&Pipeline::getAssetManager), py::return_value_policy::reference_internal, DOC(dai, Pipeline, getAssetManager)) - .def("setOpenVINOVersion", &Pipeline::setOpenVINOVersion, py::arg("version") = OpenVINO::DEFAULT_VERSION, DOC(dai, Pipeline, setOpenVINOVersion)) + .def("setOpenVINOVersion", &Pipeline::setOpenVINOVersion, py::arg("version"), DOC(dai, Pipeline, setOpenVINOVersion)) .def("getOpenVINOVersion", &Pipeline::getOpenVINOVersion, DOC(dai, Pipeline, getOpenVINOVersion)) .def("getRequiredOpenVINOVersion", &Pipeline::getRequiredOpenVINOVersion, DOC(dai, Pipeline, getRequiredOpenVINOVersion)) .def("setCameraTuningBlobPath", &Pipeline::setCameraTuningBlobPath, py::arg("path"), DOC(dai, Pipeline, setCameraTuningBlobPath)) From dfd8874568f3843c1a927fa29d4ae02358d2c194 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 24 Jan 2023 13:21:15 +0200 Subject: [PATCH 111/385] Updated Bootloader to 0.0.24 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index b4cb5378c..d8a1f3b0b 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit b4cb5378cab41c1965a166f5ae3e8ec9302ae92c +Subproject commit d8a1f3b0bc2cbc03ba179b28078cb20850d73ae3 From 98d167cbbee9503fb8ee46a9258c14fc889f827c Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 24 Jan 2023 14:38:52 +0200 Subject: [PATCH 112/385] Bump version and FW to 2.20.1 (Status LEDs improvement) --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index d8a1f3b0b..774763d4e 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit d8a1f3b0bc2cbc03ba179b28078cb20850d73ae3 +Subproject commit 774763d4ea684ba8d004e94f5d90919319e61a0d From f579f50b999a6e496ad735122bbc6ea9b89b1429 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 24 Jan 2023 16:05:12 +0200 Subject: [PATCH 113/385] calibration_flash example fix --- depthai-core | 2 +- examples/calibration/calibration_flash.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depthai-core b/depthai-core index 774763d4e..01fdd7d26 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 774763d4ea684ba8d004e94f5d90919319e61a0d +Subproject commit 01fdd7d26341b8a800cce6292cc6f9a21c75bce7 diff --git a/examples/calibration/calibration_flash.py b/examples/calibration/calibration_flash.py index 569fe4201..325902921 100755 --- a/examples/calibration/calibration_flash.py +++ b/examples/calibration/calibration_flash.py @@ -12,7 +12,7 @@ args = parser.parse_args() # Connect device -with dai.Device(dai.UsbSpeed.HIGH) as device: +with dai.Device(dai.OpenVINO.VERSION_UNIVERSAL, dai.UsbSpeed.HIGH) as device: deviceCalib = device.readCalibration() deviceCalib.eepromToJsonFile(calibBackUpFile) From f60491acaa6cd51d7bffb1f5613a85b4faa0a47b Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Wed, 25 Jan 2023 13:21:08 +0200 Subject: [PATCH 114/385] Added missing generate_stubs.py into source dist --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 70b8bf13a..536965148 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include README.md LICENSE CMakeLists.txt pyproject.toml include find_version.py +include generate_stubs.py graft cmake graft generated graft ci From 4a9b9f58271e87e59f2f458db02a9066ca6e0bc1 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 25 Jan 2023 16:49:50 +0200 Subject: [PATCH 115/385] Started adding warp node docs --- docs/source/components/nodes/image_manip.rst | 2 +- docs/source/components/nodes/warp.rst | 114 +++++++++++++++++++ docs/source/samples/Warp/warp_mesh.rst | 48 ++++++++ 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 docs/source/components/nodes/warp.rst create mode 100644 docs/source/samples/Warp/warp_mesh.rst diff --git a/docs/source/components/nodes/image_manip.rst b/docs/source/components/nodes/image_manip.rst index 3d3b69a6b..bb466e9fb 100644 --- a/docs/source/components/nodes/image_manip.rst +++ b/docs/source/components/nodes/image_manip.rst @@ -76,7 +76,7 @@ ImageManip node supports the following image formats (more info `in PR here `__), +with no extra resources (SHAVE/cmx cores). + +**ImageManip node** combines the power of warp HW block together the efficiency of CMX memory to achieve higher +throughput (e.g. 4k@30 fps). Scheduling of the HW block is done by SHAVE cores which also do color space conversion, type conversion (YUV420 to NV12), etc. +The downside of using ImageManip node is extra RAM and SHAVE usage. + +How to place it +############### + +.. tabs:: + + .. code-tab:: py + + pipeline = dai.Pipeline() + imu = pipeline.create(dai.node.Warp) + + .. code-tab:: c++ + + dai::Pipeline pipeline; + auto imu = pipeline.create(); + + +Inputs and Outputs +################## + +.. code-block:: + + ┌────────────┐ + inputImage │ │ out + ──────────►│ Warp ├──────► + │ │ + └────────────┘ + +**Message types** + +- ``inputImage`` - :ref:`ImgFrame` +- ``out`` - :ref:`ImgFrame` + +Usage +##### + +.. tabs:: + + .. code-tab:: py + + pipeline = dai.Pipeline() + + warp = pipeline.create(dai.node.Warp) + # Create a custom warp mesh + p1 = dai.Point2f(20, 20) + p2 = dai.Point2f(460, 20) + p3 = dai.Point2f(20, 460) + p4 = dai.Point2f(460, 460) + warp.setWarpMesh([p1,p2,p3,p4], 2, 2) + warp.setOutputSize((992,500)) + warp.setMaxOutputFrameSize(992 * 500 * 3) + # Specify hardware warp engine IDs to be used + warp.setHwIds([1]) + # Warp interpolation mode, choose between BILINEAR, BICUBIC, BYPASS + warp.setInterpolation(dai.node.Warp.Properties.Interpolation.BYPASS) + + .. code-tab:: c++ + + dai::Pipeline pipeline; + + auto warp = pipeline.create(); + // Create a custom warp mesh + dai::Point2f p1(20, 20); + dai::Point2f p2(460, 20); + dai::Point2f p3(20, 460); + dai::Point2f p4(460, 460); + warp->setWarpMesh({p1,p2,p3,p4}, 2, 2); + warp->setOutputSize({992, 500}); + warp->setMaxOutputFrameSize(992 * 500 * 3); + // Specify hardware warp engine IDs to be used + warp->setHwIds({1}); + // Warp interpolation mode, choose between BILINEAR, BICUBIC, BYPASS + warp->setInterpolation(dai::node::Warp::Properties::Interpolation::BYPASS); + +Examples of functionality +######################### + +- :ref:`Warp Mesh` + +Reference +######### + +.. tabs:: + + .. tab:: Python + + .. autoclass:: depthai.node.Warp + :members: + :inherited-members: + :noindex: + + .. tab:: C++ + + .. doxygenclass:: dai::node::Warp + :project: depthai-core + :members: + :private-members: + :undoc-members: + +.. include:: ../../includes/footer-short.rst diff --git a/docs/source/samples/Warp/warp_mesh.rst b/docs/source/samples/Warp/warp_mesh.rst new file mode 100644 index 000000000..e4db6b24e --- /dev/null +++ b/docs/source/samples/Warp/warp_mesh.rst @@ -0,0 +1,48 @@ +Warp Mesh +========= + +This example shows usage of :ref:`ImageManip` to crop a rotated rectangle area on a frame, +or perform various image transforms: rotate, mirror, flip, perspective transform. + +Setup +##### + +.. include:: /includes/install_from_pypi.rst + +Demo +#### + +.. image:: https://user-images.githubusercontent.com/18037362/152208899-461fa163-42ec-4922-84b5-5cd09332ea32.png + +.. code-block:: + + === Controls: + z -rotated rectangle crop, decrease rate + x -rotated rectangle crop, increase rate + c -warp 4-point transform, cycle through modes + v -resize cropped region, or disable resize + h -print controls (help) + + +Source code +########### + +.. tabs:: + + .. tab:: Python + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../examples/ImageManip/rgb_rotate_warp.py + :language: python + :linenos: + + .. tab:: C++ + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../depthai-core/examples/ImageManip/rgb_rotate_warp.cpp + :language: cpp + :linenos: + +.. include:: /includes/footer-short.rst From bd317aaecd0c505a928be0eb73d582b93acd5781 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 25 Jan 2023 17:53:31 +0200 Subject: [PATCH 116/385] Added docs for the two warp node examples --- docs/source/components/nodes/warp.rst | 20 +-- docs/source/samples/Warp/warp_mesh.rst | 22 +-- .../samples/Warp/warp_mesh_interactive.rst | 43 +++++ docs/source/tutorials/code_samples.rst | 6 + examples/Warp/warp_mesh_interactive.py | 153 ++++++++++++++++++ 5 files changed, 219 insertions(+), 25 deletions(-) create mode 100644 docs/source/samples/Warp/warp_mesh_interactive.rst create mode 100644 examples/Warp/warp_mesh_interactive.py diff --git a/docs/source/components/nodes/warp.rst b/docs/source/components/nodes/warp.rst index 828a91284..b707990d0 100644 --- a/docs/source/components/nodes/warp.rst +++ b/docs/source/components/nodes/warp.rst @@ -7,7 +7,7 @@ The node can also be used to apply a perspective transform to the image. Compared to :ref:`ImageManip` node (the `setWarpMesh()` function): **Warp node** uses underlyting warp HW block (additional `docs here `__), -with no extra resources (SHAVE/cmx cores). +with no extra resources (SHAVE/cmx cores). HW limitation: **width must be divisible by 16.** **ImageManip node** combines the power of warp HW block together the efficiency of CMX memory to achieve higher throughput (e.g. 4k@30 fps). Scheduling of the HW block is done by SHAVE cores which also do color space conversion, type conversion (YUV420 to NV12), etc. @@ -21,13 +21,12 @@ How to place it .. code-tab:: py pipeline = dai.Pipeline() - imu = pipeline.create(dai.node.Warp) + warp = pipeline.create(dai.node.Warp) .. code-tab:: c++ dai::Pipeline pipeline; - auto imu = pipeline.create(); - + auto warp = pipeline.create(); Inputs and Outputs ################## @@ -61,9 +60,9 @@ Usage p3 = dai.Point2f(20, 460) p4 = dai.Point2f(460, 460) warp.setWarpMesh([p1,p2,p3,p4], 2, 2) - warp.setOutputSize((992,500)) - warp.setMaxOutputFrameSize(992 * 500 * 3) - # Specify hardware warp engine IDs to be used + warp.setOutputSize((512,512)) + warp.setMaxOutputFrameSize(512 * 512 * 3) + # Warp engines to be used (0,1,2) warp.setHwIds([1]) # Warp interpolation mode, choose between BILINEAR, BICUBIC, BYPASS warp.setInterpolation(dai.node.Warp.Properties.Interpolation.BYPASS) @@ -79,9 +78,9 @@ Usage dai::Point2f p3(20, 460); dai::Point2f p4(460, 460); warp->setWarpMesh({p1,p2,p3,p4}, 2, 2); - warp->setOutputSize({992, 500}); - warp->setMaxOutputFrameSize(992 * 500 * 3); - // Specify hardware warp engine IDs to be used + warp->setOutputSize({512, 512}); + warp->setMaxOutputFrameSize(512 * 512 * 3); + // Warp engines to be used (0,1,2) warp->setHwIds({1}); // Warp interpolation mode, choose between BILINEAR, BICUBIC, BYPASS warp->setInterpolation(dai::node::Warp::Properties::Interpolation::BYPASS); @@ -90,6 +89,7 @@ Examples of functionality ######################### - :ref:`Warp Mesh` +- :ref:`Interactive Warp Mesh` Reference ######### diff --git a/docs/source/samples/Warp/warp_mesh.rst b/docs/source/samples/Warp/warp_mesh.rst index e4db6b24e..671e6933c 100644 --- a/docs/source/samples/Warp/warp_mesh.rst +++ b/docs/source/samples/Warp/warp_mesh.rst @@ -1,8 +1,7 @@ Warp Mesh ========= -This example shows usage of :ref:`ImageManip` to crop a rotated rectangle area on a frame, -or perform various image transforms: rotate, mirror, flip, perspective transform. +This example shows usage of :ref:`Warp` node to warp the input image frame. Setup ##### @@ -12,16 +11,9 @@ Setup Demo #### -.. image:: https://user-images.githubusercontent.com/18037362/152208899-461fa163-42ec-4922-84b5-5cd09332ea32.png +.. figure:: https://user-images.githubusercontent.com/18037362/214597821-2f76239a-48fa-4146-ba47-9cad872454ea.png -.. code-block:: - - === Controls: - z -rotated rectangle crop, decrease rate - x -rotated rectangle crop, increase rate - c -warp 4-point transform, cycle through modes - v -resize cropped region, or disable resize - h -print controls (help) + Warped images Source code @@ -31,17 +23,17 @@ Source code .. tab:: Python - Also `available on GitHub `__ + Also `available on GitHub `__ - .. literalinclude:: ../../../../examples/ImageManip/rgb_rotate_warp.py + .. literalinclude:: ../../../../examples/Warp/warp_mesh.py :language: python :linenos: .. tab:: C++ - Also `available on GitHub `__ + Also `available on GitHub `__ - .. literalinclude:: ../../../../depthai-core/examples/ImageManip/rgb_rotate_warp.cpp + .. literalinclude:: ../../../../depthai-core/examples/Warp/warp_mesh.cpp :language: cpp :linenos: diff --git a/docs/source/samples/Warp/warp_mesh_interactive.rst b/docs/source/samples/Warp/warp_mesh_interactive.rst new file mode 100644 index 000000000..f6ef1399a --- /dev/null +++ b/docs/source/samples/Warp/warp_mesh_interactive.rst @@ -0,0 +1,43 @@ +Interactive Warp Mesh +===================== + +This example shows usage of :ref:`Warp` node to warp the input image frame. It let's you interactively change the mesh points to warp the image. After changing the points, +**user has to press** ``r`` to restart the pipeline and apply the changes. + +User-defined arguments: + +- ``--mesh_dims`` - Mesh dimensions (default: ``4x4``). +- ``--resolution`` - Resolution of the input image (default: ``512x512``). Width must be divisible by 16. +- ``--random`` - To generate random mesh points (disabled by default). + +Setup +##### + +.. include:: /includes/install_from_pypi.rst + +Demo +#### + +.. figure:: https://user-images.githubusercontent.com/18037362/214605914-87cf0404-2d89-478f-9062-2dfb4baa6512.png + + Original and warped image + + +Source code +########### + +.. tabs:: + + .. tab:: Python + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../examples/Warp/warp_mesh_interactive.py + :language: python + :linenos: + + .. tab:: C++ + + WIP + +.. include:: /includes/footer-short.rst diff --git a/docs/source/tutorials/code_samples.rst b/docs/source/tutorials/code_samples.rst index e817ae708..46127c230 100644 --- a/docs/source/tutorials/code_samples.rst +++ b/docs/source/tutorials/code_samples.rst @@ -23,6 +23,7 @@ Code Samples ../samples/StereoDepth/* ../samples/SystemLogger/* ../samples/VideoEncoder/* + ../samples/Warp/* ../samples/Yolo/* Code samples are used for automated testing. They are also a great starting point for the DepthAI API, as different node functionalities @@ -150,6 +151,11 @@ are presented with code. - :ref:`Encoding Max Limit` - Encodes RGB (4k 25FPS) and both mono streams (720P, 25FPS) into :code:`.h265`/:code:`.h264` and saves them on the host - :ref:`RGB Full Resolution Saver` - Saves full resolution RGB images (4k) on the host (:code:`.jpeg`) +.. rubric:: Warp + +- :ref:`Warp Mesh` - Displays an image warped with 2 different meshes +- :ref:`Interactive Warp Mesh` - Interactively change the warp mesh + .. rubric:: Yolo - :ref:`RGB & Tiny YOLO` - Runs Tiny YOLO on RGB frames and displays detections on the frame diff --git a/examples/Warp/warp_mesh_interactive.py b/examples/Warp/warp_mesh_interactive.py new file mode 100644 index 000000000..6065dd951 --- /dev/null +++ b/examples/Warp/warp_mesh_interactive.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +import cv2 +import depthai as dai +import numpy as np +import argparse +import re +import sys +from random import randint + +parser = argparse.ArgumentParser() +parser.add_argument("-m", "--mesh_dims", type=str, default="4x4", help="mesh dimensions widthxheight (default=%(default)s)") +parser.add_argument("-r", "--resolution", type=str, default="512x512", help="preview resolution (default=%(default)s)") +parser.add_argument("-rnd", "--random", action="store_true", help="Generate random initial mesh") +args = parser.parse_args() + +# mesh dimensions +match = re.search(r'.*?(\d+)x(\d+).*', args.mesh_dims) +if not match: + raise Exception(f"Mesh dimensions format incorrect '{args.resolution}'!") +mesh_w = int(match.group(1)) +mesh_h = int(match.group(2)) + +# Preview resolution +match = re.search(r'.*?(\d+)x(\d+).*', args.resolution) +if not match: + raise Exception(f"Resolution format incorrect '{args.resolution}'!") +preview_w = int(match.group(1)) +preview_h = int(match.group(2)) +if preview_w % 16 != 0: + raise Exception(f"Preview width must be a multiple of 16!") + +# Create an initial mesh (optionally random) of dimension mesh_w x mesh_h +first_point_x = int(preview_w / 10) +between_points_x = int(4 * preview_w / (5 * (mesh_w - 1))) +first_point_y = int(preview_h / 10) +between_points_y = int(4 * preview_h / (5 * (mesh_h - 1))) +if args.random: + max_rnd_x = int(between_points_x / 4) + max_rnd_y = int(between_points_y / 4) +mesh = [] +for i in range(mesh_h): + for j in range(mesh_w): + x = first_point_x + j * between_points_x + y = first_point_y + i * between_points_y + if args.random: + rnd_x = randint(-max_rnd_x, max_rnd_x) + if x + rnd_x > 0 and x + rnd_x < preview_w: + x += rnd_x + rnd_y = randint(-max_rnd_y, max_rnd_y) + if y + rnd_y > 0 and y + rnd_y < preview_h: + y += rnd_y + mesh.append((x, y)) + +def create_pipeline(mesh): + print(mesh) + # Create pipeline + pipeline = dai.Pipeline() + + camRgb = pipeline.create(dai.node.ColorCamera) + camRgb.setPreviewSize(preview_w, preview_h) + camRgb.setInterleaved(False) + width = camRgb.getPreviewWidth() + height = camRgb.getPreviewHeight() + + # Output source + xout_source = pipeline.create(dai.node.XLinkOut) + xout_source.setStreamName('source') + camRgb.preview.link(xout_source.input) + # Warp source frame + warp = pipeline.create(dai.node.Warp) + warp.setWarpMesh(mesh, mesh_w, mesh_h) + warp.setOutputSize(width, height) + warp.setMaxOutputFrameSize(width * height * 3) + camRgb.preview.link(warp.inputImage) + + warp.setHwIds([1]) + warp.setInterpolation(dai.node.Warp.Properties.Interpolation.BYPASS) + # Output warped + xout_warped = pipeline.create(dai.node.XLinkOut) + xout_warped.setStreamName('warped') + warp.out.link(xout_warped.input) + return pipeline + +point_selected = None + +def mouse_callback(event, x, y, flags, param): + global mesh, point_selected, mesh_changed + if event == cv2.EVENT_LBUTTONDOWN: + if point_selected is None: + # Which point is selected ? + min_dist = 100 + + for i in range(len(mesh)): + dist = np.linalg.norm((x - mesh[i][0], y - mesh[i][1])) + if dist < 20 and dist < min_dist: + min_dist = dist + point_selected = i + if point_selected is not None: + mesh[point_selected] = (x, y) + mesh_changed = True + + elif event == cv2.EVENT_LBUTTONUP: + point_selected = None + elif event == cv2.EVENT_MOUSEMOVE: + if point_selected is not None: + mesh[point_selected] = (x, y) + mesh_changed = True + + +cv2.namedWindow("Source") +cv2.setMouseCallback("Source", mouse_callback) + +running = True + +print("Use your mouse to modify the mesh by clicking/moving points of the mesh in the Source window") +print("Then press 'r' key to restart the device/pipeline") +while running: + pipeline = create_pipeline(mesh) + # Connect to device and start pipeline + with dai.Device(pipeline) as device: + print("Starting device") + # Output queue will be used to get the rgb frames from the output defined above + q_source = device.getOutputQueue(name="source", maxSize=4, blocking=False) + q_warped = device.getOutputQueue(name="warped", maxSize=4, blocking=False) + + restart_device = False + mesh_changed = False + while not restart_device: + in0 = q_source.get() + if in0 is not None: + source = in0.getCvFrame() + color = (0, 0,255) if mesh_changed else (0,255,0) + for i in range(len(mesh)): + cv2.circle(source, (mesh[i][0], mesh[i][1]), 4, color, -1) + if i % mesh_w != mesh_w -1: + cv2.line(source, (mesh[i][0], mesh[i][1]), (mesh[i+1][0], mesh[i+1][1]), color, 2) + if i + mesh_w < len(mesh): + cv2.line(source, (mesh[i][0], mesh[i][1]), (mesh[i+mesh_w][0], mesh[i+mesh_w][1]), color, 2) + cv2.imshow("Source", source) + + in1 = q_warped.get() + if in1 is not None: + cv2.imshow("Warped", in1.getCvFrame()) + + key = cv2.waitKey(1) + if key == ord('r'): # Restart the device if mesh has changed + if mesh_changed: + print("Restart requested...") + mesh_changed = False + restart_device = True + elif key == 27 or key == ord('q'): # Exit + running = False + break \ No newline at end of file From 589f44f892a89e0eb66ce47bc5209a8095c1e531 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 25 Jan 2023 17:55:11 +0200 Subject: [PATCH 117/385] Added credits --- docs/source/samples/Warp/warp_mesh_interactive.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/samples/Warp/warp_mesh_interactive.rst b/docs/source/samples/Warp/warp_mesh_interactive.rst index f6ef1399a..d72e0697d 100644 --- a/docs/source/samples/Warp/warp_mesh_interactive.rst +++ b/docs/source/samples/Warp/warp_mesh_interactive.rst @@ -10,6 +10,8 @@ User-defined arguments: - ``--resolution`` - Resolution of the input image (default: ``512x512``). Width must be divisible by 16. - ``--random`` - To generate random mesh points (disabled by default). +Originally developed by `geaxgx `__. + Setup ##### From f7627d6d1dd1aeb4c2935a28f2743222354d6cb5 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Thu, 26 Jan 2023 00:15:57 +0200 Subject: [PATCH 118/385] FW: fix crash with ColorCamera at high resolution and certain ISP scaling --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index fb873489d..07a811ffd 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit fb873489dba2185f74b4bb95e0ae1b163a386720 +Subproject commit 07a811ffdf70696f7ade47c291c98d951e16d4be From 73931f04b78ec34ccc559cfc837e1757eb9318b3 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Thu, 26 Jan 2023 00:22:26 +0200 Subject: [PATCH 119/385] FW: fix crash with ColorCamera at high resolution and certain ISP scaling --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index b4f49e423..b9b32ceb6 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit b4f49e423b25f95e72a76223a4375d2e3aaf7162 +Subproject commit b9b32ceb626f0f93dcbdb5725a963830df5de7f7 From 94b2a171ab2de0660c08d4adbd806f185506c819 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Thu, 26 Jan 2023 15:01:20 +0200 Subject: [PATCH 120/385] Added IMU docs max freq --- docs/source/components/nodes/imu.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/source/components/nodes/imu.rst b/docs/source/components/nodes/imu.rst index 70b89612d..5e22d9fde 100644 --- a/docs/source/components/nodes/imu.rst +++ b/docs/source/components/nodes/imu.rst @@ -50,6 +50,23 @@ Limitations - For BNO086, gyroscope frequency above 400Hz can produce some jitter from time to time due to sensor HW limitation. - **Maximum frequencies**: 500 Hz raw accelerometer, 1000 Hz raw gyroscope values individually, and 500Hz combined (synced) output. You can obtain the combined synced 500Hz output with :code:`imu.enableIMUSensor([dai.IMUSensor.ACCELEROMETER_RAW, dai.IMUSensor.GYROSCOPE_RAW], 500)`. +IMU sensor frequencies +###################### + +Below are the discrete **stable frequencies** available for each (raw) IMU sensor. Maximum IMU frequencies are higher, eg. +for BNO086, maximum frequency for gyroscope is 1000Hz, but up to 400Hz is stable with depthai. + +**BNO086:** + +- Accelerometer: 100Hz, 200Hz, 400Hz +- Gyroscope: 125Hz, 250Hz, 400Hz +- Magnetometer: 100Hz + +**BMI270:** + +- Accelerometer: 25Hz, 50Hz, 100Hz, 200Hz, 400Hz +- Gyroscope: 25Hz, 50Hz, 100Hz, 200Hz, 400Hz + Usage ##### From 35c0a8ce660aa7ea87e390b883422f2fcc9b44ec Mon Sep 17 00:00:00 2001 From: Erol444 Date: Thu, 26 Jan 2023 15:40:13 +0200 Subject: [PATCH 121/385] Driver limitation --- docs/source/components/nodes/imu.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/components/nodes/imu.rst b/docs/source/components/nodes/imu.rst index 5e22d9fde..75aaa89ac 100644 --- a/docs/source/components/nodes/imu.rst +++ b/docs/source/components/nodes/imu.rst @@ -53,8 +53,8 @@ Limitations IMU sensor frequencies ###################### -Below are the discrete **stable frequencies** available for each (raw) IMU sensor. Maximum IMU frequencies are higher, eg. -for BNO086, maximum frequency for gyroscope is 1000Hz, but up to 400Hz is stable with depthai. +Below are the discrete **stable frequencies** available for each (raw) IMU sensor. Some maximum IMU frequencies are higher, eg. +for BNO086, maximum frequency for gyroscope is 1000Hz, but up to 400Hz is stable (due to driver limitation). **BNO086:** From 82499942f349e0b3a5feec1a7c8b675afd9c1def Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 26 Jan 2023 18:02:54 +0200 Subject: [PATCH 122/385] Added FrameEvent bindings - fixed docs building process --- src/pipeline/CommonBindings.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/pipeline/CommonBindings.cpp b/src/pipeline/CommonBindings.cpp index 2072555d8..967721ed8 100644 --- a/src/pipeline/CommonBindings.cpp +++ b/src/pipeline/CommonBindings.cpp @@ -20,6 +20,7 @@ #include "depthai-shared/common/RotatedRect.hpp" #include "depthai-shared/common/Rect.hpp" #include "depthai-shared/common/Colormap.hpp" +#include "depthai-shared/common/FrameEvent.hpp" // depthai #include "depthai/common/CameraFeatures.hpp" @@ -54,6 +55,7 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ py::class_ rect(m, "Rect", DOC(dai, Rect)); py::enum_ cameraExposureOffset(m, "CameraExposureOffset"); py::enum_ colormap(m, "Colormap", DOC(dai, Colormap)); + py::enum_ frameEvent(m, "FrameEvent", DOC(dai, FrameEvent)); /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// @@ -328,4 +330,10 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ // .value("DEEPGREEN", Colormap::DEEPGREEN) ; + frameEvent + .value("NONE", FrameEvent::NONE) + .value("READOUT_START", FrameEvent::READOUT_START) + .value("READOUT_END", FrameEvent::READOUT_END) + ; + } From 56cd611155fdfae3909571d91d2e2572f636f363 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 26 Jan 2023 18:02:54 +0200 Subject: [PATCH 123/385] Added CI to release to Artifactory --- .github/workflows/main.yml | 22 +++++++++++----------- ci/upload-artifactory-release.sh | 9 +++++++++ 2 files changed, 20 insertions(+), 11 deletions(-) create mode 100755 ci/upload-artifactory-release.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e6a02d277..2d0986f4a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -465,7 +465,7 @@ jobs: release: if: startsWith(github.ref, 'refs/tags/v') - needs: [pytest, build-linux-armhf, build-windows-x86_64, build-macos-x86_64, build-macos-arm64, build-linux-x86_64, build-linux-arm64] + needs: [deploy-mock, pytest, build-linux-armhf, build-windows-x86_64, build-macos-x86_64, build-macos-arm64, build-linux-x86_64, build-linux-arm64] runs-on: ubuntu-latest steps: @@ -473,23 +473,17 @@ jobs: with: submodules: 'recursive' - # Get tag version - # TODO(themarpe) - Node12, has to be updated - - name: Get tag version - id: tag - uses: battila7/get-version-action@v2 - - uses: actions/setup-python@v4 with: python-version: '3.8' - name: Check if version matches - run: python3.8 -c 'import find_version as v; exit(0) if "${{ steps.tag.outputs.version-without-v }}" == v.get_package_version() else exit(1)' + run: python3.8 -c 'import find_version as v; exit(0) if "${{ github.ref_name }}" == f"v{v.get_package_version()}" else exit(1)' # Create GitHub release - uses: actions/create-release@master id: createRelease - name: Create ${{ steps.tag.outputs.version-without-v }} depthai-core release + name: Create ${{ github.ref_name }} depthai-python release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -504,8 +498,8 @@ jobs: draft: true - # Deploy to PyPi. Only when a commit is tagged - deploy-pypi: + # Deploy to PyPi and Artifactory. Only when a commit is tagged + deploy: if: startsWith(github.ref, 'refs/tags/v') needs: [release] runs-on: ubuntu-latest @@ -525,6 +519,12 @@ jobs: PYPI_SERVER: ${{ secrets.PYPI_SERVER }} PYPI_USER: ${{ secrets.PYPI_USER }} PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + - name: Run deploy to Artifactory + run: bash ./ci/upload-artifactory-release.sh + env: + ARTIFACTORY_URL: ${{ secrets.ARTIFACTORY_URL }} + ARTIFACTORY_USER: ${{ secrets.ARTIFACTORY_USER }} + ARTIFACTORY_PASS: ${{ secrets.ARTIFACTORY_PASS }} notify_robothub: if: startsWith(github.ref, 'refs/tags/v') diff --git a/ci/upload-artifactory-release.sh b/ci/upload-artifactory-release.sh new file mode 100755 index 000000000..6669bcace --- /dev/null +++ b/ci/upload-artifactory-release.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +curl -fL https://getcli.jfrog.io | sh + +cd wheelhouse/audited/ || exit 1 +export PATH_PREFIX=luxonis-python-release-local/depthai + +../../jfrog config add --artifactory-url=$ARTIFACTORY_URL --user=$ARTIFACTORY_USER --password=$ARTIFACTORY_PASS +../../jfrog rt u "*" "$PATH_PREFIX/" From eefa7abf444c03d149c1a33e70a46c083122b5a0 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 26 Jan 2023 18:02:54 +0200 Subject: [PATCH 124/385] Added CMake to pyproject.toml for builds from source --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c6cd8b7f8..6e22a4b62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,2 @@ [build-system] -requires = ["setuptools", "wheel", "mypy"] # Must be preinstalled "cmake>=3.2.0" +requires = ["setuptools", "wheel", "mypy", "cmake==3.25"] \ No newline at end of file From ea59d169a82469ed655cd8762448879bedf6bce3 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 26 Jan 2023 18:09:41 +0200 Subject: [PATCH 125/385] Bump version to 2.20.2.0 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 07a811ffd..7a3ac317d 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 07a811ffdf70696f7ade47c291c98d951e16d4be +Subproject commit 7a3ac317d0f211cc33f8224de8da325b2b3bf55c From 01c31f9a9c6ba97d6a17d0313a7e80cbaf3e0650 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 26 Jan 2023 18:10:58 +0200 Subject: [PATCH 126/385] Fixup main.yml CI --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2d0986f4a..d002b5c4c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -465,7 +465,7 @@ jobs: release: if: startsWith(github.ref, 'refs/tags/v') - needs: [deploy-mock, pytest, build-linux-armhf, build-windows-x86_64, build-macos-x86_64, build-macos-arm64, build-linux-x86_64, build-linux-arm64] + needs: [pytest, build-linux-armhf, build-windows-x86_64, build-macos-x86_64, build-macos-arm64, build-linux-x86_64, build-linux-arm64] runs-on: ubuntu-latest steps: From a4fa240c066cefcc33d7176ddd1db0135b4986a4 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 26 Jan 2023 19:00:23 +0200 Subject: [PATCH 127/385] Modified dependencies CI --- .github/workflows/test-install-dependencies.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-install-dependencies.yml b/.github/workflows/test-install-dependencies.yml index 851af5558..bc08b2c62 100644 --- a/.github/workflows/test-install-dependencies.yml +++ b/.github/workflows/test-install-dependencies.yml @@ -58,7 +58,7 @@ run: Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) - name: Install dependencies shell: pwsh - run: choco install cmake git python pycharm-community -y + run: choco install cmake git python --version 3.10 -y - name: Install example requrirements run: | python examples/install_requirements.py From d9f6b8b3aac5d4d80a970a5d00ae839c9cd1bf9c Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 26 Jan 2023 22:46:46 +0200 Subject: [PATCH 128/385] Added an ColorCamera isp scale example, and modified test workflow --- depthai-core | 2 +- examples/CMakeLists.txt | 1 + examples/ColorCamera/rgb_isp_scale.py | 40 +++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100755 examples/ColorCamera/rgb_isp_scale.py diff --git a/depthai-core b/depthai-core index 7a3ac317d..1500889e4 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 7a3ac317d0f211cc33f8224de8da325b2b3bf55c +Subproject commit 1500889e483acb0e12642591f3483fac00adb261 diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ea38f5bc1..511a59cce 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -109,6 +109,7 @@ add_python_example(rgb_camera_control ColorCamera/rgb_camera_control.py) add_python_example(rgb_preview ColorCamera/rgb_preview.py) add_python_example(rgb_scene ColorCamera/rgb_scene.py) add_python_example(rgb_video ColorCamera/rgb_video.py) +add_python_example(rgb_isp_scale ColorCamera/rgb_isp_scale.py) ## EdgeDetector add_python_example(edge_detector EdgeDetector/edge_detector.py) diff --git a/examples/ColorCamera/rgb_isp_scale.py b/examples/ColorCamera/rgb_isp_scale.py new file mode 100755 index 000000000..78f5370a8 --- /dev/null +++ b/examples/ColorCamera/rgb_isp_scale.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +import cv2 +import depthai as dai + +# Create pipeline +pipeline = dai.Pipeline() + +# Define source and output +camRgb = pipeline.create(dai.node.ColorCamera) +xoutVideo = pipeline.create(dai.node.XLinkOut) + +xoutVideo.setStreamName("video") + +# Properties +camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) +camRgb.setIspScale(1, 2) +camRgb.setVideoSize(1920, 1080) + +xoutVideo.input.setBlocking(False) +xoutVideo.input.setQueueSize(1) + +# Linking +camRgb.video.link(xoutVideo.input) + +# Connect to device and start pipeline +with dai.Device(pipeline) as device: + + video = device.getOutputQueue(name="video", maxSize=1, blocking=False) + + while True: + videoIn = video.get() + + # Get BGR frame from NV12 encoded video frame to show with opencv + # Visualizing the frame on slower hosts might have overhead + cv2.imshow("video", videoIn.getCvFrame()) + + if cv2.waitKey(1) == ord('q'): + break From 3be64e03f488359bbb1ba339ef6ef6e4d54f998d Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 27 Jan 2023 00:13:38 +0200 Subject: [PATCH 129/385] Implement multi stereo support --- depthai-core | 2 +- examples/StereoDepth/depth_preview_lr.py | 98 ++++++++++++++++++------ 2 files changed, 76 insertions(+), 24 deletions(-) diff --git a/depthai-core b/depthai-core index b4f49e423..4b2e9da8d 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit b4f49e423b25f95e72a76223a4375d2e3aaf7162 +Subproject commit 4b2e9da8d97029403b9a4272a28d3d07f35d5a43 diff --git a/examples/StereoDepth/depth_preview_lr.py b/examples/StereoDepth/depth_preview_lr.py index 7d62b0302..5cc37c063 100755 --- a/examples/StereoDepth/depth_preview_lr.py +++ b/examples/StereoDepth/depth_preview_lr.py @@ -11,46 +11,99 @@ # Better handling for occlusions: lr_check = True +enableRectified = False + # Create pipeline pipeline = dai.Pipeline() # Define sources and outputs left = pipeline.create(dai.node.ColorCamera) +center = pipeline.create(dai.node.ColorCamera) right = pipeline.create(dai.node.ColorCamera) -depth = pipeline.create(dai.node.StereoDepth) -xout = pipeline.create(dai.node.XLinkOut) -xoutl = pipeline.create(dai.node.XLinkOut) -xoutr = pipeline.create(dai.node.XLinkOut) +LC_depth = pipeline.create(dai.node.StereoDepth) +LR_depth = pipeline.create(dai.node.StereoDepth) +CR_depth = pipeline.create(dai.node.StereoDepth) + +xout_LC = pipeline.create(dai.node.XLinkOut) +xout_LR = pipeline.create(dai.node.XLinkOut) +xout_CR = pipeline.create(dai.node.XLinkOut) + +xout_LC.setStreamName("disparity_LC") +if enableRectified: + xoutl_LC = pipeline.create(dai.node.XLinkOut) + xoutr_LC = pipeline.create(dai.node.XLinkOut) + xoutl_LC.setStreamName("rectifiedLeft_LC") + xoutr_LC.setStreamName("rectifiedRight_LC") -xout.setStreamName("disparity") -xoutl.setStreamName("rectifiedLeft") -xoutr.setStreamName("rectifiedRight") +xout_LR.setStreamName("disparity_LR") +if enableRectified: + xoutl_LR = pipeline.create(dai.node.XLinkOut) + xoutr_LR = pipeline.create(dai.node.XLinkOut) + xoutl_LR.setStreamName("rectifiedLeft_LR") + xoutr_LR.setStreamName("rectifiedRight_LR") + +xout_CR.setStreamName("disparity_CR") +if enableRectified: + xoutl_CR = pipeline.create(dai.node.XLinkOut) + xoutr_CR = pipeline.create(dai.node.XLinkOut) + xoutl_CR.setStreamName("rectifiedLeft_CR") + xoutr_CR.setStreamName("rectifiedRight_CR") # Properties left.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1200_P) left.setBoardSocket(dai.CameraBoardSocket.LEFT) +left.setIspScale(2, 3) + +center.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1200_P) +center.setBoardSocket(dai.CameraBoardSocket.CENTER) +center.setIspScale(2, 3) + right.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1200_P) right.setBoardSocket(dai.CameraBoardSocket.RIGHT) right.setIspScale(2, 3) -left.setIspScale(2, 3) +LC_depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) +LC_depth.initialConfig.setMedianFilter(dai.MedianFilter.MEDIAN_OFF) +LC_depth.setLeftRightCheck(lr_check) +LC_depth.setExtendedDisparity(extended_disparity) +LC_depth.setSubpixel(subpixel) -# Create a node that will produce the depth map (using disparity output as it's easier to visualize depth this way) -depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) -# Options: MEDIAN_OFF, KERNEL_3x3, KERNEL_5x5, KERNEL_7x7 (default) -depth.initialConfig.setMedianFilter(dai.MedianFilter.KERNEL_7x7) -depth.setInputResolution(1280, 800) -depth.setLeftRightCheck(lr_check) -depth.setExtendedDisparity(extended_disparity) -depth.setSubpixel(subpixel) -depth.setInputResolution(1280, 800) +LR_depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) +LR_depth.initialConfig.setMedianFilter(dai.MedianFilter.MEDIAN_OFF) +LR_depth.setLeftRightCheck(lr_check) +LR_depth.setExtendedDisparity(extended_disparity) +LR_depth.setSubpixel(subpixel) + +CR_depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) +CR_depth.initialConfig.setMedianFilter(dai.MedianFilter.MEDIAN_OFF) +CR_depth.setLeftRightCheck(lr_check) +CR_depth.setExtendedDisparity(extended_disparity) +CR_depth.setSubpixel(subpixel) # Linking -left.isp.link(depth.left) -right.isp.link(depth.right) -depth.disparity.link(xout.input) -depth.rectifiedLeft.link(xoutl.input) -depth.rectifiedRight.link(xoutr.input) +# LC +left.isp.link(LC_depth.left) +center.isp.link(LC_depth.right) +LC_depth.disparity.link(xout_LC.input) +if enableRectified: + LC_depth.rectifiedLeft.link(xoutl_LC.input) + LC_depth.rectifiedRight.link(xoutr_LC.input) +# LR +left.isp.link(LR_depth.left) +right.isp.link(LR_depth.right) +LR_depth.disparity.link(xout_LR.input) +if enableRectified: + LR_depth.rectifiedLeft.link(xoutl_LR.input) + LR_depth.rectifiedRight.link(xoutr_LR.input) +# CR +center.isp.link(CR_depth.left) +right.isp.link(CR_depth.right) +CR_depth.disparity.link(xout_CR.input) +if enableRectified: + CR_depth.rectifiedLeft.link(xoutl_CR.input) + CR_depth.rectifiedRight.link(xoutr_CR.input) + +maxDisp = LC_depth.initialConfig.getMaxDisparity() # Connect to device and start pipeline with dai.Device(pipeline) as device: @@ -62,7 +115,6 @@ if type(message) == dai.ImgFrame: frame = message.getCvFrame() if 'disparity' in q: - maxDisp = depth.initialConfig.getMaxDisparity() disp = (frame * (255.0 / maxDisp)).astype(np.uint8) disp = cv2.applyColorMap(disp, cv2.COLORMAP_JET) cv2.imshow(q, disp) From fc4ae104d4e2ea69e8b2862646bcab777423f7ce Mon Sep 17 00:00:00 2001 From: Petr Novota <99871801+PetrNovota@users.noreply.github.com> Date: Fri, 27 Jan 2023 00:12:42 +0100 Subject: [PATCH 130/385] the read -p $'text\n' -r message syntaxremoved --- docs/source/_static/install_depthai.sh | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh index af4ed3dca..1505d8861 100755 --- a/docs/source/_static/install_depthai.sh +++ b/docs/source/_static/install_depthai.sh @@ -12,7 +12,8 @@ trap 'RET=$? ; echo -e >&2 "\n\x1b[31mFailed installing dependencies. Could be a while [ "$path_correct" = "false" ] do echo "" - read -p $'ENTER absolute installation path for depthai or leave empty and default path: $HOME will be used.\n' -r install_path + echo 'ENTER absolute installation path for depthai or leave empty and default path: $HOME will be used.' + read install_path echo "" if [ "$install_path" = "" ]; then @@ -44,7 +45,7 @@ python_version_number="" if [[ "$python_version" != 'Python'* ]]; then python_version="" fi -echo $'\n' +echo "" # check default python version, offer it to the user or get another one while [ "$python_chosen" = "false" ] @@ -52,7 +53,8 @@ do if [[ "$python_version" == "" ]]; then echo "No python version found." echo "Input path for python binary, version 3.8 or higher, or leave empty and python 3.10 will be installed for you." - read -p $'Press any key to continue\n' -r python_binary_path + echo "Press any key to continue" + read python_binary_path # python not found and user wants to install python 3.10 if [ "$python_binary_path" = "" ]; then install_python="true" @@ -65,14 +67,16 @@ do echo "Python version: $python_version found." if [ "$nr_1" -gt 2 ] && [ "$nr_2" -gt 7 ]; then # first two digits of python version greater then 3.7 -> python version 3.8 or greater is allowed. echo "If you want to use it for installation, press ANY key, otherwise input path to python binary." - read -p $'Press any key to continue\n' -r python_binary_path + echo "Press any key to continue" + read python_binary_path # user wants to use already installed python whose version is high enough if [ "$python_binary_path" = "" ]; then python_chosen="true" fi else echo "This python version is not supported by depthai. Enter path to python binary version et least 3.8, or leave empty and python 3.10 will be installed automatically." - read -p $'Press any key to continue\n' -r python_binary_path + echo "Press any key to continue" + read python_binary_path # python version is too low and user wants to install python 3.10 if [ "$python_binary_path" = "" ]; then install_python="true" @@ -229,6 +233,7 @@ fi echo -e '\n\n:::::::::::::::: INSTALATION COMPLETE ::::::::::::::::\n' echo -e '\nTo run demo app write in terminal.' -read -rsp $'Press ANY KEY to finish and run the demo app...\n' -n1 key +echo "Press ANY KEY to finish and run the demo app..." +read -n1 key echo "STARTING DEMO APP." python "$DEPTHAI_DIR/launcher/launcher.py" -r "$DEPTHAI_DIR" From 44bb95409fc05e4cea3f7d374628dd6a2717fa72 Mon Sep 17 00:00:00 2001 From: Petr Novota <99871801+PetrNovota@users.noreply.github.com> Date: Fri, 27 Jan 2023 00:59:06 +0100 Subject: [PATCH 131/385] installation script fix --- docs/source/_static/install_depthai.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh index 1505d8861..6d2849f84 100755 --- a/docs/source/_static/install_depthai.sh +++ b/docs/source/_static/install_depthai.sh @@ -13,7 +13,7 @@ while [ "$path_correct" = "false" ] do echo "" echo 'ENTER absolute installation path for depthai or leave empty and default path: $HOME will be used.' - read install_path + read install_path < /dev/tty echo "" if [ "$install_path" = "" ]; then @@ -54,7 +54,7 @@ do echo "No python version found." echo "Input path for python binary, version 3.8 or higher, or leave empty and python 3.10 will be installed for you." echo "Press any key to continue" - read python_binary_path + read python_binary_path < /dev/tty # python not found and user wants to install python 3.10 if [ "$python_binary_path" = "" ]; then install_python="true" @@ -68,7 +68,7 @@ do if [ "$nr_1" -gt 2 ] && [ "$nr_2" -gt 7 ]; then # first two digits of python version greater then 3.7 -> python version 3.8 or greater is allowed. echo "If you want to use it for installation, press ANY key, otherwise input path to python binary." echo "Press any key to continue" - read python_binary_path + read python_binary_path < /dev/tty # user wants to use already installed python whose version is high enough if [ "$python_binary_path" = "" ]; then python_chosen="true" @@ -76,7 +76,7 @@ do else echo "This python version is not supported by depthai. Enter path to python binary version et least 3.8, or leave empty and python 3.10 will be installed automatically." echo "Press any key to continue" - read python_binary_path + read python_binary_path < /dev/tty # python version is too low and user wants to install python 3.10 if [ "$python_binary_path" = "" ]; then install_python="true" @@ -234,6 +234,6 @@ fi echo -e '\n\n:::::::::::::::: INSTALATION COMPLETE ::::::::::::::::\n' echo -e '\nTo run demo app write in terminal.' echo "Press ANY KEY to finish and run the demo app..." -read -n1 key +read -n1 key < /dev/tty echo "STARTING DEMO APP." python "$DEPTHAI_DIR/launcher/launcher.py" -r "$DEPTHAI_DIR" From 65cdbdbf96b7d5269a0e4ddb3197f9608009c6f2 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Fri, 27 Jan 2023 13:50:18 +0200 Subject: [PATCH 132/385] [Device Manager] Moved updating bootloader to Danger Zone --- utilities/device_manager.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 728df5b5d..ea2cf44e9 100755 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -185,6 +185,8 @@ def wait(self) -> dai.DeviceInfo: return deviceSelected def flashBootloader(bl: dai.DeviceBootloader, device: dai.DeviceInfo, type: dai.DeviceBootloader.Type): + userBlWarningMessage = """Updating bootloader can soft-brick a device. +Proceed with caution""" factoryBlWarningMessage = """Factory Bootloader type or version doesn't support User Bootloader flashing. Factory bootloader will be updated instead. Proceed with caution @@ -192,10 +194,13 @@ def flashBootloader(bl: dai.DeviceBootloader, device: dai.DeviceInfo, type: dai. try: if bl.isUserBootloaderSupported(): - pr = Progress('Flashing...') - progress = lambda p : pr.update(p) - bl.flashUserBootloader(progress) - pr.finish("Flashed newest User Bootloader version.") + if AreYouSure(text=userBlWarningMessage).wait(): + pr = Progress('Flashing...') + progress = lambda p : pr.update(p) + bl.flashUserBootloader(progress) + pr.finish("Flashed newest User Bootloader version.") + else: + return False elif AreYouSure(text=factoryBlWarningMessage).wait(): bl.close() pr = Progress('Connecting...') @@ -400,12 +405,6 @@ def deviceStateTxt(state: dai.XLinkDeviceState) -> str: sg.Text("-version-", key="version", size=(30, 1)), sg.VSeparator(), sg.Text("-version-", key="commit", size=(31, 1)) - ], - [sg.HSeparator()], - [ - sg.Text("", size=(7, 2)), - sg.Button("Update Bootloader", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, - button_color='#FFA500'), ] ] @@ -510,12 +509,17 @@ def deviceStateTxt(state: dai.XLinkDeviceState) -> str: ], [sg.HSeparator()], [ + sg.Button("Update Bootloader", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, + button_color='#FFA500'), sg.Button("Flash Factory Bootloader", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFA500', key='flashFactoryBootloader'), + ], + [sg.HSeparator()], + [ sg.Button("Factory reset", size=(17, 2), font=('Arial', 10, 'bold'), disabled=True, button_color='#FFA500'), sg.Button("Boot into USB\nRecovery mode", size=(20, 2), font=('Arial', 10, 'bold'), disabled=True, key='recoveryMode', button_color='#FFA500') - ], + ] ] @@ -611,6 +615,8 @@ def run(self) -> None: if self.bl is None: continue self.getConfigs() self.unlockConfig() + + # Danger elif event == "Update Bootloader": # Use current type if flashBootloader(self.bl, self.device, self.bl.getType()): @@ -620,8 +626,6 @@ def run(self) -> None: self.getDevices() else: print("Flashing bootloader canceled.") - - # Danger elif event == "flashFactoryBootloader": sel = SelectBootloader(['AUTO', 'USB', 'NETWORK'], "Select bootloader type to flash.") ok, type = sel.wait() From a821db9512df3dffaf99773e885f892e7fd9f11d Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 27 Jan 2023 22:38:46 +0200 Subject: [PATCH 133/385] Implement brightness filter --- depthai-core | 2 +- src/pipeline/datatype/StereoDepthConfigBindings.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 75f7607b9..5fd6e2e4e 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 75f7607b96c5f2e96e4a58aaa6aa802738dfb83d +Subproject commit 5fd6e2e4e68ed644d797f7cd2e3f4fedc70cd642 diff --git a/src/pipeline/datatype/StereoDepthConfigBindings.cpp b/src/pipeline/datatype/StereoDepthConfigBindings.cpp index d95fa9c37..5ec2ef4e5 100644 --- a/src/pipeline/datatype/StereoDepthConfigBindings.cpp +++ b/src/pipeline/datatype/StereoDepthConfigBindings.cpp @@ -26,6 +26,7 @@ void bind_stereodepthconfig(pybind11::module& m, void* pCallstack){ py::class_ temporalFilter(postProcessing, "TemporalFilter", DOC(dai, RawStereoDepthConfig, PostProcessing, TemporalFilter)); py::enum_ persistencyMode(temporalFilter, "PersistencyMode", DOC(dai, RawStereoDepthConfig, PostProcessing, TemporalFilter, PersistencyMode)); py::class_ thresholdFilter(postProcessing, "ThresholdFilter", DOC(dai, RawStereoDepthConfig, PostProcessing, ThresholdFilter)); + py::class_ brightnessFilter(postProcessing, "BrightnessFilter", DOC(dai, RawStereoDepthConfig, PostProcessing, BrightnessFilter)); py::class_ speckleFilter(postProcessing, "SpeckleFilter", DOC(dai, RawStereoDepthConfig, PostProcessing, SpeckleFilter)); py::class_ decimationFilter(postProcessing, "DecimationFilter", DOC(dai, RawStereoDepthConfig, PostProcessing, DecimationFilter)); py::enum_ decimationMode(decimationFilter, "DecimationMode", DOC(dai, RawStereoDepthConfig, PostProcessing, DecimationFilter, DecimationMode)); @@ -124,6 +125,12 @@ void bind_stereodepthconfig(pybind11::module& m, void* pCallstack){ .def_readwrite("maxRange", &RawStereoDepthConfig::PostProcessing::ThresholdFilter::maxRange, DOC(dai, RawStereoDepthConfig, PostProcessing, ThresholdFilter, maxRange)) ; + brightnessFilter + .def(py::init<>()) + .def_readwrite("minBrightness", &RawStereoDepthConfig::PostProcessing::BrightnessFilter::minBrightness, DOC(dai, RawStereoDepthConfig, PostProcessing, BrightnessFilter, minBrightness)) + .def_readwrite("maxBrightness", &RawStereoDepthConfig::PostProcessing::BrightnessFilter::maxBrightness, DOC(dai, RawStereoDepthConfig, PostProcessing, BrightnessFilter, maxBrightness)) + ; + speckleFilter .def(py::init<>()) .def_readwrite("enable", &RawStereoDepthConfig::PostProcessing::SpeckleFilter::enable, DOC(dai, RawStereoDepthConfig, PostProcessing, SpeckleFilter, enable)) @@ -149,6 +156,7 @@ void bind_stereodepthconfig(pybind11::module& m, void* pCallstack){ .def_readwrite("spatialFilter", &RawStereoDepthConfig::PostProcessing::spatialFilter, DOC(dai, RawStereoDepthConfig, PostProcessing, spatialFilter)) .def_readwrite("temporalFilter", &RawStereoDepthConfig::PostProcessing::temporalFilter, DOC(dai, RawStereoDepthConfig, PostProcessing, temporalFilter)) .def_readwrite("thresholdFilter", &RawStereoDepthConfig::PostProcessing::thresholdFilter, DOC(dai, RawStereoDepthConfig, PostProcessing, thresholdFilter)) + .def_readwrite("brightnessFilter", &RawStereoDepthConfig::PostProcessing::brightnessFilter, DOC(dai, RawStereoDepthConfig, PostProcessing, brightnessFilter)) .def_readwrite("speckleFilter", &RawStereoDepthConfig::PostProcessing::speckleFilter, DOC(dai, RawStereoDepthConfig, PostProcessing, speckleFilter)) .def_readwrite("decimationFilter", &RawStereoDepthConfig::PostProcessing::decimationFilter, DOC(dai, RawStereoDepthConfig, PostProcessing, decimationFilter)) ; From d6ee8e7c85bb1b73b1c79190ae6f43a7e092400c Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Fri, 27 Jan 2023 23:32:41 +0200 Subject: [PATCH 134/385] FW: fix OV9282 SW-sync on devices with OV9782 RGB camera --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 1500889e4..e73cf4cf5 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 1500889e483acb0e12642591f3483fac00adb261 +Subproject commit e73cf4cf5d06183cd291eac75f3693b0a330bfbf From e09e13703cd7309604f7585ba4f5cb51e220fef9 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 27 Jan 2023 18:39:01 +0200 Subject: [PATCH 135/385] Added IMUReport python bindings for getSequenceNum --- src/pipeline/datatype/IMUDataBindings.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pipeline/datatype/IMUDataBindings.cpp b/src/pipeline/datatype/IMUDataBindings.cpp index 91bcbc3d2..4aedeafe6 100644 --- a/src/pipeline/datatype/IMUDataBindings.cpp +++ b/src/pipeline/datatype/IMUDataBindings.cpp @@ -48,6 +48,7 @@ void bind_imudata(pybind11::module& m, void* pCallstack){ .def_readwrite("tsDevice", &IMUReport::tsDevice) .def("getTimestamp", &IMUReport::getTimestamp, DOC(dai, IMUReport, getTimestamp)) .def("getTimestampDevice", &IMUReport::getTimestampDevice, DOC(dai, IMUReport, getTimestampDevice)) + .def("getSequenceNum", &IMUReport::getSequenceNum, DOC(dai, IMUReport, getSequenceNum)) ; imuReportAccuracy From ab23c2817e0bd8ec86e9b3a4ffee6796f8e34822 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Sat, 28 Jan 2023 16:19:06 +0200 Subject: [PATCH 136/385] Update FW: Fix OOM due to too many SIPP pipelines --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index df25f9393..d17527faa 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit df25f9393c8dcba478e16d25463afa010e202bc6 +Subproject commit d17527faafde36afd639aff9922a8473f896c5df From 83219ef31dd43cc68a84f4a231592d973188c676 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 30 Jan 2023 15:29:29 +0200 Subject: [PATCH 137/385] FW: handle EEPROM `boardOptions` bit 7 for separate I2C on L/R --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 86956d34c..36089a87b 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 86956d34c83a4cde02cfa9d92ce30e5b19630379 +Subproject commit 36089a87bb5ef3d79fecf6deecf05c17a3e746af From adf13019e5d83833adb8acf2a733ac3ea8072262 Mon Sep 17 00:00:00 2001 From: Petr Novota <99871801+PetrNovota@users.noreply.github.com> Date: Mon, 30 Jan 2023 18:13:44 +0100 Subject: [PATCH 138/385] proper pip upgrade make sure the pip used in virtual venv is upgraded --- docs/source/_static/install_depthai.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh index af4ed3dca..38ff2424d 100755 --- a/docs/source/_static/install_depthai.sh +++ b/docs/source/_static/install_depthai.sh @@ -159,7 +159,7 @@ if [[ $(uname -s) == "Darwin" ]]; then "$python_executable" -m venv "$VENV_DIR" # activate environment source "$VENV_DIR/bin/activate" - pip install --upgrade pip + python -m pip install --upgrade pip # install launcher dependencies # only on mac silicon point PYTHONPATH to pyqt5 installation via homebrew, otherwise install pyqt5 with pip @@ -218,7 +218,7 @@ elif [[ $(uname -s) == "Linux" ]]; then "$python_executable" -m venv "$VENV_DIR" source "$VENV_DIR/bin/activate" - pip install --upgrade pip + python -m pip install --upgrade pip pip install packaging pip install pyqt5 From b33b3091dfc9868070a53d4eb8de0e238d5b6996 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Tue, 31 Jan 2023 00:06:47 +0200 Subject: [PATCH 139/385] FW: fix for IMX378/477/577 on sockets other than RGB --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index e73cf4cf5..88af9a219 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit e73cf4cf5d06183cd291eac75f3693b0a330bfbf +Subproject commit 88af9a219eb8435090daf8dcbb8c72e2bb29e292 From 05b52d07887e02e32ca71e215622ff6565adc029 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 1 Feb 2023 03:02:39 +0200 Subject: [PATCH 140/385] Update FW: support for stereo alignment to original left or right inputs; OOM fix for 4 stereo nodes with median filter --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 9eb556d5a..80727e4b5 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 9eb556d5a0ac17eb78f54149de207a4df6fab73e +Subproject commit 80727e4b5d1f8f4c6ce9d2c25f429aa6aba92a94 From f3d968aa24d8f8ca7f89f6fbafc50b8a85ea3fd7 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 1 Feb 2023 16:08:46 +0200 Subject: [PATCH 141/385] Update FW: parsing optimization and improvements for Yolo v6, v6r2, v8 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 856d47914..9cb42b6e5 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 856d47914b59ad18c6dd0b62704419f666326121 +Subproject commit 9cb42b6e5b25d11a75d1e662d346107b05d77c54 From 8584a3290f481dd530293cdc3d33c72d6b234195 Mon Sep 17 00:00:00 2001 From: Petr Novota <99871801+PetrNovota@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:20:08 +0100 Subject: [PATCH 142/385] Start interactive shell session when reading user input Allows user to navigate cursor in user input --- docs/source/_static/install_depthai.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh index 689706f9b..94556ce55 100755 --- a/docs/source/_static/install_depthai.sh +++ b/docs/source/_static/install_depthai.sh @@ -13,7 +13,7 @@ while [ "$path_correct" = "false" ] do echo "" echo 'ENTER absolute installation path for depthai or leave empty and default path: $HOME will be used.' - read install_path < /dev/tty + read -e install_path < /dev/tty echo "" if [ "$install_path" = "" ]; then @@ -54,7 +54,7 @@ do echo "No python version found." echo "Input path for python binary, version 3.8 or higher, or leave empty and python 3.10 will be installed for you." echo "Press any key to continue" - read python_binary_path < /dev/tty + read -e python_binary_path < /dev/tty # python not found and user wants to install python 3.10 if [ "$python_binary_path" = "" ]; then install_python="true" @@ -68,7 +68,7 @@ do if [ "$nr_1" -gt 2 ] && [ "$nr_2" -gt 7 ]; then # first two digits of python version greater then 3.7 -> python version 3.8 or greater is allowed. echo "If you want to use it for installation, press ANY key, otherwise input path to python binary." echo "Press any key to continue" - read python_binary_path < /dev/tty + read -e python_binary_path < /dev/tty # user wants to use already installed python whose version is high enough if [ "$python_binary_path" = "" ]; then python_chosen="true" @@ -76,7 +76,7 @@ do else echo "This python version is not supported by depthai. Enter path to python binary version et least 3.8, or leave empty and python 3.10 will be installed automatically." echo "Press any key to continue" - read python_binary_path < /dev/tty + read -e python_binary_path < /dev/tty # python version is too low and user wants to install python 3.10 if [ "$python_binary_path" = "" ]; then install_python="true" From 60d5688335748113c0d71218741b3c756ea23134 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Thu, 2 Feb 2023 14:54:26 +0200 Subject: [PATCH 143/385] Add option to swap left right cameras --- examples/StereoDepth/stereo_depth_video.py | 36 +++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/examples/StereoDepth/stereo_depth_video.py b/examples/StereoDepth/stereo_depth_video.py index 8fcc02fec..1f45dff20 100755 --- a/examples/StereoDepth/stereo_depth_video.py +++ b/examples/StereoDepth/stereo_depth_video.py @@ -69,6 +69,13 @@ action="store_true", help="Display depth frames", ) +parser.add_argument( + "-swlr", + "--swap_left_right", + default=False, + action="store_true", + help="Swap left right frames", +) args = parser.parse_args() resolutionMap = {"800": (1280, 800), "720": (1280, 720), "400": (640, 400)} @@ -175,9 +182,28 @@ def getDisparityFrame(frame): return disp +device = dai.Device() +calibData = device.readCalibration() print("Creating Stereo Depth pipeline") pipeline = dai.Pipeline() +if args.swap_left_right: + M_left, width_l, height_l = calibData.getDefaultIntrinsics(dai.CameraBoardSocket.LEFT) + M_right, width_r, height_r = calibData.getDefaultIntrinsics(dai.CameraBoardSocket.RIGHT) + d_l = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.LEFT)) + d_r = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.RIGHT)) + + rect_l = calibData.getStereoLeftRectificationRotation() + rect_r = calibData.getStereoRightRectificationRotation() + + calibData.setCameraIntrinsics(dai.CameraBoardSocket.LEFT, M_right, width_r, height_r) + calibData.setCameraIntrinsics(dai.CameraBoardSocket.RIGHT, M_left, width_l, height_l) + calibData.setDistortionCoefficients(dai.CameraBoardSocket.LEFT, d_r) + calibData.setDistortionCoefficients(dai.CameraBoardSocket.RIGHT, d_l) + calibData.setStereoLeft(dai.CameraBoardSocket.LEFT, rect_r) + calibData.setStereoRight(dai.CameraBoardSocket.RIGHT, rect_l) + pipeline.setCalibrationData(calibData) + camLeft = pipeline.create(dai.node.MonoCamera) camRight = pipeline.create(dai.node.MonoCamera) stereo = pipeline.create(dai.node.StereoDepth) @@ -215,8 +241,12 @@ def getDisparityFrame(frame): xoutRectifLeft.setStreamName("rectifiedLeft") xoutRectifRight.setStreamName("rectifiedRight") -camLeft.out.link(stereo.left) -camRight.out.link(stereo.right) +if args.swap_left_right: + camLeft.out.link(stereo.right) + camRight.out.link(stereo.left) +else: + camLeft.out.link(stereo.left) + camRight.out.link(stereo.right) stereo.syncedLeft.link(xoutLeft.input) stereo.syncedRight.link(xoutRight.input) stereo.disparity.link(xoutDisparity.input) @@ -233,8 +263,6 @@ def getDisparityFrame(frame): if depth: streams.append("depth") -device = dai.Device() -calibData = device.readCalibration() leftMesh, rightMesh = getMesh(calibData) if generateMesh: meshLeft = list(leftMesh.tobytes()) From 7888c4a8c839b45fdf93c5def51d15d6bd79eff8 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Thu, 2 Feb 2023 17:31:28 +0200 Subject: [PATCH 144/385] Update FW w/ syncing stall fix --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 8af255d71..66420ce9e 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 8af255d719c080a06d5129309f489c3a80ef2439 +Subproject commit 66420ce9ef2fa884267280630e76c6e3c9603540 From be029bcc72d3b2b8e4d17bcd6523a773bb1759c1 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Thu, 2 Feb 2023 17:46:22 +0200 Subject: [PATCH 145/385] Remove calibration swap --- examples/StereoDepth/stereo_depth_video.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/examples/StereoDepth/stereo_depth_video.py b/examples/StereoDepth/stereo_depth_video.py index 1f45dff20..107f4486c 100755 --- a/examples/StereoDepth/stereo_depth_video.py +++ b/examples/StereoDepth/stereo_depth_video.py @@ -187,23 +187,6 @@ def getDisparityFrame(frame): print("Creating Stereo Depth pipeline") pipeline = dai.Pipeline() -if args.swap_left_right: - M_left, width_l, height_l = calibData.getDefaultIntrinsics(dai.CameraBoardSocket.LEFT) - M_right, width_r, height_r = calibData.getDefaultIntrinsics(dai.CameraBoardSocket.RIGHT) - d_l = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.LEFT)) - d_r = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.RIGHT)) - - rect_l = calibData.getStereoLeftRectificationRotation() - rect_r = calibData.getStereoRightRectificationRotation() - - calibData.setCameraIntrinsics(dai.CameraBoardSocket.LEFT, M_right, width_r, height_r) - calibData.setCameraIntrinsics(dai.CameraBoardSocket.RIGHT, M_left, width_l, height_l) - calibData.setDistortionCoefficients(dai.CameraBoardSocket.LEFT, d_r) - calibData.setDistortionCoefficients(dai.CameraBoardSocket.RIGHT, d_l) - calibData.setStereoLeft(dai.CameraBoardSocket.LEFT, rect_r) - calibData.setStereoRight(dai.CameraBoardSocket.RIGHT, rect_l) - pipeline.setCalibrationData(calibData) - camLeft = pipeline.create(dai.node.MonoCamera) camRight = pipeline.create(dai.node.MonoCamera) stereo = pipeline.create(dai.node.StereoDepth) From a504e6ebb85459717bc5fd19e4278f3d2a219986 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Thu, 2 Feb 2023 20:50:22 +0200 Subject: [PATCH 146/385] Update stereo with more robust frame sync --- depthai-core | 2 +- examples/StereoDepth/stereo_depth_from_host.py | 7 +++---- examples/StereoDepth/stereo_depth_video.py | 17 +++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/depthai-core b/depthai-core index 66420ce9e..d2a50d2cd 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 66420ce9ef2fa884267280630e76c6e3c9603540 +Subproject commit d2a50d2cd45995dde874aeee7ac182e272fe57b7 diff --git a/examples/StereoDepth/stereo_depth_from_host.py b/examples/StereoDepth/stereo_depth_from_host.py index ae27cf9fe..a0d4339d2 100755 --- a/examples/StereoDepth/stereo_depth_from_host.py +++ b/examples/StereoDepth/stereo_depth_from_host.py @@ -555,6 +555,7 @@ def __init__(self, config): if args.dumpdisparitycostvalues: stereo.debugDispCostDump.link(xoutDebugCostDump.input) + StereoConfigHandler(stereo.initialConfig.get()) StereoConfigHandler.registerWindow('Stereo control panel') @@ -607,7 +608,7 @@ def convertToCv2Frame(name, image, config): elif 'disparity' in name: if 1: # Optionally, extend disparity range to better visualize it frame = (frame * 255. / maxDisp).astype(np.uint8) - + return frame # if 1: # Optionally, apply a color map # frame = cv2.applyColorMap(frame, cv2.COLORMAP_HOT) @@ -618,7 +619,7 @@ def convertToCv2Frame(name, image, config): with dai.Device(pipeline) as device: stereoDepthConfigInQueue = device.getInputQueue("stereoDepthConfig") - inStreams = ['in_right', 'in_left'] + inStreams = ['in_left', 'in_right'] inStreamsCameraID = [dai.CameraBoardSocket.RIGHT, dai.CameraBoardSocket.LEFT] in_q_list = [] for s in inStreams: @@ -658,8 +659,6 @@ def convertToCv2Frame(name, image, config): img.setWidth(width) img.setHeight(height) q.send(img) - if timestamp_ms == 0: # Send twice for first iteration - q.send(img) # print("Sent frame: {:25s}".format(path), 'timestamp_ms:', timestamp_ms) timestamp_ms += frame_interval_ms index = (index + 1) % dataset_size diff --git a/examples/StereoDepth/stereo_depth_video.py b/examples/StereoDepth/stereo_depth_video.py index 107f4486c..0d9ef4e6b 100755 --- a/examples/StereoDepth/stereo_depth_video.py +++ b/examples/StereoDepth/stereo_depth_video.py @@ -197,8 +197,13 @@ def getDisparityFrame(frame): xoutRectifLeft = pipeline.create(dai.node.XLinkOut) xoutRectifRight = pipeline.create(dai.node.XLinkOut) -camLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) -camRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +if args.swap_left_right: + camLeft.setBoardSocket(dai.CameraBoardSocket.RIGHT) + camRight.setBoardSocket(dai.CameraBoardSocket.LEFT) +else: + camLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) + camRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) + res = ( dai.MonoCameraProperties.SensorResolution.THE_800_P if resolution[1] == 800 @@ -224,12 +229,8 @@ def getDisparityFrame(frame): xoutRectifLeft.setStreamName("rectifiedLeft") xoutRectifRight.setStreamName("rectifiedRight") -if args.swap_left_right: - camLeft.out.link(stereo.right) - camRight.out.link(stereo.left) -else: - camLeft.out.link(stereo.left) - camRight.out.link(stereo.right) +camLeft.out.link(stereo.left) +camRight.out.link(stereo.right) stereo.syncedLeft.link(xoutLeft.input) stereo.syncedRight.link(xoutRight.input) stereo.disparity.link(xoutDisparity.input) From 57cf5b9b23a79583477677dc16a3ff234e889a14 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 3 Feb 2023 19:31:49 +0200 Subject: [PATCH 147/385] Update FW with optional override of spec translation for stereo algorithm calculations --- depthai-core | 2 +- examples/StereoDepth/stereo_depth_from_host.py | 2 +- src/pipeline/node/StereoDepthBindings.cpp | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/depthai-core b/depthai-core index d2a50d2cd..84bc648a9 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit d2a50d2cd45995dde874aeee7ac182e272fe57b7 +Subproject commit 84bc648a9b2a3243d9dafe8e9ee6554a70b6062b diff --git a/examples/StereoDepth/stereo_depth_from_host.py b/examples/StereoDepth/stereo_depth_from_host.py index a0d4339d2..0a2f2f773 100755 --- a/examples/StereoDepth/stereo_depth_from_host.py +++ b/examples/StereoDepth/stereo_depth_from_host.py @@ -620,7 +620,7 @@ def convertToCv2Frame(name, image, config): stereoDepthConfigInQueue = device.getInputQueue("stereoDepthConfig") inStreams = ['in_left', 'in_right'] - inStreamsCameraID = [dai.CameraBoardSocket.RIGHT, dai.CameraBoardSocket.LEFT] + inStreamsCameraID = [dai.CameraBoardSocket.LEFT, dai.CameraBoardSocket.RIGHT] in_q_list = [] for s in inStreams: q = device.getInputQueue(s) diff --git a/src/pipeline/node/StereoDepthBindings.cpp b/src/pipeline/node/StereoDepthBindings.cpp index 1ed9e43cb..cfd0917be 100644 --- a/src/pipeline/node/StereoDepthBindings.cpp +++ b/src/pipeline/node/StereoDepthBindings.cpp @@ -79,10 +79,6 @@ void bind_stereodepth(pybind11::module& m, void* pCallstack){ .def_readonly("debugExtDispLrCheckIt2", &StereoDepth::debugExtDispLrCheckIt2, DOC(dai, node, StereoDepth, debugExtDispLrCheckIt2)) .def_readonly("debugDispCostDump", &StereoDepth::debugDispCostDump, DOC(dai, node, StereoDepth, debugDispCostDump)) .def_readonly("confidenceMap", &StereoDepth::confidenceMap, DOC(dai, node, StereoDepth, confidenceMap)) -#if 0 //will be enabled when confidence map RGB alignment/LR-check support will be added - .def_readonly("debugConfMapLrCheckIt1", &StereoDepth::debugConfMapLrCheckIt1, DOC(dai, node, StereoDepth, debugConfMapLrCheckIt1)) - .def_readonly("debugConfMapLrCheckIt2", &StereoDepth::debugConfMapLrCheckIt2, DOC(dai, node, StereoDepth, debugConfMapLrCheckIt2)) -#endif .def("loadMeshFiles", &StereoDepth::loadMeshFiles, py::arg("pathLeft"), py::arg("pathRight"), DOC(dai, node, StereoDepth, loadMeshFiles)) .def("loadMeshData", &StereoDepth::loadMeshData, py::arg("dataLeft"), py::arg("dataRight"), DOC(dai, node, StereoDepth, loadMeshData)) .def("setMeshStep", &StereoDepth::setMeshStep, py::arg("width"), py::arg("height"), DOC(dai, node, StereoDepth, setMeshStep)) @@ -168,6 +164,9 @@ void bind_stereodepth(pybind11::module& m, void* pCallstack){ .def("enableDistortionCorrection", &StereoDepth::enableDistortionCorrection, DOC(dai, node, StereoDepth, enableDistortionCorrection)) .def("setBaseline", &StereoDepth::setBaseline, DOC(dai, node, StereoDepth, setBaseline)) .def("setFocalLength", &StereoDepth::setFocalLength, DOC(dai, node, StereoDepth, setFocalLength)) + .def("setDisparityToDepthUseSpecTranslation", &StereoDepth::setDisparityToDepthUseSpecTranslation, DOC(dai, node, StereoDepth, setDisparityToDepthUseSpecTranslation)) + .def("setRectificationUseSpecTranslation", &StereoDepth::setRectificationUseSpecTranslation, DOC(dai, node, StereoDepth, setRectificationUseSpecTranslation)) + .def("setDepthAlignmentuseSpecTranslation", &StereoDepth::setDepthAlignmentuseSpecTranslation, DOC(dai, node, StereoDepth, setDepthAlignmentuseSpecTranslation)) ; // ALIAS daiNodeModule.attr("StereoDepth").attr("Properties") = stereoDepthProperties; From e7c099b025a04a488c08a4094fb205914e203faa Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 3 Feb 2023 20:08:15 +0200 Subject: [PATCH 148/385] Correct typo --- depthai-core | 2 +- src/pipeline/node/StereoDepthBindings.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depthai-core b/depthai-core index 84bc648a9..104c21977 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 84bc648a9b2a3243d9dafe8e9ee6554a70b6062b +Subproject commit 104c21977027de39d613e7897e05833eac19bdc1 diff --git a/src/pipeline/node/StereoDepthBindings.cpp b/src/pipeline/node/StereoDepthBindings.cpp index cfd0917be..c2b13dea7 100644 --- a/src/pipeline/node/StereoDepthBindings.cpp +++ b/src/pipeline/node/StereoDepthBindings.cpp @@ -166,7 +166,7 @@ void bind_stereodepth(pybind11::module& m, void* pCallstack){ .def("setFocalLength", &StereoDepth::setFocalLength, DOC(dai, node, StereoDepth, setFocalLength)) .def("setDisparityToDepthUseSpecTranslation", &StereoDepth::setDisparityToDepthUseSpecTranslation, DOC(dai, node, StereoDepth, setDisparityToDepthUseSpecTranslation)) .def("setRectificationUseSpecTranslation", &StereoDepth::setRectificationUseSpecTranslation, DOC(dai, node, StereoDepth, setRectificationUseSpecTranslation)) - .def("setDepthAlignmentuseSpecTranslation", &StereoDepth::setDepthAlignmentuseSpecTranslation, DOC(dai, node, StereoDepth, setDepthAlignmentuseSpecTranslation)) + .def("setDepthAlignmentUseSpecTranslation", &StereoDepth::setDepthAlignmentUseSpecTranslation, DOC(dai, node, StereoDepth, setDepthAlignmentUseSpecTranslation)) ; // ALIAS daiNodeModule.attr("StereoDepth").attr("Properties") = stereoDepthProperties; From ad6f4034b85a92321808c63653f1950690e3c847 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sat, 4 Feb 2023 18:52:35 +0200 Subject: [PATCH 149/385] Added PoE latency docs --- docs/source/tutorials/low-latency.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/source/tutorials/low-latency.rst b/docs/source/tutorials/low-latency.rst index a4d33aaf0..376b7cd1f 100644 --- a/docs/source/tutorials/low-latency.rst +++ b/docs/source/tutorials/low-latency.rst @@ -113,6 +113,20 @@ branch of the DepthAI. This will pass pointers (at XLink level) to cv2.Mat inste so performance improvement would depend on the image sizes you are using. (Note: API differs and not all functionality is available as is on the `message_zero_copy` branch) +PoE latency +########### + +On PoE, the latency can vary quite a bit due to a number of factors: + +* **Network** itself. Eg. if you are in a large network with many nodes, the latency will be higher compared to using a direct connection. +* There's a **bottleneck** in **bandwidth**: + + * Perhaps some network link is 10mbps/100mbps instead of full 1gbps (due to switch/network card..). You can test this with `PoE Test script `__ (``speed`` should be 1000). + * Network/computer is saturated with other traffic. You can test the actual bandwidth with `OAK bandwidth test `__ script. With direct link I got ~800mbps downlink and ~210mbps uplink. + +* Computer's **Network Interface Card settings**, `documentation here `__ +* 100% OAK Leon CSS (CPU) usage. The Leon CSS core handles the POE communication (`see docs here `__), and if the CPU is 100% used, it will not be able to handle the communication as fast as it should. +* Another potential way to improve PoE latency would be to fine-tune network settings, like MTU, TCP window size, etc. (see `here `__ for more info) Reducing latency when running NN ################################ From f3eae613fb39299076ab77f5b2c6748efd96a11d Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sat, 4 Feb 2023 18:53:21 +0200 Subject: [PATCH 150/385] Added resource debugging docs --- docs/source/tutorials/debugging.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/source/tutorials/debugging.rst b/docs/source/tutorials/debugging.rst index 4b6a36002..4cce3e4d2 100644 --- a/docs/source/tutorials/debugging.rst +++ b/docs/source/tutorials/debugging.rst @@ -104,4 +104,21 @@ Code above will print the following values to the user: [Script(0)] [warning] FP16 values: [1.2001953125, 1.2001953125, 3.900390625, 5.5] [Script(0)] [warning] UINT8 values: [6, 9, 4, 2, 0] +Resource Debugging +================== + +By enabling ``info`` log level (or lower), depthai will print usage of `hardware resources `__, +specifically SHAVE core and CMX memory usage: + +.. code-block:: bash + + NeuralNetwork allocated resources: shaves: [0-11] cmx slices: [0-11] # 12 SHAVES/CMXs allocated to NN + ColorCamera allocated resources: no shaves; cmx slices: [13-15] # 3 CMXs allocated to Color an Mono cameras (ISP) + MonoCamera allocated resources: no shaves; cmx slices: [13-15] + StereoDepth allocated resources: shaves: [12-12] cmx slices: [12-12] # StereoDepth node consumes 1 CMX and 1 SHAVE core + ImageManip allocated resources: shaves: [15-15] no cmx slices. # ImageManip node(s) consume 1 SHAVE core + SpatialLocationCalculator allocated resources: shaves: [14-14] no cmx slices. # SLC consumes 1 SHAVE core + +In total, this pipeline consumes 15 SHAVE cores and 16 CMX slices. + .. include:: /includes/footer-short.rst \ No newline at end of file From 384e8985c57ae3942092aca160c6e020110d50e2 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sat, 4 Feb 2023 18:53:33 +0200 Subject: [PATCH 151/385] Updated disparity explanation image --- .../components/disparity_explanation.jpeg | Bin 51374 -> 63775 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/source/_static/images/components/disparity_explanation.jpeg b/docs/source/_static/images/components/disparity_explanation.jpeg index f4df8648438788161446ed352b048b20d2a4e638..e5450fd208f94c6a6c9d53283326c7184f6c5377 100644 GIT binary patch literal 63775 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 KDpw(67W`jK<1g(1 literal 51374 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 BSDgR= From 1ea8282295fbddc085cc52e8f13325bb9f4989f6 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sat, 4 Feb 2023 18:58:09 +0200 Subject: [PATCH 152/385] Updated debugging docs --- docs/source/tutorials/debugging.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/tutorials/debugging.rst b/docs/source/tutorials/debugging.rst index 4cce3e4d2..378780a8f 100644 --- a/docs/source/tutorials/debugging.rst +++ b/docs/source/tutorials/debugging.rst @@ -119,6 +119,6 @@ specifically SHAVE core and CMX memory usage: ImageManip allocated resources: shaves: [15-15] no cmx slices. # ImageManip node(s) consume 1 SHAVE core SpatialLocationCalculator allocated resources: shaves: [14-14] no cmx slices. # SLC consumes 1 SHAVE core -In total, this pipeline consumes 15 SHAVE cores and 16 CMX slices. +In total, this pipeline consumes 15 SHAVE cores and 16 CMX slices. The pipeline is running an object detection model compiled for 6 SHAVE cores. .. include:: /includes/footer-short.rst \ No newline at end of file From 56a224d44833c3735b824fd4c33c479de103af26 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sat, 4 Feb 2023 20:15:46 +0200 Subject: [PATCH 153/385] Update IQ docs about motion blur --- docs/source/tutorials/image_quality.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/source/tutorials/image_quality.rst b/docs/source/tutorials/image_quality.rst index 870e151d4..bee73828d 100644 --- a/docs/source/tutorials/image_quality.rst +++ b/docs/source/tutorials/image_quality.rst @@ -85,9 +85,18 @@ Motion blur In the image above the right foot moved about 50 pixels during the exposure time, which results in a blurry image in that region. The left foot was on the ground the whole time of the exposure, so it's not blurry. +In **high-vibration environments** we recommend **using Fixed-Focus** color camera, as otherwise the Auto-Focus lens will be shaking and +cause blurry images (`docs here `__). + **Potential workarounds:** -- Decrease the shutter (exposure) time - this will decrease the motion blur, but will also decrease the light that reaches the sensor, so the image will be darker. You could either use a larger sensor (so more photons hit the sensor) or use a higher ISO (sensitivity) value. -- If the motion blur negatively affects your model's accuracy, you could fine-tune it to be more robust to motion blur by including motion blur images in your training dataset. +1. Decrease the shutter (exposure) time - this will decrease the motion blur, but will also decrease the light that reaches the sensor, so the image will be darker. You could either use a larger sensor (so more photons hit the sensor) or use a higher ISO (sensitivity) value. +2. If the motion blur negatively affects your model's accuracy, you could fine-tune it to be more robust to motion blur by including motion blur images in your training dataset. Example video: + +.. raw:: html + +
+ +
.. include:: /includes/footer-short.rst \ No newline at end of file From a3f66b6a11946ffd16d61768398f8335f050cf87 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Sat, 4 Feb 2023 21:18:32 +0200 Subject: [PATCH 154/385] Expose center alignment scale factor for debug purposes --- depthai-core | 2 +- examples/StereoDepth/stereo_depth_from_host.py | 12 ++++++++++++ src/pipeline/datatype/StereoDepthConfigBindings.cpp | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 104c21977..2e15fa389 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 104c21977027de39d613e7897e05833eac19bdc1 +Subproject commit 2e15fa389d44527c51ea6fb155bba619e4636a68 diff --git a/examples/StereoDepth/stereo_depth_from_host.py b/examples/StereoDepth/stereo_depth_from_host.py index 0a2f2f773..cb1d782f0 100755 --- a/examples/StereoDepth/stereo_depth_from_host.py +++ b/examples/StereoDepth/stereo_depth_from_host.py @@ -156,6 +156,7 @@ def destroyWindow(self): trSpatialNumIterations = list() trDecimationFactor = list() trDisparityShift = list() + trCenterAlignmentShift = list() def trackbarSigma(value): StereoConfigHandler.config.postProcessing.bilateralSigmaValue = value @@ -279,6 +280,16 @@ def trackbarDisparityShift(value): for tr in StereoConfigHandler.trDisparityShift: tr.set(value) + def trackbarCenterAlignmentShift(value): + if StereoConfigHandler.config.algorithmControl.depthAlign != dai.StereoDepthConfig.AlgorithmControl.DepthAlign.CENTER: + print("Center alignment shift factor requires CENTER alignment enabled!") + return + StereoConfigHandler.config.algorithmControl.centerAlignmentShiftFactor = value / 100. + print(f"centerAlignmentShiftFactor: {StereoConfigHandler.config.algorithmControl.centerAlignmentShiftFactor:.2f}") + StereoConfigHandler.newConfig = True + for tr in StereoConfigHandler.trCenterAlignmentShift: + tr.set(value) + def handleKeypress(key, stereoDepthConfigInQueue): if key == ord('m'): StereoConfigHandler.newConfig = True @@ -416,6 +427,7 @@ def registerWindow(stream): StereoConfigHandler.trLrCheck.append(StereoConfigHandler.Trackbar('LR-check threshold', stream, 0, 16, StereoConfigHandler.config.algorithmControl.leftRightCheckThreshold, StereoConfigHandler.trackbarLrCheckThreshold)) StereoConfigHandler.trFractionalBits.append(StereoConfigHandler.Trackbar('Subpixel fractional bits', stream, 3, 5, StereoConfigHandler.config.algorithmControl.subpixelFractionalBits, StereoConfigHandler.trackbarFractionalBits)) StereoConfigHandler.trDisparityShift.append(StereoConfigHandler.Trackbar('Disparity shift', stream, 0, 100, StereoConfigHandler.config.algorithmControl.disparityShift, StereoConfigHandler.trackbarDisparityShift)) + StereoConfigHandler.trCenterAlignmentShift.append(StereoConfigHandler.Trackbar('Center alignment shift factor', stream, 0, 100, StereoConfigHandler.config.algorithmControl.centerAlignmentShiftFactor, StereoConfigHandler.trackbarCenterAlignmentShift)) StereoConfigHandler.trLineqAlpha.append(StereoConfigHandler.Trackbar('Linear equation alpha', stream, 0, 15, StereoConfigHandler.config.costMatching.linearEquationParameters.alpha, StereoConfigHandler.trackbarLineqAlpha)) StereoConfigHandler.trLineqBeta.append(StereoConfigHandler.Trackbar('Linear equation beta', stream, 0, 15, StereoConfigHandler.config.costMatching.linearEquationParameters.beta, StereoConfigHandler.trackbarLineqBeta)) StereoConfigHandler.trLineqThreshold.append(StereoConfigHandler.Trackbar('Linear equation threshold', stream, 0, 255, StereoConfigHandler.config.costMatching.linearEquationParameters.threshold, StereoConfigHandler.trackbarLineqThreshold)) diff --git a/src/pipeline/datatype/StereoDepthConfigBindings.cpp b/src/pipeline/datatype/StereoDepthConfigBindings.cpp index d95fa9c37..0f4aaedec 100644 --- a/src/pipeline/datatype/StereoDepthConfigBindings.cpp +++ b/src/pipeline/datatype/StereoDepthConfigBindings.cpp @@ -87,6 +87,7 @@ void bind_stereodepthconfig(pybind11::module& m, void* pCallstack){ .def_readwrite("leftRightCheckThreshold", &RawStereoDepthConfig::AlgorithmControl::leftRightCheckThreshold, DOC(dai, RawStereoDepthConfig, AlgorithmControl, leftRightCheckThreshold)) .def_readwrite("subpixelFractionalBits", &RawStereoDepthConfig::AlgorithmControl::subpixelFractionalBits, DOC(dai, RawStereoDepthConfig, AlgorithmControl, subpixelFractionalBits)) .def_readwrite("disparityShift", &RawStereoDepthConfig::AlgorithmControl::disparityShift, DOC(dai, RawStereoDepthConfig, AlgorithmControl, disparityShift)) + .def_readwrite("centerAlignmentShiftFactor", &RawStereoDepthConfig::AlgorithmControl::centerAlignmentShiftFactor, DOC(dai, RawStereoDepthConfig, AlgorithmControl, centerAlignmentShiftFactor)) ; spatialFilter From 20eb39fcaec72807ed0d94172465a4a57a6241d4 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Sun, 5 Feb 2023 00:07:17 +0200 Subject: [PATCH 155/385] Expose SIPP mempool configurable sizes --- depthai-core | 2 +- src/DeviceBindings.cpp | 32 +++++++++++++++++--------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/depthai-core b/depthai-core index 2e15fa389..f9d9534e3 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 2e15fa389d44527c51ea6fb155bba619e4636a68 +Subproject commit f9d9534e3884543ec101a9e5b1a4b403030e4379 diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 976354b8b..f7bbd6997 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -447,21 +447,23 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ // Bind BoardConfig boardConfig .def(py::init<>()) - .def_readwrite("usb", &BoardConfig::usb) - .def_readwrite("network", &BoardConfig::network) - .def_readwrite("sysctl", &BoardConfig::sysctl) - .def_readwrite("watchdogTimeoutMs", &BoardConfig::watchdogTimeoutMs) - .def_readwrite("watchdogInitialDelayMs", &BoardConfig::watchdogInitialDelayMs) - .def_readwrite("gpio", &BoardConfig::gpio) - .def_readwrite("uart", &BoardConfig::uart) - .def_readwrite("pcieInternalClock", &BoardConfig::pcieInternalClock) - .def_readwrite("usb3PhyInternalClock", &BoardConfig::usb3PhyInternalClock) - .def_readwrite("mipi4LaneRgb", &BoardConfig::mipi4LaneRgb) - .def_readwrite("emmc", &BoardConfig::emmc) - .def_readwrite("logPath", &BoardConfig::logPath) - .def_readwrite("logSizeMax", &BoardConfig::logSizeMax) - .def_readwrite("logVerbosity", &BoardConfig::logVerbosity) - .def_readwrite("logDevicePrints", &BoardConfig::logDevicePrints) + .def_readwrite("usb", &BoardConfig::usb, DOC(dai, BoardConfig, usb)) + .def_readwrite("network", &BoardConfig::network, DOC(dai, BoardConfig, network)) + .def_readwrite("sysctl", &BoardConfig::sysctl, DOC(dai, BoardConfig, sysctl)) + .def_readwrite("watchdogTimeoutMs", &BoardConfig::watchdogTimeoutMs, DOC(dai, BoardConfig, watchdogTimeoutMs)) + .def_readwrite("watchdogInitialDelayMs", &BoardConfig::watchdogInitialDelayMs, DOC(dai, BoardConfig, watchdogInitialDelayMs)) + .def_readwrite("sippBufferSize", &BoardConfig::sippBufferSize, DOC(dai, BoardConfig, sippBufferSize)) + .def_readwrite("sippDmaBufferSize", &BoardConfig::sippDmaBufferSize, DOC(dai, BoardConfig, sippDmaBufferSize)) + .def_readwrite("gpio", &BoardConfig::gpio, DOC(dai, BoardConfig, gpio)) + .def_readwrite("uart", &BoardConfig::uart, DOC(dai, BoardConfig, uart)) + .def_readwrite("pcieInternalClock", &BoardConfig::pcieInternalClock, DOC(dai, BoardConfig, pcieInternalClock)) + .def_readwrite("usb3PhyInternalClock", &BoardConfig::usb3PhyInternalClock, DOC(dai, BoardConfig, usb3PhyInternalClock)) + .def_readwrite("mipi4LaneRgb", &BoardConfig::mipi4LaneRgb, DOC(dai, BoardConfig, mipi4LaneRgb)) + .def_readwrite("emmc", &BoardConfig::emmc, DOC(dai, BoardConfig, emmc)) + .def_readwrite("logPath", &BoardConfig::logPath, DOC(dai, BoardConfig, logPath)) + .def_readwrite("logSizeMax", &BoardConfig::logSizeMax, DOC(dai, BoardConfig, logSizeMax)) + .def_readwrite("logVerbosity", &BoardConfig::logVerbosity, DOC(dai, BoardConfig, logVerbosity)) + .def_readwrite("logDevicePrints", &BoardConfig::logDevicePrints, DOC(dai, BoardConfig, logDevicePrints)) ; // Bind Device::Config From d015cab77b9959932d2e86ae19c803c4fcf76b56 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 6 Feb 2023 13:49:08 +0200 Subject: [PATCH 156/385] Update FW before merge --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index f9d9534e3..4cfdafb7f 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit f9d9534e3884543ec101a9e5b1a4b403030e4379 +Subproject commit 4cfdafb7f1b4db896f361982b6ac963c093f1783 From 5fb7d9c9085cf8860a62c95d9397bc4604daa7df Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 6 Feb 2023 18:47:21 +0200 Subject: [PATCH 157/385] remove copyright from docs --- docs/conf.py.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py.in b/docs/conf.py.in index e6e8a5d74..1c057d961 100644 --- a/docs/conf.py.in +++ b/docs/conf.py.in @@ -26,7 +26,7 @@ sys.path.insert(0, os.path.abspath(source_directory / ".." / "_extensions")) # -- Project information ----------------------------------------------------- project = "DepthAI API Docs" -copyright = u"@build_year@, Luxonis" +html_show_copyright=False author = "Luxonis" version = u"@DEPTHAI_PYTHON_VERSION@" release = version From a2880dc994f3f35a72d36891812359450ed49ca9 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 30 Jan 2023 14:14:42 +0200 Subject: [PATCH 158/385] Merge pull request #745 from luxonis/warp_node Started adding warp node docs (cherry picked from commit 548b44442832ac98dd923223ead808d5afac0199) --- docs/source/components/nodes/image_manip.rst | 2 +- docs/source/components/nodes/warp.rst | 114 +++++++++++++ docs/source/samples/Warp/warp_mesh.rst | 40 +++++ .../samples/Warp/warp_mesh_interactive.rst | 45 ++++++ docs/source/tutorials/code_samples.rst | 6 + examples/Warp/warp_mesh_interactive.py | 153 ++++++++++++++++++ 6 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 docs/source/components/nodes/warp.rst create mode 100644 docs/source/samples/Warp/warp_mesh.rst create mode 100644 docs/source/samples/Warp/warp_mesh_interactive.rst create mode 100644 examples/Warp/warp_mesh_interactive.py diff --git a/docs/source/components/nodes/image_manip.rst b/docs/source/components/nodes/image_manip.rst index 3d3b69a6b..bb466e9fb 100644 --- a/docs/source/components/nodes/image_manip.rst +++ b/docs/source/components/nodes/image_manip.rst @@ -76,7 +76,7 @@ ImageManip node supports the following image formats (more info `in PR here `__), +with no extra resources (SHAVE/cmx cores). HW limitation: **width must be divisible by 16.** + +**ImageManip node** combines the power of warp HW block together the efficiency of CMX memory to achieve higher +throughput (e.g. 4k@30 fps). Scheduling of the HW block is done by SHAVE cores which also do color space conversion, type conversion (YUV420 to NV12), etc. +The downside of using ImageManip node is extra RAM and SHAVE usage. + +How to place it +############### + +.. tabs:: + + .. code-tab:: py + + pipeline = dai.Pipeline() + warp = pipeline.create(dai.node.Warp) + + .. code-tab:: c++ + + dai::Pipeline pipeline; + auto warp = pipeline.create(); + +Inputs and Outputs +################## + +.. code-block:: + + ┌────────────┐ + inputImage │ │ out + ──────────►│ Warp ├──────► + │ │ + └────────────┘ + +**Message types** + +- ``inputImage`` - :ref:`ImgFrame` +- ``out`` - :ref:`ImgFrame` + +Usage +##### + +.. tabs:: + + .. code-tab:: py + + pipeline = dai.Pipeline() + + warp = pipeline.create(dai.node.Warp) + # Create a custom warp mesh + p1 = dai.Point2f(20, 20) + p2 = dai.Point2f(460, 20) + p3 = dai.Point2f(20, 460) + p4 = dai.Point2f(460, 460) + warp.setWarpMesh([p1,p2,p3,p4], 2, 2) + warp.setOutputSize((512,512)) + warp.setMaxOutputFrameSize(512 * 512 * 3) + # Warp engines to be used (0,1,2) + warp.setHwIds([1]) + # Warp interpolation mode, choose between BILINEAR, BICUBIC, BYPASS + warp.setInterpolation(dai.node.Warp.Properties.Interpolation.BYPASS) + + .. code-tab:: c++ + + dai::Pipeline pipeline; + + auto warp = pipeline.create(); + // Create a custom warp mesh + dai::Point2f p1(20, 20); + dai::Point2f p2(460, 20); + dai::Point2f p3(20, 460); + dai::Point2f p4(460, 460); + warp->setWarpMesh({p1,p2,p3,p4}, 2, 2); + warp->setOutputSize({512, 512}); + warp->setMaxOutputFrameSize(512 * 512 * 3); + // Warp engines to be used (0,1,2) + warp->setHwIds({1}); + // Warp interpolation mode, choose between BILINEAR, BICUBIC, BYPASS + warp->setInterpolation(dai::node::Warp::Properties::Interpolation::BYPASS); + +Examples of functionality +######################### + +- :ref:`Warp Mesh` +- :ref:`Interactive Warp Mesh` + +Reference +######### + +.. tabs:: + + .. tab:: Python + + .. autoclass:: depthai.node.Warp + :members: + :inherited-members: + :noindex: + + .. tab:: C++ + + .. doxygenclass:: dai::node::Warp + :project: depthai-core + :members: + :private-members: + :undoc-members: + +.. include:: ../../includes/footer-short.rst diff --git a/docs/source/samples/Warp/warp_mesh.rst b/docs/source/samples/Warp/warp_mesh.rst new file mode 100644 index 000000000..671e6933c --- /dev/null +++ b/docs/source/samples/Warp/warp_mesh.rst @@ -0,0 +1,40 @@ +Warp Mesh +========= + +This example shows usage of :ref:`Warp` node to warp the input image frame. + +Setup +##### + +.. include:: /includes/install_from_pypi.rst + +Demo +#### + +.. figure:: https://user-images.githubusercontent.com/18037362/214597821-2f76239a-48fa-4146-ba47-9cad872454ea.png + + Warped images + + +Source code +########### + +.. tabs:: + + .. tab:: Python + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../examples/Warp/warp_mesh.py + :language: python + :linenos: + + .. tab:: C++ + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../depthai-core/examples/Warp/warp_mesh.cpp + :language: cpp + :linenos: + +.. include:: /includes/footer-short.rst diff --git a/docs/source/samples/Warp/warp_mesh_interactive.rst b/docs/source/samples/Warp/warp_mesh_interactive.rst new file mode 100644 index 000000000..d72e0697d --- /dev/null +++ b/docs/source/samples/Warp/warp_mesh_interactive.rst @@ -0,0 +1,45 @@ +Interactive Warp Mesh +===================== + +This example shows usage of :ref:`Warp` node to warp the input image frame. It let's you interactively change the mesh points to warp the image. After changing the points, +**user has to press** ``r`` to restart the pipeline and apply the changes. + +User-defined arguments: + +- ``--mesh_dims`` - Mesh dimensions (default: ``4x4``). +- ``--resolution`` - Resolution of the input image (default: ``512x512``). Width must be divisible by 16. +- ``--random`` - To generate random mesh points (disabled by default). + +Originally developed by `geaxgx `__. + +Setup +##### + +.. include:: /includes/install_from_pypi.rst + +Demo +#### + +.. figure:: https://user-images.githubusercontent.com/18037362/214605914-87cf0404-2d89-478f-9062-2dfb4baa6512.png + + Original and warped image + + +Source code +########### + +.. tabs:: + + .. tab:: Python + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../examples/Warp/warp_mesh_interactive.py + :language: python + :linenos: + + .. tab:: C++ + + WIP + +.. include:: /includes/footer-short.rst diff --git a/docs/source/tutorials/code_samples.rst b/docs/source/tutorials/code_samples.rst index e817ae708..46127c230 100644 --- a/docs/source/tutorials/code_samples.rst +++ b/docs/source/tutorials/code_samples.rst @@ -23,6 +23,7 @@ Code Samples ../samples/StereoDepth/* ../samples/SystemLogger/* ../samples/VideoEncoder/* + ../samples/Warp/* ../samples/Yolo/* Code samples are used for automated testing. They are also a great starting point for the DepthAI API, as different node functionalities @@ -150,6 +151,11 @@ are presented with code. - :ref:`Encoding Max Limit` - Encodes RGB (4k 25FPS) and both mono streams (720P, 25FPS) into :code:`.h265`/:code:`.h264` and saves them on the host - :ref:`RGB Full Resolution Saver` - Saves full resolution RGB images (4k) on the host (:code:`.jpeg`) +.. rubric:: Warp + +- :ref:`Warp Mesh` - Displays an image warped with 2 different meshes +- :ref:`Interactive Warp Mesh` - Interactively change the warp mesh + .. rubric:: Yolo - :ref:`RGB & Tiny YOLO` - Runs Tiny YOLO on RGB frames and displays detections on the frame diff --git a/examples/Warp/warp_mesh_interactive.py b/examples/Warp/warp_mesh_interactive.py new file mode 100644 index 000000000..6065dd951 --- /dev/null +++ b/examples/Warp/warp_mesh_interactive.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +import cv2 +import depthai as dai +import numpy as np +import argparse +import re +import sys +from random import randint + +parser = argparse.ArgumentParser() +parser.add_argument("-m", "--mesh_dims", type=str, default="4x4", help="mesh dimensions widthxheight (default=%(default)s)") +parser.add_argument("-r", "--resolution", type=str, default="512x512", help="preview resolution (default=%(default)s)") +parser.add_argument("-rnd", "--random", action="store_true", help="Generate random initial mesh") +args = parser.parse_args() + +# mesh dimensions +match = re.search(r'.*?(\d+)x(\d+).*', args.mesh_dims) +if not match: + raise Exception(f"Mesh dimensions format incorrect '{args.resolution}'!") +mesh_w = int(match.group(1)) +mesh_h = int(match.group(2)) + +# Preview resolution +match = re.search(r'.*?(\d+)x(\d+).*', args.resolution) +if not match: + raise Exception(f"Resolution format incorrect '{args.resolution}'!") +preview_w = int(match.group(1)) +preview_h = int(match.group(2)) +if preview_w % 16 != 0: + raise Exception(f"Preview width must be a multiple of 16!") + +# Create an initial mesh (optionally random) of dimension mesh_w x mesh_h +first_point_x = int(preview_w / 10) +between_points_x = int(4 * preview_w / (5 * (mesh_w - 1))) +first_point_y = int(preview_h / 10) +between_points_y = int(4 * preview_h / (5 * (mesh_h - 1))) +if args.random: + max_rnd_x = int(between_points_x / 4) + max_rnd_y = int(between_points_y / 4) +mesh = [] +for i in range(mesh_h): + for j in range(mesh_w): + x = first_point_x + j * between_points_x + y = first_point_y + i * between_points_y + if args.random: + rnd_x = randint(-max_rnd_x, max_rnd_x) + if x + rnd_x > 0 and x + rnd_x < preview_w: + x += rnd_x + rnd_y = randint(-max_rnd_y, max_rnd_y) + if y + rnd_y > 0 and y + rnd_y < preview_h: + y += rnd_y + mesh.append((x, y)) + +def create_pipeline(mesh): + print(mesh) + # Create pipeline + pipeline = dai.Pipeline() + + camRgb = pipeline.create(dai.node.ColorCamera) + camRgb.setPreviewSize(preview_w, preview_h) + camRgb.setInterleaved(False) + width = camRgb.getPreviewWidth() + height = camRgb.getPreviewHeight() + + # Output source + xout_source = pipeline.create(dai.node.XLinkOut) + xout_source.setStreamName('source') + camRgb.preview.link(xout_source.input) + # Warp source frame + warp = pipeline.create(dai.node.Warp) + warp.setWarpMesh(mesh, mesh_w, mesh_h) + warp.setOutputSize(width, height) + warp.setMaxOutputFrameSize(width * height * 3) + camRgb.preview.link(warp.inputImage) + + warp.setHwIds([1]) + warp.setInterpolation(dai.node.Warp.Properties.Interpolation.BYPASS) + # Output warped + xout_warped = pipeline.create(dai.node.XLinkOut) + xout_warped.setStreamName('warped') + warp.out.link(xout_warped.input) + return pipeline + +point_selected = None + +def mouse_callback(event, x, y, flags, param): + global mesh, point_selected, mesh_changed + if event == cv2.EVENT_LBUTTONDOWN: + if point_selected is None: + # Which point is selected ? + min_dist = 100 + + for i in range(len(mesh)): + dist = np.linalg.norm((x - mesh[i][0], y - mesh[i][1])) + if dist < 20 and dist < min_dist: + min_dist = dist + point_selected = i + if point_selected is not None: + mesh[point_selected] = (x, y) + mesh_changed = True + + elif event == cv2.EVENT_LBUTTONUP: + point_selected = None + elif event == cv2.EVENT_MOUSEMOVE: + if point_selected is not None: + mesh[point_selected] = (x, y) + mesh_changed = True + + +cv2.namedWindow("Source") +cv2.setMouseCallback("Source", mouse_callback) + +running = True + +print("Use your mouse to modify the mesh by clicking/moving points of the mesh in the Source window") +print("Then press 'r' key to restart the device/pipeline") +while running: + pipeline = create_pipeline(mesh) + # Connect to device and start pipeline + with dai.Device(pipeline) as device: + print("Starting device") + # Output queue will be used to get the rgb frames from the output defined above + q_source = device.getOutputQueue(name="source", maxSize=4, blocking=False) + q_warped = device.getOutputQueue(name="warped", maxSize=4, blocking=False) + + restart_device = False + mesh_changed = False + while not restart_device: + in0 = q_source.get() + if in0 is not None: + source = in0.getCvFrame() + color = (0, 0,255) if mesh_changed else (0,255,0) + for i in range(len(mesh)): + cv2.circle(source, (mesh[i][0], mesh[i][1]), 4, color, -1) + if i % mesh_w != mesh_w -1: + cv2.line(source, (mesh[i][0], mesh[i][1]), (mesh[i+1][0], mesh[i+1][1]), color, 2) + if i + mesh_w < len(mesh): + cv2.line(source, (mesh[i][0], mesh[i][1]), (mesh[i+mesh_w][0], mesh[i+mesh_w][1]), color, 2) + cv2.imshow("Source", source) + + in1 = q_warped.get() + if in1 is not None: + cv2.imshow("Warped", in1.getCvFrame()) + + key = cv2.waitKey(1) + if key == ord('r'): # Restart the device if mesh has changed + if mesh_changed: + print("Restart requested...") + mesh_changed = False + restart_device = True + elif key == 27 or key == ord('q'): # Exit + running = False + break \ No newline at end of file From 16b2b10732f0f4e4a8aa38f174316e978fb84f0e Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 6 Feb 2023 20:09:03 +0200 Subject: [PATCH 159/385] Updated stereo docs about disparity --- docs/source/components/nodes/stereo_depth.rst | 5 ----- docs/source/tutorials/image_quality.rst | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/source/components/nodes/stereo_depth.rst b/docs/source/components/nodes/stereo_depth.rst index 0c7c3f83a..5b6750415 100644 --- a/docs/source/components/nodes/stereo_depth.rst +++ b/docs/source/components/nodes/stereo_depth.rst @@ -270,15 +270,10 @@ Disparity ========= Disparity refers to the distance between two corresponding points in the left and right image of a stereo pair. -By looking at the image below, it can be seen that point :code:`X` gets projected to :code:`XL = (u, v)` in the :code:`Left view` and :code:`XR = (p, q)` in the :code:`Right view`. .. image:: /_static/images/components/disparity_explanation.jpeg :target: https://stackoverflow.com/a/17620159 -Since we know points :code:`XL` and :code:`XR` refer to the same point: :code:`X`, the disparity for this point is equal to the magnitude of the vector between :code:`(u, v)` and :code:`(p, q)`. - -For a more detailed explanation see `this `__ answer on Stack Overflow. - When calculating the disparity, each pixel in the disparity map gets assigned a confidence value :code:`0..255` by the stereo matching algorithm, as: diff --git a/docs/source/tutorials/image_quality.rst b/docs/source/tutorials/image_quality.rst index bee73828d..71065016f 100644 --- a/docs/source/tutorials/image_quality.rst +++ b/docs/source/tutorials/image_quality.rst @@ -6,6 +6,7 @@ There are a few ways to improve Image Quality (IQ) on OAK cameras. A few example #. Changing :ref:`Color camera ISP configuration` #. Try keeping camera sensitivity low - :ref:`Low-light increased sensitivity` #. :ref:`Camera tuning` with custom tuning blobs +#. Ways to reduce :ref:`Motion blur` effects Note that the `Series 3 OAK cameras `__ will also have **temporal noise filter**, which will improve IQ. From 474ce08a15c3efdc34a1d8db21eb3361e9ac69a0 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Mon, 6 Feb 2023 20:12:54 +0200 Subject: [PATCH 160/385] Fixed usage of DeviceBootloader with incomplete DeviceInfo and added a convinience constructor --- depthai-core | 2 +- src/DeviceBootloaderBindings.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/depthai-core b/depthai-core index 9a75541f7..cf497bead 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 9a75541f726dd7513eedc243aae5ea543dd02995 +Subproject commit cf497bead9caf28069ada622e2e60555604c05cd diff --git a/src/DeviceBootloaderBindings.cpp b/src/DeviceBootloaderBindings.cpp index c91d4365e..b862c6f80 100644 --- a/src/DeviceBootloaderBindings.cpp +++ b/src/DeviceBootloaderBindings.cpp @@ -131,8 +131,10 @@ void DeviceBootloaderBindings::bind(pybind11::module& m, void* pCallstack){ .def_static("getEmbeddedBootloaderVersion", &DeviceBootloader::getEmbeddedBootloaderVersion, DOC(dai, DeviceBootloader, getEmbeddedBootloaderVersion)) .def_static("getEmbeddedBootloaderBinary", &DeviceBootloader::getEmbeddedBootloaderBinary, DOC(dai, DeviceBootloader, getEmbeddedBootloaderBinary)) - .def(py::init(), py::arg("devInfo"), py::arg("allowFlashingBootloader") = false, DOC(dai, DeviceBootloader, DeviceBootloader)) - .def(py::init(), py::arg("devInfo"), py::arg("pathToCmd"), py::arg("allowFlashingBootloader") = false, DOC(dai, DeviceBootloader, DeviceBootloader, 2)) + .def(py::init(), py::arg("devInfo"), py::arg("allowFlashingBootloader") = false, DOC(dai, DeviceBootloader, DeviceBootloader, 4)) + .def(py::init(), py::arg("devInfo"), py::arg("pathToCmd"), py::arg("allowFlashingBootloader") = false, DOC(dai, DeviceBootloader, DeviceBootloader, 5)) + .def(py::init(), py::arg("nameOrDeviceId"), py::arg("allowFlashingBootloader") = false, DOC(dai, DeviceBootloader, DeviceBootloader, 6)) + .def("flash", [](DeviceBootloader& db, std::function progressCallback, const Pipeline& pipeline, bool compress, std::string applicationName, DeviceBootloader::Memory memory, bool checkChecksum) { py::gil_scoped_release release; return db.flash(progressCallback, pipeline, compress, applicationName, memory, checkChecksum); }, py::arg("progressCallback"), py::arg("pipeline"), py::arg("compress") = false, py::arg("applicationName") = "", py::arg("memory") = DeviceBootloader::Memory::AUTO, py::arg("checkChecksum") = false, DOC(dai, DeviceBootloader, flash)) .def("flash", [](DeviceBootloader& db, const Pipeline& pipeline, bool compress, std::string applicationName, DeviceBootloader::Memory memory, bool checkChecksum) { py::gil_scoped_release release; return db.flash(pipeline, compress, applicationName, memory, checkChecksum); }, py::arg("pipeline"), py::arg("compress") = false, py::arg("applicationName") = "", py::arg("memory") = DeviceBootloader::Memory::AUTO, py::arg("checkChecksum") = false, DOC(dai, DeviceBootloader, flash, 2)) From 6a977524ada58bc4c69747c8787e79ccd6ae937d Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 6 Feb 2023 21:22:25 +0200 Subject: [PATCH 161/385] Add alpha scaling option for StereoDepth --- depthai-core | 2 +- src/pipeline/node/StereoDepthBindings.cpp | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 9a75541f7..57c8e7cf7 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 9a75541f726dd7513eedc243aae5ea543dd02995 +Subproject commit 57c8e7cf7029cc35e2cfdabb3f8489fd4cabd4d2 diff --git a/src/pipeline/node/StereoDepthBindings.cpp b/src/pipeline/node/StereoDepthBindings.cpp index c2b13dea7..674a591a7 100644 --- a/src/pipeline/node/StereoDepthBindings.cpp +++ b/src/pipeline/node/StereoDepthBindings.cpp @@ -53,6 +53,14 @@ void bind_stereodepth(pybind11::module& m, void* pCallstack){ .def_readwrite("numFramesPool", &StereoDepthProperties::numFramesPool, DOC(dai, StereoDepthProperties, numFramesPool)) .def_readwrite("numPostProcessingShaves", &StereoDepthProperties::numPostProcessingShaves, DOC(dai, StereoDepthProperties, numPostProcessingShaves)) .def_readwrite("numPostProcessingMemorySlices", &StereoDepthProperties::numPostProcessingMemorySlices, DOC(dai, StereoDepthProperties, numPostProcessingMemorySlices)) + .def_readwrite("focalLengthFromCalibration", &StereoDepthProperties::focalLengthFromCalibration, DOC(dai, StereoDepthProperties, focalLengthFromCalibration)) + .def_readwrite("useHomographyRectification", &StereoDepthProperties::useHomographyRectification, DOC(dai, StereoDepthProperties, useHomographyRectification)) + .def_readwrite("baseline", &StereoDepthProperties::baseline, DOC(dai, StereoDepthProperties, baseline)) + .def_readwrite("focalLength", &StereoDepthProperties::focalLength, DOC(dai, StereoDepthProperties, focalLength)) + .def_readwrite("disparityToDepthUseSpecTranslation", &StereoDepthProperties::disparityToDepthUseSpecTranslation, DOC(dai, StereoDepthProperties, disparityToDepthUseSpecTranslation)) + .def_readwrite("rectificationUseSpecTranslation", &StereoDepthProperties::rectificationUseSpecTranslation, DOC(dai, StereoDepthProperties, rectificationUseSpecTranslation)) + .def_readwrite("depthAlignmentUseSpecTranslation", &StereoDepthProperties::depthAlignmentUseSpecTranslation, DOC(dai, StereoDepthProperties, depthAlignmentUseSpecTranslation)) + .def_readwrite("alphaScaling", &StereoDepthProperties::alphaScaling, DOC(dai, StereoDepthProperties, alphaScaling)) ; stereoDepthPresetMode @@ -167,6 +175,7 @@ void bind_stereodepth(pybind11::module& m, void* pCallstack){ .def("setDisparityToDepthUseSpecTranslation", &StereoDepth::setDisparityToDepthUseSpecTranslation, DOC(dai, node, StereoDepth, setDisparityToDepthUseSpecTranslation)) .def("setRectificationUseSpecTranslation", &StereoDepth::setRectificationUseSpecTranslation, DOC(dai, node, StereoDepth, setRectificationUseSpecTranslation)) .def("setDepthAlignmentUseSpecTranslation", &StereoDepth::setDepthAlignmentUseSpecTranslation, DOC(dai, node, StereoDepth, setDepthAlignmentUseSpecTranslation)) + .def("setAlphaScaling", &StereoDepth::setAlphaScaling, DOC(dai, node, StereoDepth, setAlphaScaling)) ; // ALIAS daiNodeModule.attr("StereoDepth").attr("Properties") = stereoDepthProperties; From 4963f8f06d9a31217a3fbd58595bc36959082e99 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 7 Feb 2023 13:31:52 +0200 Subject: [PATCH 162/385] Update FW before merge --- depthai-core | 2 +- examples/StereoDepth/stereo_depth_video.py | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/depthai-core b/depthai-core index 57c8e7cf7..e08025e9e 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 57c8e7cf7029cc35e2cfdabb3f8489fd4cabd4d2 +Subproject commit e08025e9e81abdb84f89efba520fd260891f13ea diff --git a/examples/StereoDepth/stereo_depth_video.py b/examples/StereoDepth/stereo_depth_video.py index 0d9ef4e6b..8ee3a2269 100755 --- a/examples/StereoDepth/stereo_depth_video.py +++ b/examples/StereoDepth/stereo_depth_video.py @@ -76,6 +76,13 @@ action="store_true", help="Swap left right frames", ) +parser.add_argument( + "-a", + "--alpha", + type=float, + default=None, + help="Alpha scaling parameter to increase FOV", +) args = parser.parse_args() resolutionMap = {"800": (1280, 800), "720": (1280, 720), "400": (640, 400)} @@ -174,10 +181,10 @@ def saveMeshFiles(meshLeft, meshRight, outputPath): meshRight.tofile(outputPath + "/right_mesh.calib") -def getDisparityFrame(frame): +def getDisparityFrame(frame, cvColorMap): maxDisp = stereo.initialConfig.getMaxDisparity() disp = (frame * (255.0 / maxDisp)).astype(np.uint8) - disp = cv2.applyColorMap(disp, cv2.COLORMAP_JET) + disp = cv2.applyColorMap(disp, cvColorMap) return disp @@ -221,6 +228,11 @@ def getDisparityFrame(frame): stereo.setLeftRightCheck(lrcheck) stereo.setExtendedDisparity(extended) stereo.setSubpixel(subpixel) +if args.alpha is not None: + stereo.setAlphaScaling(args.alpha) + config = stereo.initialConfig.get() + config.postProcessing.brightnessFilter.minBrightness = 0 + stereo.initialConfig.set(config) xoutLeft.setStreamName("left") xoutRight.setStreamName("right") @@ -256,7 +268,8 @@ def getDisparityFrame(frame): if meshDirectory is not None: saveMeshFiles(leftMesh, rightMesh, meshDirectory) - +cvColorMap = cv2.applyColorMap(np.arange(256, dtype=np.uint8), cv2.COLORMAP_JET) +cvColorMap[0] = [0, 0, 0] print("Creating DepthAI device") with device: device.startPipeline(pipeline) @@ -271,7 +284,7 @@ def getDisparityFrame(frame): if name == "depth": frame = frame.astype(np.uint16) elif name == "disparity": - frame = getDisparityFrame(frame) + frame = getDisparityFrame(frame, cvColorMap) cv2.imshow(name, frame) if cv2.waitKey(1) == ord("q"): From 1dd975caa5c3d1756b3d296c595baff45d8dd592 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 7 Feb 2023 14:27:56 +0200 Subject: [PATCH 163/385] Update FW with RGB alignment fix --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index e08025e9e..9f1de6957 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit e08025e9e81abdb84f89efba520fd260891f13ea +Subproject commit 9f1de695786b01c5313c617b0d50f55de7836a68 From db8ffa7ed859dea007d29bf17b348877f52fd9d1 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 7 Feb 2023 17:18:28 +0200 Subject: [PATCH 164/385] Update FW with performance metrics when DEPTHAI_LEVEL=info is enabled; enable brightness filter for 0 intensity pixels by default --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 9f1de6957..ec72ea827 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 9f1de695786b01c5313c617b0d50f55de7836a68 +Subproject commit ec72ea8272734ad0b59ecaaaa4c425e2c622e496 From 7d30bb1c5e134745bbe669e56d76887fe6ad2cd4 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 8 Feb 2023 19:29:01 +0200 Subject: [PATCH 165/385] Improve spatial calculation X and Y accuracy; fix RGB alignment when custom output depth size is specified --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index ec72ea827..25bacff1a 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit ec72ea8272734ad0b59ecaaaa4c425e2c622e496 +Subproject commit 25bacff1a11f2d0fcde1412b0d30f888571dae46 From 03f87642dda39f205fb87188aa29d45399b111a1 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 10 Feb 2023 19:46:59 +0200 Subject: [PATCH 166/385] Add crash dump functionality --- depthai-core | 2 +- src/DeviceBindings.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index ec72ea827..a1febd3ea 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit ec72ea8272734ad0b59ecaaaa4c425e2c622e496 +Subproject commit a1febd3ea3cdbd75d7f377e00c3a0e0330b97c77 diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index f7bbd6997..ba1d8a06e 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -6,6 +6,7 @@ #include "depthai/pipeline/Pipeline.hpp" #include "depthai/utility/Clock.hpp" #include "depthai/xlink/XLinkConnection.hpp" +#include "depthai-shared/device/CrashDump.hpp" // std::chrono bindings #include @@ -321,6 +322,13 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ py::class_ deviceBase(m, "DeviceBase", DOC(dai, DeviceBase)); py::class_ device(m, "Device", DOC(dai, Device)); py::class_ deviceConfig(device, "Config", DOC(dai, DeviceBase, Config)); + py::class_ crashDump(m, "CrashDump", DOC(dai, CrashDump)); + py::class_ crashReport(crashDump, "CrashReport", DOC(dai, CrashDump, CrashReport)); + py::class_ errorSourceInfo(crashReport, "ErrorSourceInfo", DOC(dai, CrashDump, CrashReport, ErrorSourceInfo)); + py::class_ assertContext(errorSourceInfo, "AssertContext", DOC(dai, CrashDump, CrashReport, ErrorSourceInfo, AssertContext)); + py::class_ trapContext(errorSourceInfo, "TrapContext", DOC(dai, CrashDump, CrashReport, ErrorSourceInfo, TrapContext)); + py::class_ threadCallstack(crashReport, "ThreadCallstack", DOC(dai, CrashDump, CrashReport, ThreadCallstack)); + py::class_ callstackContext(threadCallstack, "CallstackContext", DOC(dai, CrashDump, CrashReport, ThreadCallstack, CallstackContext)); py::class_ boardConfig(m, "BoardConfig", DOC(dai, BoardConfig)); py::class_ boardConfigUsb(boardConfig, "USB", DOC(dai, BoardConfig, USB)); py::class_ boardConfigNetwork(boardConfig, "Network", DOC(dai, BoardConfig, Network)); @@ -474,6 +482,60 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("nonExclusiveMode", &Device::Config::nonExclusiveMode) ; + // Bind CrashDump + crashDump + .def(py::init<>()) + .def_readwrite("crashReports", &CrashDump::crashReports, DOC(dai, CrashDump, crashReports)) + ; + + crashReport + .def(py::init<>()) + .def_readwrite("processor", &CrashDump::CrashReport::processor, DOC(dai, CrashDump, CrashReport, processor)) + .def_readwrite("errorSource", &CrashDump::CrashReport::errorSource, DOC(dai, CrashDump, CrashReport, errorSource)) + .def_readwrite("threadCallstack", &CrashDump::CrashReport::threadCallstack, DOC(dai, CrashDump, CrashReport, threadCallstack)) + ; + + errorSourceInfo + .def(py::init<>()) + .def_readwrite("assertContext", &CrashDump::CrashReport::ErrorSourceInfo::assertContext, DOC(dai, CrashDump, CrashReport, ErrorSourceInfo, assertContext)) + .def_readwrite("trapContext", &CrashDump::CrashReport::ErrorSourceInfo::trapContext, DOC(dai, CrashDump, CrashReport, ErrorSourceInfo, trapContext)) + .def_readwrite("errorId", &CrashDump::CrashReport::ErrorSourceInfo::errorId, DOC(dai, CrashDump, CrashReport, ErrorSourceInfo, errorId)) + ; + + assertContext + .def(py::init<>()) + .def_readwrite("fileName", &CrashDump::CrashReport::ErrorSourceInfo::AssertContext::fileName, DOC(dai, CrashDump, CrashReport, ErrorSourceInfo, AssertContext, fileName)) + .def_readwrite("functionName", &CrashDump::CrashReport::ErrorSourceInfo::AssertContext::functionName, DOC(dai, CrashDump, CrashReport, ErrorSourceInfo, AssertContext, functionName)) + .def_readwrite("line", &CrashDump::CrashReport::ErrorSourceInfo::AssertContext::line, DOC(dai, CrashDump, CrashReport, ErrorSourceInfo, AssertContext, line)) + ; + + trapContext + .def(py::init<>()) + .def_readwrite("trapNumber", &CrashDump::CrashReport::ErrorSourceInfo::TrapContext::trapNumber, DOC(dai, CrashDump, CrashReport, ErrorSourceInfo, TrapContext, trapNumber)) + .def_readwrite("trapAddress", &CrashDump::CrashReport::ErrorSourceInfo::TrapContext::trapAddress, DOC(dai, CrashDump, CrashReport, ErrorSourceInfo, TrapContext, trapAddress)) + .def_readwrite("trapName", &CrashDump::CrashReport::ErrorSourceInfo::TrapContext::trapName, DOC(dai, CrashDump, CrashReport, ErrorSourceInfo, TrapContext, trapName)) + ; + + threadCallstack + .def(py::init<>()) + .def_readwrite("threadId", &CrashDump::CrashReport::ThreadCallstack::threadId, DOC(dai, CrashDump, CrashReport, ThreadCallstack, threadId)) + .def_readwrite("threadName", &CrashDump::CrashReport::ThreadCallstack::threadName, DOC(dai, CrashDump, CrashReport, ThreadCallstack, threadName)) + .def_readwrite("stackBottom", &CrashDump::CrashReport::ThreadCallstack::stackBottom, DOC(dai, CrashDump, CrashReport, ThreadCallstack, stackBottom)) + .def_readwrite("stackTop", &CrashDump::CrashReport::ThreadCallstack::stackTop, DOC(dai, CrashDump, CrashReport, ThreadCallstack, stackTop)) + .def_readwrite("stackPointer", &CrashDump::CrashReport::ThreadCallstack::stackPointer, DOC(dai, CrashDump, CrashReport, ThreadCallstack, stackPointer)) + .def_readwrite("instructionPointer", &CrashDump::CrashReport::ThreadCallstack::instructionPointer, DOC(dai, CrashDump, CrashReport, ThreadCallstack, instructionPointer)) + .def_readwrite("threadStatus", &CrashDump::CrashReport::ThreadCallstack::threadStatus, DOC(dai, CrashDump, CrashReport, ThreadCallstack, threadStatus)) + .def_readwrite("callStack", &CrashDump::CrashReport::ThreadCallstack::callStack, DOC(dai, CrashDump, CrashReport, ThreadCallstack, callStack)) + ; + + callstackContext + .def(py::init<>()) + .def_readwrite("callSite", &CrashDump::CrashReport::ThreadCallstack::CallstackContext::callSite, DOC(dai, CrashDump, CrashReport, ThreadCallstack, CallstackContext, callSite)) + .def_readwrite("calledTarget", &CrashDump::CrashReport::ThreadCallstack::CallstackContext::calledTarget, DOC(dai, CrashDump, CrashReport, ThreadCallstack, CallstackContext, calledTarget)) + .def_readwrite("framePointer", &CrashDump::CrashReport::ThreadCallstack::CallstackContext::framePointer, DOC(dai, CrashDump, CrashReport, ThreadCallstack, CallstackContext, framePointer)) + .def_readwrite("context", &CrashDump::CrashReport::ThreadCallstack::CallstackContext::context, DOC(dai, CrashDump, CrashReport, ThreadCallstack, CallstackContext, context)) + ; + // Bind constructors bindConstructors(deviceBase); // Bind the rest @@ -521,6 +583,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def("getLogLevel", [](DeviceBase& d) { py::gil_scoped_release release; return d.getLogLevel(); }, DOC(dai, DeviceBase, getLogLevel)) .def("setSystemInformationLoggingRate", [](DeviceBase& d, float hz) { py::gil_scoped_release release; d.setSystemInformationLoggingRate(hz); }, py::arg("rateHz"), DOC(dai, DeviceBase, setSystemInformationLoggingRate)) .def("getSystemInformationLoggingRate", [](DeviceBase& d) { py::gil_scoped_release release; return d.getSystemInformationLoggingRate(); }, DOC(dai, DeviceBase, getSystemInformationLoggingRate)) + .def("getCrashDump", [](DeviceBase& d) { py::gil_scoped_release release; return d.getCrashDump(); }, DOC(dai, DeviceBase, getCrashDump)) .def("getConnectedCameras", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedCameras(); }, DOC(dai, DeviceBase, getConnectedCameras)) .def("getConnectedCameraFeatures", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedCameraFeatures(); }, DOC(dai, DeviceBase, getConnectedCameraFeatures)) .def("getCameraSensorNames", [](DeviceBase& d) { py::gil_scoped_release release; return d.getCameraSensorNames(); }, DOC(dai, DeviceBase, getCameraSensorNames)) From d1162f3401d34881e1af23cff787146a7d831d49 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 10 Feb 2023 20:34:09 +0200 Subject: [PATCH 167/385] Add commit hash to crash dump --- depthai-core | 2 +- src/DeviceBindings.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index a1febd3ea..b7a4cb7c5 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit a1febd3ea3cdbd75d7f377e00c3a0e0330b97c77 +Subproject commit b7a4cb7c5d77873c46077032ba91f525712efe02 diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index ba1d8a06e..842a5b991 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -486,6 +486,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ crashDump .def(py::init<>()) .def_readwrite("crashReports", &CrashDump::crashReports, DOC(dai, CrashDump, crashReports)) + .def_readwrite("depthaiCommitHash", &CrashDump::depthaiCommitHash, DOC(dai, CrashDump, depthaiCommitHash)) ; crashReport From b52e3476833b1e66bf5ee033cbcc16f4979fcffe Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 10 Feb 2023 23:20:44 +0200 Subject: [PATCH 168/385] Add json serializer to crash dump --- examples/CrashReport/crash_report.py | 18 ++++++++++++++++++ src/DeviceBindings.cpp | 2 ++ 2 files changed, 20 insertions(+) create mode 100755 examples/CrashReport/crash_report.py diff --git a/examples/CrashReport/crash_report.py b/examples/CrashReport/crash_report.py new file mode 100755 index 000000000..b06e20cd6 --- /dev/null +++ b/examples/CrashReport/crash_report.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +import cv2 +import depthai as dai +import json + +# Connect to device and start pipeline +with dai.Device() as device: + + crashDump = device.getCrashDump() + if len(crashDump.crashReports) == 0: + print("There was no crash dump found on your device!") + else: + json = crashDump.serializeToJson() + print(json) + destPath = "crashDump.json" + with open(destPath, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=4) diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 842a5b991..99a8a21c0 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -485,6 +485,8 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ // Bind CrashDump crashDump .def(py::init<>()) + .def("serializeToJson", &CrashDump::serializeToJson, DOC(dai, CrashDump, serializeToJson)) + .def_readwrite("crashReports", &CrashDump::crashReports, DOC(dai, CrashDump, crashReports)) .def_readwrite("depthaiCommitHash", &CrashDump::depthaiCommitHash, DOC(dai, CrashDump, depthaiCommitHash)) ; From 2dc766518510d6b74ad252954b605df96ac5a856 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 10 Feb 2023 23:26:20 +0200 Subject: [PATCH 169/385] Update example --- depthai-core | 2 +- examples/CrashReport/crash_report.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/depthai-core b/depthai-core index b7a4cb7c5..3fc14d554 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit b7a4cb7c5d77873c46077032ba91f525712efe02 +Subproject commit 3fc14d55410bbb5d373db25588ebca094c8cf82f diff --git a/examples/CrashReport/crash_report.py b/examples/CrashReport/crash_report.py index b06e20cd6..498a6f944 100755 --- a/examples/CrashReport/crash_report.py +++ b/examples/CrashReport/crash_report.py @@ -2,7 +2,7 @@ import cv2 import depthai as dai -import json +from json import dump # Connect to device and start pipeline with dai.Device() as device: @@ -15,4 +15,4 @@ print(json) destPath = "crashDump.json" with open(destPath, 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False, indent=4) + dump(json, f, ensure_ascii=False, indent=4) From 419bab23628ec83fced3cccea8e5a3c23264dfe0 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 13 Feb 2023 16:21:31 +0200 Subject: [PATCH 170/385] Add hasCrashDump API --- depthai-core | 2 +- src/DeviceBindings.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 3fc14d554..80af184a9 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 3fc14d55410bbb5d373db25588ebca094c8cf82f +Subproject commit 80af184a946cd627bd7d9db61d71d2839f7b67cf diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 99a8a21c0..e95c1dcb9 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -587,6 +587,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def("setSystemInformationLoggingRate", [](DeviceBase& d, float hz) { py::gil_scoped_release release; d.setSystemInformationLoggingRate(hz); }, py::arg("rateHz"), DOC(dai, DeviceBase, setSystemInformationLoggingRate)) .def("getSystemInformationLoggingRate", [](DeviceBase& d) { py::gil_scoped_release release; return d.getSystemInformationLoggingRate(); }, DOC(dai, DeviceBase, getSystemInformationLoggingRate)) .def("getCrashDump", [](DeviceBase& d) { py::gil_scoped_release release; return d.getCrashDump(); }, DOC(dai, DeviceBase, getCrashDump)) + .def("hasCrashDump", [](DeviceBase& d) { py::gil_scoped_release release; return d.hasCrashDump(); }, DOC(dai, DeviceBase, hasCrashDump)) .def("getConnectedCameras", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedCameras(); }, DOC(dai, DeviceBase, getConnectedCameras)) .def("getConnectedCameraFeatures", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedCameraFeatures(); }, DOC(dai, DeviceBase, getConnectedCameraFeatures)) .def("getCameraSensorNames", [](DeviceBase& d) { py::gil_scoped_release release; return d.getCameraSensorNames(); }, DOC(dai, DeviceBase, getCameraSensorNames)) From e570d5876ef79c69959bf36248749db62dcf5799 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 13 Feb 2023 16:24:03 +0200 Subject: [PATCH 171/385] Update crash report example --- examples/CrashReport/crash_report.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/CrashReport/crash_report.py b/examples/CrashReport/crash_report.py index 498a6f944..390d788a4 100755 --- a/examples/CrashReport/crash_report.py +++ b/examples/CrashReport/crash_report.py @@ -7,12 +7,15 @@ # Connect to device and start pipeline with dai.Device() as device: - crashDump = device.getCrashDump() - if len(crashDump.crashReports) == 0: - print("There was no crash dump found on your device!") - else: + if device.hasCrashDump(): + crashDump = device.getCrashDump() + json = crashDump.serializeToJson() print(json) + destPath = "crashDump.json" with open(destPath, 'w', encoding='utf-8') as f: dump(json, f, ensure_ascii=False, indent=4) + else: + print("There was no crash dump found on your device!") + From ba8fdc53c4b12c543c69eef106b8357ae3f1628f Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 13 Feb 2023 21:47:01 +0200 Subject: [PATCH 172/385] Update core/FW --- depthai-core | 2 +- src/DeviceBindings.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 80af184a9..f747c2f79 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 80af184a946cd627bd7d9db61d71d2839f7b67cf +Subproject commit f747c2f79230f8188356b78151c077ab204f30f0 diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index e95c1dcb9..fe418909d 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -495,6 +495,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def(py::init<>()) .def_readwrite("processor", &CrashDump::CrashReport::processor, DOC(dai, CrashDump, CrashReport, processor)) .def_readwrite("errorSource", &CrashDump::CrashReport::errorSource, DOC(dai, CrashDump, CrashReport, errorSource)) + .def_readwrite("crashedThreadId", &CrashDump::CrashReport::crashedThreadId, DOC(dai, CrashDump, CrashReport, crashedThreadId)) .def_readwrite("threadCallstack", &CrashDump::CrashReport::threadCallstack, DOC(dai, CrashDump, CrashReport, threadCallstack)) ; From 76cfa60790e3003b3095b513ef8279d35127365e Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 14 Feb 2023 18:04:28 +0200 Subject: [PATCH 173/385] Update FW, crash dump contains device ID --- depthai-core | 2 +- examples/CrashReport/crash_report.py | 21 +++++++++++++++++---- src/DeviceBindings.cpp | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/depthai-core b/depthai-core index f747c2f79..de15c2715 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit f747c2f79230f8188356b78151c077ab204f30f0 +Subproject commit de15c271571d60c2a53dc9a5d44ecdb3cf8d6e4d diff --git a/examples/CrashReport/crash_report.py b/examples/CrashReport/crash_report.py index 390d788a4..a68b48ade 100755 --- a/examples/CrashReport/crash_report.py +++ b/examples/CrashReport/crash_report.py @@ -3,19 +3,32 @@ import cv2 import depthai as dai from json import dump +from os.path import exists # Connect to device and start pipeline with dai.Device() as device: if device.hasCrashDump(): crashDump = device.getCrashDump() + commitHash = crashDump.depthaiCommitHash + deviceId = crashDump.deviceId json = crashDump.serializeToJson() - print(json) - destPath = "crashDump.json" - with open(destPath, 'w', encoding='utf-8') as f: - dump(json, f, ensure_ascii=False, indent=4) + i = -1 + while True: + i += 1 + destPath = "crashDump_" + str(i) + "_" + deviceId + "_" + commitHash + ".json" + if exists(destPath): + continue + + with open(destPath, 'w', encoding='utf-8') as f: + dump(json, f, ensure_ascii=False, indent=4) + + print("Crash dump found on your device!") + print(f"Saved to {destPath}") + print("Please report to developers!") + break else: print("There was no crash dump found on your device!") diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index fe418909d..1319e38ff 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -489,6 +489,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("crashReports", &CrashDump::crashReports, DOC(dai, CrashDump, crashReports)) .def_readwrite("depthaiCommitHash", &CrashDump::depthaiCommitHash, DOC(dai, CrashDump, depthaiCommitHash)) + .def_readwrite("deviceId", &CrashDump::deviceId, DOC(dai, CrashDump, deviceId)) ; crashReport From 9c3e9405ab6eab2ff5ccd3f593c05663bf668dfd Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 14 Feb 2023 21:59:33 +0200 Subject: [PATCH 174/385] Enable MEDAIN spatial calculation method for SpatialDetectionNetwork --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 11d175b2b..605909895 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 11d175b2b605686c3f4655362f9d1a81ce25ad70 +Subproject commit 605909895cba9f8241750287b2bd7c7725020fa5 From 19c950bfe851a28d2a272c10ecc441f1fa86584e Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 14 Feb 2023 22:05:58 +0200 Subject: [PATCH 175/385] Update core --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 605909895..9e4af24b4 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 605909895cba9f8241750287b2bd7c7725020fa5 +Subproject commit 9e4af24b439fd3ebf76265d03ce188fc2e3a5787 From 7bd6f7113f3903c53a834c671369406560eab830 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 15 Feb 2023 17:20:54 +0200 Subject: [PATCH 176/385] Update core/examples --- depthai-core | 2 +- examples/ImageManip/image_manip_warp_mesh.py | 0 examples/MonoCamera/mono_preview_alternate_pro.py | 0 examples/NeuralNetwork/concat_multi_input.py | 0 examples/NeuralNetwork/detection_parser.py | 10 +++++----- examples/Script/script_change_pipeline_flow.py | 0 examples/Script/script_forward_frames.py | 0 examples/Warp/warp_mesh.py | 0 examples/Warp/warp_mesh_interactive.py | 0 examples/bootloader/poe_set_ip.py | 0 examples/bootloader/read_flash_memory.py | 0 examples/device/device_all_boot_bootloader.py | 0 examples/device/device_boot_non_exclusive.py | 0 examples/host_side/device_information.py | 0 examples/host_side/latency_measurement.py | 0 examples/mixed/frame_sync.py | 0 examples/mixed/multiple_devices.py | 0 examples/mixed/report_camera_settings.py | 0 18 files changed, 6 insertions(+), 6 deletions(-) mode change 100644 => 100755 examples/ImageManip/image_manip_warp_mesh.py mode change 100644 => 100755 examples/MonoCamera/mono_preview_alternate_pro.py mode change 100644 => 100755 examples/NeuralNetwork/concat_multi_input.py mode change 100644 => 100755 examples/Script/script_change_pipeline_flow.py mode change 100644 => 100755 examples/Script/script_forward_frames.py mode change 100644 => 100755 examples/Warp/warp_mesh.py mode change 100644 => 100755 examples/Warp/warp_mesh_interactive.py mode change 100644 => 100755 examples/bootloader/poe_set_ip.py mode change 100644 => 100755 examples/bootloader/read_flash_memory.py mode change 100644 => 100755 examples/device/device_all_boot_bootloader.py mode change 100644 => 100755 examples/device/device_boot_non_exclusive.py mode change 100644 => 100755 examples/host_side/device_information.py mode change 100644 => 100755 examples/host_side/latency_measurement.py mode change 100644 => 100755 examples/mixed/frame_sync.py mode change 100644 => 100755 examples/mixed/multiple_devices.py mode change 100644 => 100755 examples/mixed/report_camera_settings.py diff --git a/depthai-core b/depthai-core index 9e4af24b4..708d9a816 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 9e4af24b439fd3ebf76265d03ce188fc2e3a5787 +Subproject commit 708d9a816f4c0cec622dbd2942f6461b14470b6b diff --git a/examples/ImageManip/image_manip_warp_mesh.py b/examples/ImageManip/image_manip_warp_mesh.py old mode 100644 new mode 100755 diff --git a/examples/MonoCamera/mono_preview_alternate_pro.py b/examples/MonoCamera/mono_preview_alternate_pro.py old mode 100644 new mode 100755 diff --git a/examples/NeuralNetwork/concat_multi_input.py b/examples/NeuralNetwork/concat_multi_input.py old mode 100644 new mode 100755 diff --git a/examples/NeuralNetwork/detection_parser.py b/examples/NeuralNetwork/detection_parser.py index 340fb6040..2630e2c3b 100755 --- a/examples/NeuralNetwork/detection_parser.py +++ b/examples/NeuralNetwork/detection_parser.py @@ -42,11 +42,11 @@ nn.setNumInferenceThreads(2) nn.input.setBlocking(False) -blob = dai.OpenVINO.Blob(args.nnPath); -nn.setBlob(blob); -det.setBlob(blob); -det.setNNFamily(dai.DetectionNetworkType.MOBILENET); -det.setConfidenceThreshold(0.5); +blob = dai.OpenVINO.Blob(args.nnPath) +nn.setBlob(blob) +det.setBlob(blob) +det.setNNFamily(dai.DetectionNetworkType.MOBILENET) +det.setConfidenceThreshold(0.5) # Linking if args.sync: diff --git a/examples/Script/script_change_pipeline_flow.py b/examples/Script/script_change_pipeline_flow.py old mode 100644 new mode 100755 diff --git a/examples/Script/script_forward_frames.py b/examples/Script/script_forward_frames.py old mode 100644 new mode 100755 diff --git a/examples/Warp/warp_mesh.py b/examples/Warp/warp_mesh.py old mode 100644 new mode 100755 diff --git a/examples/Warp/warp_mesh_interactive.py b/examples/Warp/warp_mesh_interactive.py old mode 100644 new mode 100755 diff --git a/examples/bootloader/poe_set_ip.py b/examples/bootloader/poe_set_ip.py old mode 100644 new mode 100755 diff --git a/examples/bootloader/read_flash_memory.py b/examples/bootloader/read_flash_memory.py old mode 100644 new mode 100755 diff --git a/examples/device/device_all_boot_bootloader.py b/examples/device/device_all_boot_bootloader.py old mode 100644 new mode 100755 diff --git a/examples/device/device_boot_non_exclusive.py b/examples/device/device_boot_non_exclusive.py old mode 100644 new mode 100755 diff --git a/examples/host_side/device_information.py b/examples/host_side/device_information.py old mode 100644 new mode 100755 diff --git a/examples/host_side/latency_measurement.py b/examples/host_side/latency_measurement.py old mode 100644 new mode 100755 diff --git a/examples/mixed/frame_sync.py b/examples/mixed/frame_sync.py old mode 100644 new mode 100755 diff --git a/examples/mixed/multiple_devices.py b/examples/mixed/multiple_devices.py old mode 100644 new mode 100755 diff --git a/examples/mixed/report_camera_settings.py b/examples/mixed/report_camera_settings.py old mode 100644 new mode 100755 From 85d93c0091d246e1ed10be4effaff896c7c3ffa3 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 17 Feb 2023 18:15:40 +0100 Subject: [PATCH 177/385] ImagegConfig docs update --- docs/source/components/messages/image_manip_config.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/source/components/messages/image_manip_config.rst b/docs/source/components/messages/image_manip_config.rst index 85ac597f9..5535536f5 100644 --- a/docs/source/components/messages/image_manip_config.rst +++ b/docs/source/components/messages/image_manip_config.rst @@ -2,10 +2,9 @@ ImageManipConfig ================ This message can is used for cropping, warping, rotating, resizing, etc. an image in runtime. -It is sent either from the host to :ref:`ColorCamera` or :ref:`ImageManip`. +It can be sent from host/:ref:`Script` node to either :ref:`ColorCamera` or :ref:`ImageManip`. -.. - It is sent either from the host or from the :ref:`Script` node to :ref:`ColorCamera` or :ref:`ImageManip`. +.. note:: This message will reconfigure the whole config of the node, meaning you need to set all settings, not just the setting you want to change. Examples of functionality ######################### From 613107a83d54fd42f672cbb3ca684b6866df5c76 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 17 Feb 2023 18:15:55 +0100 Subject: [PATCH 178/385] Low latency docs update (bandwidth) --- docs/source/tutorials/low-latency.rst | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/source/tutorials/low-latency.rst b/docs/source/tutorials/low-latency.rst index 376b7cd1f..c7ab2f092 100644 --- a/docs/source/tutorials/low-latency.rst +++ b/docs/source/tutorials/low-latency.rst @@ -128,6 +128,27 @@ On PoE, the latency can vary quite a bit due to a number of factors: * 100% OAK Leon CSS (CPU) usage. The Leon CSS core handles the POE communication (`see docs here `__), and if the CPU is 100% used, it will not be able to handle the communication as fast as it should. * Another potential way to improve PoE latency would be to fine-tune network settings, like MTU, TCP window size, etc. (see `here `__ for more info) +Bandwidth +######### + +With large, unencoded frames, one can quickly saturate the bandwidth even at 30FPS, especially on PoE devices (1gbps link): + +.. code-block::bash + + 720P NV12/YUV420 frames: 1280 * 720 * 1.5 * 30fps * 8bits = 331 mbps + 1080P NV12/YUV420 frames: 1920 * 1080 * 1.5 * 30fps * 8bits = 747 mbps + 1080P RGB frames: 1920 * 1080 * 3 * 30fps * 8bits = 1.5 gbps + 4K NV12/YUV420 frames: 3840 * 2160 * 1.5 * 30fps * 8bits = 3 gbps + 800P depth frames: 1280 * 800 * 2 * 30fps * 8bits = 492 mbps + 400P depth frames: 640 * 400 * 2 * 30fps * 8bits = 123 mbps + +The third value in the formula is byte/pixel, which is 1.5 for NV12/YUV420, 3 for RGB, and 2 for depth frames. + +A few options to reduce bandwidth: + +- Encode frames (H.264, H.265, MJPEG) on-device using :ref:`VideoEncoder node ` +- Reduce FPS/resolution/number of streams + Reducing latency when running NN ################################ @@ -153,11 +174,11 @@ This time includes the following: - And finally, eventual extra latency until it reaches the app Note: if the FPS is increased slightly more, towards 19..21 FPS, an extra latency of about 10ms appears, that we believe -is related to firmware. We are activaly looking for improvements for lower latencies. +is related to firmware. We are actively looking for improvements for lower latencies. -NN input queue size and blocking behaviour ------------------------------------------- +NN input queue size and blocking behavior +----------------------------------------- If the app has ``detNetwork.input.setBlocking(False)``, but the queue size doesn't change, the following adjustment may help improve latency performance: From ff45e07b3fa38049959e4ae31e7ffc7bee6d985d Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 17 Feb 2023 18:16:12 +0100 Subject: [PATCH 179/385] RGB depth alignment add docs on high-res color stream --- docs/source/samples/StereoDepth/rgb_depth_aligned.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/samples/StereoDepth/rgb_depth_aligned.rst b/docs/source/samples/StereoDepth/rgb_depth_aligned.rst index fbdd16264..4c854cbe6 100644 --- a/docs/source/samples/StereoDepth/rgb_depth_aligned.rst +++ b/docs/source/samples/StereoDepth/rgb_depth_aligned.rst @@ -12,6 +12,9 @@ By default, the depth map will get scaled to match the resolution of the camera depth is aligned to the 1080P color sensor, StereoDepth will upscale depth to 1080P as well. Depth scaling can be avoided by configuring :ref:`StereoDepth`'s ``stereo.setOutputSize(width, height)``. +To align depth with **higher resolution color stream** (eg. 12MP), you need to limit the resolution of the depth map. You can +do that with ``stereo.setOutputSize(w,h)``. Code `example here `__. + Demo #### From cd37a3947d950fdeeea03acb64aa74111005ca37 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 17 Feb 2023 18:16:33 +0100 Subject: [PATCH 180/385] VideoENcoder docs elaboration on lossy codecs --- docs/source/components/nodes/video_encoder.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/components/nodes/video_encoder.rst b/docs/source/components/nodes/video_encoder.rst index 060d36c1c..4f899de7a 100644 --- a/docs/source/components/nodes/video_encoder.rst +++ b/docs/source/components/nodes/video_encoder.rst @@ -2,7 +2,7 @@ VideoEncoder ============ VideoEncoder node is used to encode :ref:`ImgFrame` into either H264, H265, or MJPEG streams. Only NV12 or GRAY8 (which gets converted to NV12) format is -supported as an input. +supported as an input. All codecs are lossy (except lossless MJPEG), for more information please see `encoding quality docs `__. .. include:: /includes/container-encoding.rst From 82c0e7ee16d3be6444315689239e297e421066f4 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Sun, 19 Feb 2023 23:06:19 +0200 Subject: [PATCH 181/385] FW: HW sync (trigger mode) enabled for OAK-D-LR, for cameras with matching FPS --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 708d9a816..47d6fffa4 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 708d9a816f4c0cec622dbd2942f6461b14470b6b +Subproject commit 47d6fffa42dfa3a4d32b4389addaedbf92f70c23 From 91d769708ea4b27a1ae3a562673ed5c6fdb93e33 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 20 Feb 2023 15:11:08 +0100 Subject: [PATCH 182/385] Updated low latency to include docs about allocating resources for max fps vs lowest latency --- docs/source/tutorials/low-latency.rst | 77 ++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/docs/source/tutorials/low-latency.rst b/docs/source/tutorials/low-latency.rst index c7ab2f092..72358b398 100644 --- a/docs/source/tutorials/low-latency.rst +++ b/docs/source/tutorials/low-latency.rst @@ -135,14 +135,20 @@ With large, unencoded frames, one can quickly saturate the bandwidth even at 30F .. code-block::bash - 720P NV12/YUV420 frames: 1280 * 720 * 1.5 * 30fps * 8bits = 331 mbps + 4K NV12/YUV420 frames: 3840 * 2160 * 1.5 * 30fps * 8bits = 3 gbps 1080P NV12/YUV420 frames: 1920 * 1080 * 1.5 * 30fps * 8bits = 747 mbps + 720P NV12/YUV420 frames: 1280 * 720 * 1.5 * 30fps * 8bits = 331 mbps + 1080P RGB frames: 1920 * 1080 * 3 * 30fps * 8bits = 1.5 gbps - 4K NV12/YUV420 frames: 3840 * 2160 * 1.5 * 30fps * 8bits = 3 gbps + 800P depth frames: 1280 * 800 * 2 * 30fps * 8bits = 492 mbps 400P depth frames: 640 * 400 * 2 * 30fps * 8bits = 123 mbps -The third value in the formula is byte/pixel, which is 1.5 for NV12/YUV420, 3 for RGB, and 2 for depth frames. + 800P mono frames: 1280 * 800 * 1 * 30fps * 8bits = 246 mbps + 400P mono frames: 640 * 400 * 1 * 30fps * 8bits = 62 mbps + +The third value in the formula is byte/pixel, which is 1.5 for NV12/YUV420, 3 for RGB, and 2 for depth frames, and 1 +for mono (grayscale) frames. It's either 1 (normal) or 2 (subpixel mode) for disparity frames. A few options to reduce bandwidth: @@ -155,8 +161,65 @@ Reducing latency when running NN In the examples above we were only streaming frames, without doing anything else on the OAK camera. This section will focus on how to reduce latency when also running NN model on the OAK. -Lowering camera FPS to match NN FPS ------------------------------------ +1. Increasing NN resources +-------------------------- + +One option to reduce latency is to increase the NN resources. This can be done by changing the number of allocated NCEs and SHAVES (see HW accelerator `docs here `__). +`Compile Tool `__ can compile a model for more SHAVE cores. To allocate more NCEs, you can use API below: + +.. code-block:: python + + import depthai as dai + + pipeline = dai.Pipeline() + # nn = pipeline.createNeuralNetwork() + # nn = pipeline.create(dai.node.MobileNetDetectionNetwork) + nn = pipeline.create(dai.node.YoloDetectionNetwork) + nn.setNumInferenceThreads(1) # By default 2 threads are used + nn.setNumNCEPerInferenceThread(2) # By default, 1 NCE is used per thread + +Models usually run at **max FPS** when using 2 threads (1 NCE/Thread), and compiling model for ``AVAILABLE_SHAVES / 2``. + +Example of FPS & latency comparison for YoloV7-tiny: + +.. list-table:: + :header-rows: 1 + + * - NN resources + - Camera FPS + - Latency + - NN FPS + * - **6 SHAVEs, 2x Threads (1NCE/Thread)** + - 15 + - 155 ms + - 15 + * - 6 SHAVEs, 2x Threads (1NCE/Thread) + - 14 + - 149 ms + - 14 + * - 6 SHAVEs, 2x Threads (1NCE/Thread) + - 13 + - 146 ms + - 13 + * - 6 SHAVEs, 2x Threads (1NCE/Thread) + - 10 + - 141 ms + - 10 + * - **13 SHAVEs, 1x Thread (2NCE/Thread)** + - 30 + - 145 ms + - 11.6 + * - 13 SHAVEs, 1x Thread (2NCE/Thread) + - 12 + - 128 ms + - 12 + * - 13 SHAVEs, 1x Thread (2NCE/Thread) + - 10 + - 118 ms + - 10 + +2. Lowering camera FPS to match NN FPS +-------------------------------------- Lowering FPS to not exceed NN capabilities typically provides the best latency performance, since the NN is able to start the inference as soon as a new frame is available. @@ -177,8 +240,8 @@ Note: if the FPS is increased slightly more, towards 19..21 FPS, an extra latenc is related to firmware. We are actively looking for improvements for lower latencies. -NN input queue size and blocking behavior ------------------------------------------ +3. NN input queue size and blocking behavior +-------------------------------------------- If the app has ``detNetwork.input.setBlocking(False)``, but the queue size doesn't change, the following adjustment may help improve latency performance: From c104d623a5588b7328ef10ea103f1d8326c41b95 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 20 Feb 2023 16:27:01 +0100 Subject: [PATCH 183/385] Updated linux instal docs --- docs/source/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/install.rst b/docs/source/install.rst index 020b76b61..c72fbee2b 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -31,7 +31,7 @@ Follow the steps below to just install depthai api library dependencies for your .. code-block:: bash - sudo wget -qO- https://docs.luxonis.com/install_depthai.sh | bash + sudo wget -qO- https://docs.luxonis.com/install_dependencies.sh | bash Please refer to :ref:`Supported Platforms` if any issues occur. From 821e4b318922b5d3e98c9f374740811d800e15d3 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 20 Feb 2023 18:58:53 +0200 Subject: [PATCH 184/385] Add missing binding to SpatialLocations --- src/pipeline/datatype/SpatialLocationCalculatorDataBindings.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pipeline/datatype/SpatialLocationCalculatorDataBindings.cpp b/src/pipeline/datatype/SpatialLocationCalculatorDataBindings.cpp index 52c6a4853..98ff81754 100644 --- a/src/pipeline/datatype/SpatialLocationCalculatorDataBindings.cpp +++ b/src/pipeline/datatype/SpatialLocationCalculatorDataBindings.cpp @@ -37,6 +37,8 @@ void bind_spatiallocationcalculatordata(pybind11::module& m, void* pCallstack){ .def(py::init<>()) .def_readwrite("config", &SpatialLocations::config, DOC(dai, SpatialLocations, config)) .def_readwrite("depthAverage", &SpatialLocations::depthAverage, DOC(dai, SpatialLocations, depthAverage)) + .def_readwrite("depthMode", &SpatialLocations::depthMode, DOC(dai, SpatialLocations, depthMode)) + .def_readwrite("depthMedian", &SpatialLocations::depthMedian, DOC(dai, SpatialLocations, depthMedian)) .def_readwrite("depthMin", &SpatialLocations::depthMin, DOC(dai, SpatialLocations, depthMin)) .def_readwrite("depthMax", &SpatialLocations::depthMax, DOC(dai, SpatialLocations, depthMax)) .def_readwrite("depthAveragePixelCount", &SpatialLocations::depthAveragePixelCount, DOC(dai, SpatialLocations, depthAveragePixelCount)) From 766c7283c055c2749cf606e1a5d6315b6f0cfa82 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 20 Feb 2023 18:13:46 +0100 Subject: [PATCH 185/385] Updated favicon --- docs/source/_static/images/favicon.png | Bin 6290 -> 2256 bytes docs/source/_static/images/logo.png | Bin 35472 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/source/_static/images/logo.png diff --git a/docs/source/_static/images/favicon.png b/docs/source/_static/images/favicon.png index 2bbaad0ecd427e58e7479766688bdd03caa4a1da..decd05d31ed4363e55ec6c568b568af9b69e6db6 100644 GIT binary patch literal 2256 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 diff --git a/docs/source/_static/images/logo.png b/docs/source/_static/images/logo.png deleted file mode 100644 index 5f294fb91ef67eac6b98859f99bf1ccbc538dc63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35472 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 From 9a1a60cf5e177c92c98cd3b966acff9e3940e2d2 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 21 Feb 2023 17:03:05 +0200 Subject: [PATCH 186/385] Change default SIPP buffer sizes --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 47d6fffa4..244ffbaad 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 47d6fffa42dfa3a4d32b4389addaedbf92f70c23 +Subproject commit 244ffbaad7b89990b102224f4da0980a46044948 From 43cb3c2cfd9aaa71c48492d1b4de6fc320baf9ef Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Thu, 23 Feb 2023 15:23:04 +0200 Subject: [PATCH 187/385] Add 3A skipping option to reduce CPU usage --- depthai-core | 2 +- src/pipeline/node/CameraBindings.cpp | 1 + src/pipeline/node/ColorCameraBindings.cpp | 1 + src/pipeline/node/MonoCameraBindings.cpp | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 244ffbaad..0b6e48472 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 244ffbaad7b89990b102224f4da0980a46044948 +Subproject commit 0b6e48472b22b7b1ea4713859ea361b147604649 diff --git a/src/pipeline/node/CameraBindings.cpp b/src/pipeline/node/CameraBindings.cpp index 421d0a7b9..1b79c040c 100644 --- a/src/pipeline/node/CameraBindings.cpp +++ b/src/pipeline/node/CameraBindings.cpp @@ -117,6 +117,7 @@ void bind_camera(pybind11::module& m, void* pCallstack){ // .def("setResolution", &Camera::setResolution, py::arg("resolution"), DOC(dai, node, Camera, setResolution)) // .def("getResolution", &Camera::getResolution, DOC(dai, node, Camera, getResolution)) .def("setFps", &Camera::setFps, py::arg("fps"), DOC(dai, node, Camera, setFps)) + .def("set3AFpsDenominator", &Camera::set3AFpsDenominator, DOC(dai, node, Camera, set3AFpsDenominator)) .def("getFps", &Camera::getFps, DOC(dai, node, Camera, getFps)) .def("getPreviewSize", &Camera::getPreviewSize, DOC(dai, node, Camera, getPreviewSize)) .def("getPreviewWidth", &Camera::getPreviewWidth, DOC(dai, node, Camera, getPreviewWidth)) diff --git a/src/pipeline/node/ColorCameraBindings.cpp b/src/pipeline/node/ColorCameraBindings.cpp index 143e3c5ff..37580cc7f 100644 --- a/src/pipeline/node/ColorCameraBindings.cpp +++ b/src/pipeline/node/ColorCameraBindings.cpp @@ -123,6 +123,7 @@ void bind_colorcamera(pybind11::module& m, void* pCallstack){ .def("setResolution", &ColorCamera::setResolution, py::arg("resolution"), DOC(dai, node, ColorCamera, setResolution)) .def("getResolution", &ColorCamera::getResolution, DOC(dai, node, ColorCamera, getResolution)) .def("setFps", &ColorCamera::setFps, py::arg("fps"), DOC(dai, node, ColorCamera, setFps)) + .def("set3AFpsDenominator", &ColorCamera::set3AFpsDenominator, DOC(dai, node, ColorCamera, set3AFpsDenominator)) .def("getFps", &ColorCamera::getFps, DOC(dai, node, ColorCamera, getFps)) .def("setFrameEventFilter", &ColorCamera::setFrameEventFilter, py::arg("events"), DOC(dai, node, ColorCamera, setFrameEventFilter)) .def("getFrameEventFilter", &ColorCamera::getFrameEventFilter, DOC(dai, node, ColorCamera, getFrameEventFilter)) diff --git a/src/pipeline/node/MonoCameraBindings.cpp b/src/pipeline/node/MonoCameraBindings.cpp index 5ff0bf685..a80f0625c 100644 --- a/src/pipeline/node/MonoCameraBindings.cpp +++ b/src/pipeline/node/MonoCameraBindings.cpp @@ -80,6 +80,7 @@ void bind_monocamera(pybind11::module& m, void* pCallstack){ .def("setFrameEventFilter", &MonoCamera::setFrameEventFilter, py::arg("events"), DOC(dai, node, MonoCamera, setFrameEventFilter)) .def("getFrameEventFilter", &MonoCamera::getFrameEventFilter, DOC(dai, node, MonoCamera, getFrameEventFilter)) .def("setFps", &MonoCamera::setFps, py::arg("fps"), DOC(dai, node, MonoCamera, setFps)) + .def("set3AFpsDenominator", &MonoCamera::set3AFpsDenominator, DOC(dai, node, MonoCamera, set3AFpsDenominator)) .def("getFps", &MonoCamera::getFps, DOC(dai, node, MonoCamera, getFps)) .def("getResolutionSize", &MonoCamera::getResolutionSize, DOC(dai, node, MonoCamera, getResolutionSize)) .def("getResolutionWidth", &MonoCamera::getResolutionWidth, DOC(dai, node, MonoCamera, getResolutionWidth)) From 0d9b14b2787734d83937bf579147082d757c9f60 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Sat, 25 Feb 2023 15:11:35 +0200 Subject: [PATCH 188/385] Change API to be able to configure isp 3A FPS --- depthai-core | 2 +- src/pipeline/node/CameraBindings.cpp | 3 ++- src/pipeline/node/ColorCameraBindings.cpp | 3 ++- src/pipeline/node/MonoCameraBindings.cpp | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/depthai-core b/depthai-core index 0b6e48472..69a99c6ff 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 0b6e48472b22b7b1ea4713859ea361b147604649 +Subproject commit 69a99c6ff1bda5c86ce0096be06649fd445ac455 diff --git a/src/pipeline/node/CameraBindings.cpp b/src/pipeline/node/CameraBindings.cpp index 1b79c040c..d99d0eab0 100644 --- a/src/pipeline/node/CameraBindings.cpp +++ b/src/pipeline/node/CameraBindings.cpp @@ -72,6 +72,7 @@ void bind_camera(pybind11::module& m, void* pCallstack){ .def_readwrite("stillWidth", &CameraProperties::stillWidth) // .def_readwrite("resolution", &CameraProperties::resolution) .def_readwrite("fps", &CameraProperties::fps) + .def_readwrite("isp3aFps", &CameraProperties::isp3aFps) .def_readwrite("sensorCropX", &CameraProperties::sensorCropX) .def_readwrite("sensorCropY", &CameraProperties::sensorCropY) .def_readwrite("previewKeepAspectRatio", &CameraProperties::previewKeepAspectRatio) @@ -117,7 +118,7 @@ void bind_camera(pybind11::module& m, void* pCallstack){ // .def("setResolution", &Camera::setResolution, py::arg("resolution"), DOC(dai, node, Camera, setResolution)) // .def("getResolution", &Camera::getResolution, DOC(dai, node, Camera, getResolution)) .def("setFps", &Camera::setFps, py::arg("fps"), DOC(dai, node, Camera, setFps)) - .def("set3AFpsDenominator", &Camera::set3AFpsDenominator, DOC(dai, node, Camera, set3AFpsDenominator)) + .def("setIsp3aFps", &Camera::setIsp3aFps, DOC(dai, node, Camera, setIsp3aFps)) .def("getFps", &Camera::getFps, DOC(dai, node, Camera, getFps)) .def("getPreviewSize", &Camera::getPreviewSize, DOC(dai, node, Camera, getPreviewSize)) .def("getPreviewWidth", &Camera::getPreviewWidth, DOC(dai, node, Camera, getPreviewWidth)) diff --git a/src/pipeline/node/ColorCameraBindings.cpp b/src/pipeline/node/ColorCameraBindings.cpp index 37580cc7f..0816ffefd 100644 --- a/src/pipeline/node/ColorCameraBindings.cpp +++ b/src/pipeline/node/ColorCameraBindings.cpp @@ -65,6 +65,7 @@ void bind_colorcamera(pybind11::module& m, void* pCallstack){ .def_readwrite("stillWidth", &ColorCameraProperties::stillWidth) .def_readwrite("resolution", &ColorCameraProperties::resolution) .def_readwrite("fps", &ColorCameraProperties::fps) + .def_readwrite("isp3aFps", &ColorCameraProperties::isp3aFps) .def_readwrite("sensorCropX", &ColorCameraProperties::sensorCropX) .def_readwrite("sensorCropY", &ColorCameraProperties::sensorCropY) .def_readwrite("previewKeepAspectRatio", &ColorCameraProperties::previewKeepAspectRatio) @@ -123,7 +124,7 @@ void bind_colorcamera(pybind11::module& m, void* pCallstack){ .def("setResolution", &ColorCamera::setResolution, py::arg("resolution"), DOC(dai, node, ColorCamera, setResolution)) .def("getResolution", &ColorCamera::getResolution, DOC(dai, node, ColorCamera, getResolution)) .def("setFps", &ColorCamera::setFps, py::arg("fps"), DOC(dai, node, ColorCamera, setFps)) - .def("set3AFpsDenominator", &ColorCamera::set3AFpsDenominator, DOC(dai, node, ColorCamera, set3AFpsDenominator)) + .def("setIsp3aFps", &ColorCamera::setIsp3aFps, DOC(dai, node, ColorCamera, setIsp3aFps)) .def("getFps", &ColorCamera::getFps, DOC(dai, node, ColorCamera, getFps)) .def("setFrameEventFilter", &ColorCamera::setFrameEventFilter, py::arg("events"), DOC(dai, node, ColorCamera, setFrameEventFilter)) .def("getFrameEventFilter", &ColorCamera::getFrameEventFilter, DOC(dai, node, ColorCamera, getFrameEventFilter)) diff --git a/src/pipeline/node/MonoCameraBindings.cpp b/src/pipeline/node/MonoCameraBindings.cpp index a80f0625c..41c3b981f 100644 --- a/src/pipeline/node/MonoCameraBindings.cpp +++ b/src/pipeline/node/MonoCameraBindings.cpp @@ -43,6 +43,7 @@ void bind_monocamera(pybind11::module& m, void* pCallstack){ .def_readwrite("boardSocket", &MonoCameraProperties::boardSocket) .def_readwrite("resolution", &MonoCameraProperties::resolution) .def_readwrite("fps", &MonoCameraProperties::fps) + .def_readwrite("isp3aFps", &MonoCameraProperties::isp3aFps) .def_readwrite("numFramesPool", &MonoCameraProperties::numFramesPool) .def_readwrite("numFramesPoolRaw", &MonoCameraProperties::numFramesPoolRaw) .def_readwrite("eventFilter", &MonoCameraProperties::eventFilter) @@ -80,7 +81,7 @@ void bind_monocamera(pybind11::module& m, void* pCallstack){ .def("setFrameEventFilter", &MonoCamera::setFrameEventFilter, py::arg("events"), DOC(dai, node, MonoCamera, setFrameEventFilter)) .def("getFrameEventFilter", &MonoCamera::getFrameEventFilter, DOC(dai, node, MonoCamera, getFrameEventFilter)) .def("setFps", &MonoCamera::setFps, py::arg("fps"), DOC(dai, node, MonoCamera, setFps)) - .def("set3AFpsDenominator", &MonoCamera::set3AFpsDenominator, DOC(dai, node, MonoCamera, set3AFpsDenominator)) + .def("setIsp3aFps", &MonoCamera::setIsp3aFps, DOC(dai, node, MonoCamera, setIsp3aFps)) .def("getFps", &MonoCamera::getFps, DOC(dai, node, MonoCamera, getFps)) .def("getResolutionSize", &MonoCamera::getResolutionSize, DOC(dai, node, MonoCamera, getResolutionSize)) .def("getResolutionWidth", &MonoCamera::getResolutionWidth, DOC(dai, node, MonoCamera, getResolutionWidth)) From 8078873a276075297d1b000269ef9801d2dfeb1c Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Sat, 25 Feb 2023 15:33:59 +0200 Subject: [PATCH 189/385] Add isp3aFps to cam_test --- utilities/cam_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 58fb20bc8..44b4e6294 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -63,6 +63,8 @@ def socket_type_pair(arg): help="Which cameras to rotate 180 degrees. All if not filtered") parser.add_argument('-fps', '--fps', type=float, default=30, help="FPS to set for all cameras") +parser.add_argument('-isp3afps', '--isp3afps', type=int, default=0, + help="3A FPS to set for all cameras") parser.add_argument('-ds', '--isp-downscale', default=1, type=int, help="Downscale the ISP output by this factor") parser.add_argument('-rs', '--resizable-windows', action='store_true', @@ -176,6 +178,7 @@ def get(self): if rotate[c]: cam[c].setImageOrientation(dai.CameraImageOrientation.ROTATE_180_DEG) cam[c].setFps(args.fps) + cam[c].setIsp3aFps(args.isp3afps) if args.camera_tuning: pipeline.setCameraTuningBlobPath(str(args.camera_tuning)) From e67ae76c5909662d24350f93db1d509de148833f Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 27 Feb 2023 19:06:22 +0200 Subject: [PATCH 190/385] Update BoarConfig with limits --- depthai-core | 2 +- src/DeviceBindings.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 69a99c6ff..6526e938f 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 69a99c6ff1bda5c86ce0096be06649fd445ac455 +Subproject commit 6526e938f04ed1c61aca1d3f8dd7dd4793f3d7ca diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 1319e38ff..095995a00 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -375,6 +375,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("flashBootedVid", &BoardConfig::USB::flashBootedVid) .def_readwrite("flashBootedPid", &BoardConfig::USB::flashBootedPid) .def_readwrite("maxSpeed", &BoardConfig::USB::maxSpeed) + .def_readwrite("isp3aMaxFps", &BoardConfig::USB::isp3aMaxFps, DOC(dai, BoardConfig, USB, isp3aMaxFps)) ; // Bind BoardConfig::Network @@ -382,6 +383,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def(py::init<>()) .def_readwrite("mtu", &BoardConfig::Network::mtu) .def_readwrite("xlinkTcpNoDelay", &BoardConfig::Network::xlinkTcpNoDelay) + .def_readwrite("isp3aMaxFps", &BoardConfig::Network::isp3aMaxFps, DOC(dai, BoardConfig, Network, isp3aMaxFps)) ; // GPIO Mode From ab45c200ef6ac5a9f63c4ad013644e41a06c6830 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 27 Feb 2023 20:27:39 +0200 Subject: [PATCH 191/385] Update script node python bindings --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 244ffbaad..d34f74c7b 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 244ffbaad7b89990b102224f4da0980a46044948 +Subproject commit d34f74c7bced726d596bae612b487addf8f33f89 From 4d72d28faefa643156cccd8e68b3f7e828813c42 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 1 Mar 2023 15:46:38 +0200 Subject: [PATCH 192/385] Update FW: Add workaround for BNO sequence number limit of 256 (sensors sends uint8) --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index b62967c42..37b1cc599 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit b62967c426b63818399f20e297bc7a9fbe47db6c +Subproject commit 37b1cc599161b7f35d86c4a6d4b4c0df2b3d94a8 From 4a472a58b8ae947bdba68ce110a9e4f3382b53de Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Thu, 2 Mar 2023 04:59:55 +0200 Subject: [PATCH 193/385] FW: camera sensor improvements: - AR0234 improved AE smoothness, increased max gain to 400x (first 25.6x analog), - OV9782 on RGB/CAM-A socket max FPS: 120 (previously was only for OV9282), also improves image quality in very bright light, - OV9782/9282 minimum exposure time decreased: 20us -> 10us, helps in very bright light. TODO update tuning to make use of it, currently only possible to use with manual exposure --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 37b1cc599..ae75d7270 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 37b1cc599161b7f35d86c4a6d4b4c0df2b3d94a8 +Subproject commit ae75d72701344eecfc047ebb6117af20244d37ea From bfc3cd72200424571136fe39d4a93ebce02a42ed Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 3 Mar 2023 12:45:03 +0100 Subject: [PATCH 194/385] Started working on stereo --- .../images/components/disp_to_depth.jpg | Bin 0 -> 22711 bytes .../images/components/theoretical_error.jpg | Bin 0 -> 22715 bytes docs/source/components/bootloader.rst | 8 +- docs/source/components/nodes/stereo_depth.rst | 69 +---- .../calibration/calibration_reader.rst | 37 +++ .../tutorials/configuring-stereo-depth.rst | 258 ++++++++++++++++++ docs/source/tutorials/low-latency.rst | 2 +- 7 files changed, 302 insertions(+), 72 deletions(-) create mode 100644 docs/source/_static/images/components/disp_to_depth.jpg create mode 100644 docs/source/_static/images/components/theoretical_error.jpg create mode 100644 docs/source/tutorials/configuring-stereo-depth.rst diff --git a/docs/source/_static/images/components/disp_to_depth.jpg b/docs/source/_static/images/components/disp_to_depth.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c5bda25f5cd7f3696783ca4c4b4d6946f1b70da2 GIT binary patch literal 22711 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 YR>uJ~?)Nle6q7X~|L^>ICi39_1M9N9X8-^I literal 0 HcmV?d00001 diff --git a/docs/source/_static/images/components/theoretical_error.jpg b/docs/source/_static/images/components/theoretical_error.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9b71d37a5f641589c15b6d76bbf65ccc238a4ed8 GIT binary patch literal 22715 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 fQ;#cEo@IJw`). +StereoDepth node calculates the disparity/depth from the stereo camera pair (2x :ref:`MonoCamera `/:ref:`ColorCamera`). How to place it =============== @@ -285,54 +285,6 @@ as: For the final disparity map, a filtering is applied based on the confidence threshold value: the pixels that have their confidence score larger than the threshold get invalidated, i.e. their disparity value is set to zero. You can set the confidence threshold with :code:`stereo.initialConfig.setConfidenceThreshold()`. -Calculate depth using disparity map -=================================== - -Disparity and depth are inversely related. As disparity decreases, depth increases exponentially depending on baseline and focal length. Meaning, if the disparity value is close to zero, then a small change in disparity generates a large change in depth. Similarly, if the disparity value is big, then large changes in disparity do not lead to a large change in depth. - -By considering this fact, depth can be calculated using this formula: - -.. code-block:: python - - depth = focal_length_in_pixels * baseline / disparity_in_pixels - -Where baseline is the distance between two mono cameras. Note the unit used for baseline and depth is the same. - -To get focal length in pixels, you can :ref:`read camera calibration `, as focal length in pixels is -written in camera intrinsics (``intrinsics[0][0]``): - -.. code-block:: python - - import depthai as dai - - with dai.Device() as device: - calibData = device.readCalibration() - intrinsics = calibData.getCameraIntrinsics(dai.CameraBoardSocket.RIGHT) - print('Right mono camera focal length in pixels:', intrinsics[0][0]) - -Here's theoretical calculation of the focal length in pixels: - -.. code-block:: python - - focal_length_in_pixels = image_width_in_pixels * 0.5 / tan(HFOV * 0.5 * PI/180) - - # With 400P mono camera resolution where HFOV=71.9 degrees - focal_length_in_pixels = 640 * 0.5 / tan(71.9 * 0.5 * PI / 180) = 441.25 - - # With 800P mono camera resolution where HFOV=71.9 degrees - focal_length_in_pixels = 1280 * 0.5 / tan(71.9 * 0.5 * PI / 180) = 882.5 - -Examples for calculating the depth value, using the OAK-D (7.5cm baseline): - -.. code-block:: python - - # For OAK-D @ 400P mono cameras and disparity of eg. 50 pixels - depth = 441.25 * 7.5 / 50 = 66.19 # cm - - # For OAK-D @ 800P mono cameras and disparity of eg. 10 pixels - depth = 882.5 * 7.5 / 10 = 661.88 # cm - -Note the value of disparity depth data is stored in :code:`uint16`, where 0 is a special value, meaning that distance is unknown. Min stereo depth distance ========================= @@ -383,10 +335,10 @@ Disparity shift can be combined with extended/subpixel/LR-check modes. **Left graph** shows min and max disparity and depth for OAK-D (7.5cm baseline, 800P resolution, ~70° HFOV) by default (disparity shift=0). See :ref:`Calculate depth using disparity map`. Since hardware (stereo block) has a fixed 95 pixel disparity search, DepthAI will search from 0 pixels (depth=INF) to 95 pixels (depth=71cm). +**Limitations**: **Right graph** shows the same, but at disparity shift set to 30 pixels. This means that disparity search will be from 30 pixels (depth=2.2m) to 125 pixels (depth=50cm). This also means that depth will be very accurate at the short range (**theoretically** below 5mm depth error). -**Limitations**: - Because of the inverse relationship between disparity and depth, MaxZ will decrease much faster than MinZ as the disparity shift is increased. Therefore, it is **advised not to use a larger than necessary disparity shift**. - Tradeoff in reducing the MinZ this way is that objects at **distances farther away than MaxZ will not be seen**. @@ -425,23 +377,6 @@ So using this formula for existing models the *theoretical* max distance is: If greater precision for long range measurements is required, consider enabling Subpixel Disparity or using a larger baseline distance between mono cameras. For a custom baseline, you could consider using `OAK-FFC `__ device or design your own baseboard PCB with required baseline. For more information see Subpixel Disparity under the Stereo Mode tab in :ref:`this table `. -Depth perception accuracy -========================= - -Disparity depth works by matching features from one image to the other and its accuracy is based on multiple parameters: - -* Texture of objects / backgrounds - -Backgrounds may interfere with the object detection, since backgrounds are objects too, which will make depth perception less accurate. So disparity depth works very well outdoors as there are very rarely perfectly-clean/blank surfaces there - but these are relatively commonplace indoors (in clean buildings at least). - -* Lighting - -If the illumination is low, the diparity map will be of low confidence, which will result in a noisy depth map. - -* Baseline / distance to objects - -Lower baseline enables us to detect the depth at a closer distance as long as the object is visible in both the frames. However, this reduces the accuracy for large distances due to less pixels representing the object and disparity decreasing towards 0 much faster. -So the common norm is to adjust the baseline according to how far/close we want to be able to detect objects. Limitation ========== diff --git a/docs/source/samples/calibration/calibration_reader.rst b/docs/source/samples/calibration/calibration_reader.rst index b238cecea..199922bf6 100644 --- a/docs/source/samples/calibration/calibration_reader.rst +++ b/docs/source/samples/calibration/calibration_reader.rst @@ -9,6 +9,43 @@ This example shows how to read calibration data stored on device over XLink. Thi - :ref:`Calibration Flash` - :ref:`Calibration Load` +Camera intrinsics +~~~~~~~~~~~~~~~~~ + +Calibration also contains camera intrinsics and extrinsics parameters. + +.. code-block:: python + + import depthai as dai + + with dai.Device() as device: + calibData = device.readCalibration() + intrinsics = calibData.getCameraIntrinsics(dai.CameraBoardSocket.RIGHT) + print('Right mono camera focal length in pixels:', intrinsics[0][0]) + +Here's theoretical calculation of the focal length in pixels: + +.. math:: + + focal_length_in_pixels = image_width_in_pixels * 0.5 / tan(HFOV * 0.5 * PI/180) + + // With 400P mono camera resolution where HFOV=71.9 degrees + focal_length_in_pixels = 640 * 0.5 / tan(71.9 * 0.5 * PI / 180) = 441.25 + + // With 800P mono camera resolution where HFOV=71.9 degrees + focal_length_in_pixels = 1280 * 0.5 / tan(71.9 * 0.5 * PI / 180) = 882.5 + +Examples for calculating the depth value, using the OAK-D (7.5cm baseline): + +.. math:: + + # For OAK-D @ 400P resolution and disparity of eg. 50 pixels + depth = 441.25 * 7.5 / 50 = 66.19 # cm + + # For OAK-D @ 800P resolution and disparity of eg. 10 pixels + depth = 882.5 * 7.5 / 10 = 661.88 # cm + + Setup ##### diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst new file mode 100644 index 000000000..e1b6c43a5 --- /dev/null +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -0,0 +1,258 @@ +Configuring Stereo Depth +######################## + +Our :ref:`StereoDepth node ` is very configurable and with this tutorial we will go over some **configurations and troubleshooting** +you can do to get the best results. + +This documentation is divided into 6 chapters: + +- :ref:`1. Stereo Depth Basics` +- :ref:`2. Fixing noisy depth` +- :ref:`3. Improving depth accuracy` +- :ref:`4. Short range stereo depth` +- :ref:`5. Long range stereo depth` +- :ref:`6. Fixing noisy pointcloud` + +1. Stereo Depth Basics +********************** + +`Stereo depth vision `__ works by calculating the disparity between two images taken from +slightly different points. + +Stereo vision works a lot like our eyes. Our brains (subconsciously) estimate the depth of objects and scenes based on the difference of what our left eye sees +versus what our right eye sees. On OAK-D cameras, it's exactly the same; we have left and right camera (of the stereo camera pair) +and the OAK does on-device disparity matching to estimate the depth of objects and scenes. + +Disparity refers to the distance between two corresponding points in the left and right image of a stereo pair. + +.. image:: /_static/images/components/disparity_explanation.jpeg + +Depth from disparity +-------------------- + +Let's first look at how the depth is calculated: + +.. math:: + + depth [mm] = focal_length [pixels] * baseline [mm] / disparity [pixels] + + +`RVC2 `__-based cameras have a **0..95 disparity search** range, +which limits the minimal depth perception. Baseline is the distance between two cameras of the +stereo camera pair. You can read camera's focal length (in pixels) from calibration, see :ref:`tutorial here ` + +Here's a graph showing disparity vs depth for OAK-D (7.5cm baseline distance) at 800P: + +.. figure:: /_static/images/components/disp_to_depth.jpg + + `Full chart here `__ + +Note the value of disparity depth data is stored in *uint16*, where 0 means that the distance is invalid/unknown. + +2. Fixing noisy depth +********************* + +A few topics we have noticed that are relevant for stereo depth quality are: + +- :ref:`Scene Texture` +- :ref:`Stereo depth confidence threshold` +- :ref:`Stereo camera pair noise` +- :ref:`Stereo postprocessing filters` + +Scene Texture +------------- + +Due to the way the stereo matching algorithm works, **passive stereo depth requires** to have a **good texture** in the scene, otherwise the depth will be noisy/invalid. +low-visual-interest surfaces (blank surfaces with little to no texture), such as a wall or floor. + +**Solution** + +Our `Pro version `__ of OAK cameras have onboard **IR laser dot projector**, +which projects thousands of little dots on the scene, which helps the stereo matching algorithm as it provides more texture to the scene. + +.. + .. image:: dot projector vs no dot projector gif + +The technique that we use is called ASV (`Conventional Active Stereo Vision `__) +as stereo matching is performed on the device the same way as on a passive stereo OAK-D. + + +Stereo depth confidence threshold +--------------------------------- + +When calculating the disparity, each pixel in the disparity map gets assigned a confidence value :code:`0..255` by the stereo matching algorithm. +This confidence score is kind-of inverted (if say comparing with NN): + +- **0** - maximum confidence that it holds a valid value +- **255** - minimum confidence, so there is more chance that the value is incorrect + +For the final disparity map, a filtering is applied based on the confidence threshold value: the pixels that have their confidence score larger than +the threshold get invalidated, i.e. their disparity value is set to zero. You can set the confidence threshold via the API below. + +This means that with the confidence threshold users can prioritize **fill-rate or accuracy**. + +.. code-block:: python + + stereo_depth = pipeline.create(dai.node.StereoDepth) + stereo_depth.initialConfig.setConfidenceThreshold(int) + + # Or, alternatively, set the Stereo Preset Mode: + # Prioritize fill-rate, sets Confidence threshold to 245 + stereo_depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) + # Prioritize accuracy, sets Confidence threshold to 200 + stereo_depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_ACCURACY) + + +.. + .. image:: gif of changing threshold, and how fill-rate/accuracy changes + +Stereo camera pair noise +------------------------ + +If input left/right images are noisy, the disparity map will be noisy as well. So prerequisite for good depth are high IQ (see :ref:`Image Quality ` docs) +left/right stereo images. Active stereo (`Pro version `__ of OAK cameras) +mostly alleviates this issue, but for passive stereo cameras, there are a few things you can do to improve the quality of the stereo camera pair. + +It is preferred to use mono (grayscale) cameras for the stereo camera pair as they +have better quantum efficiency (QE) as they don't have color (Bayer) filter. Higher QE means more signal will be generated for the same amount of light (photons), +which leads to better SNR (signal-to-noise ratio). + +For better low-light performance, it's advised to use longer exposure times instead of higher gain (ISO) as it will improve SNR. Sometimes this means lowering +camera FPS - at 30 FPS, you can use 1/30s exposure time, at 15 FPS, you can use 1/15s exposure time, etc. For more information, see :ref:`Low-light increased sensitivity`. + +Another potential improvement is to tweak sensor's ISP settings, like chroma & luma denoise, and sharpness. For more information, see :ref:`Color camera ISP configuration`. + +Stereo postprocessing filters +----------------------------- + +Stereo depth node has a few postprocessing filters that **run on-device**, which can be enabled to improve the quality of the disparity map. For **implementation +(API) details**, see :ref:`StereoDepth configurable blocks `. For an example, see :ref:`Depth Post-Processing` example. + +As these filters run on device, it has a **decent performance cost**, which mean that at high-resolution frames (1MP) these might bottleneck the FPS. To improve +the cost, one should consider using lower-resolution frames (eg. 400P) or use :ref:`Decimation filter`. Due to additional processing, these filters also introduce +:ref:`additional latency `. + +Median filter +~~~~~~~~~~~~~ + +This is a non-edge preserving Median filter, which can be used to reduce noise and smoothen the depth map. Median filter is implemented in hardware, so it's the +fastest filter. + +Speckle filter +~~~~~~~~~~~~~~ + +Speckle Filter is used to reduce the speckle noise. Speckle noise is a region with huge variance between neighboring disparity/depth pixels, and speckle +filter tries to filter this region. + +Temporal filter +~~~~~~~~~~~~~~~ + +Temporal Filter is intended to improve the depth data persistency by manipulating per-pixel values based on previous frames. The filter performs a single pass on +the data, adjusting the depth values while also updating the tracking history. + +In cases where the pixel data is missing or invalid, the filter uses a user-defined persistency mode to decide whether the missing value should be improved +with stored data. Note that due to its reliance on historic data the filter may introduce +visible motion blurring/smearing artifacts, and therefore is best-suited for **static scenes**. + +Spatial filter +~~~~~~~~~~~~~~ + +Spatial Edge-Preserving Filter will fill invalid depth pixels with valid neighboring depth pixels. It performs a series of 1D horizontal and vertical passes or +iterations, to enhance the smoothness of the reconstructed data. It is based on `this research paper `__. + +Threshold filter +~~~~~~~~~~~~~~~~ + +Threshold filter will filter out all depth pixels outside the configured min/max threshold values. In controlled environment, where you know exactly how far the scene +can be (eg. 30cm - 2m) it's advised to use this filter. + +Decimation filter +~~~~~~~~~~~~~~~~~ + +Decimation Filter will sub-samples the depth map, which means it reduces the depth scene complexity and allows other filters to run faster. Setting +*decimationFactor* to 2 will downscale 1280x800 depth map to 640x400. + +3. Improving depth accuracy +*************************** + +The above chapter () was focused on noise, which isn't necessary the only reason for +inaccurate depth. + +There are a few ways to improve depth accuracy: + +- (mentioned above) :ref:`Fixing noisy depth <2. Fixing noisy depth>` - depth should be high quality in order to be accurate +- (mentioned above) :ref:`Stereo depth confidence threshold` should we low(er) in order to get the best accuracy +- + + + +Looking at :ref:`Depth from disparity` section, from the graph it's clear that at the 95 disparity pixels (close distance, about 70cm), +depth change between disparity pixels (eg. 95->94) is the lowest, so the depth accuracy is the highest. + + +.. figure:: /_static/images/components/theoretical_error.jpg + +It's clear that depth accuracy decreases exponentially with the distance from the camera. Note that With :ref:`Stereo Subpixel mode` +you can have a better depth accuracy at a longer distance but it only works to some extent. + + + +Stereo Subpixel mode +~~~~~~~~~~~~~~~~~~~~ + +(see `What's subpixel? `__) + + + +Disparity and depth are inversely related. As disparity decreases, depth increases exponentially depending on baseline and focal length. Meaning, if the disparity value is close to zero, then a small change in disparity generates a large change in depth. Similarly, if the disparity value is big, then large changes in disparity do not lead to a large change in depth. + +* Baseline / distance to objects + +Lower baseline enables us to detect the depth at a closer distance as long as the object is visible in both the frames. However, this reduces the accuracy for large distances due to less pixels representing the object and disparity decreasing towards 0 much faster. +So the common norm is to adjust the baseline according to how far/close we want to be able to detect objects. + + +4. Short range stereo depth +*************************** + +To get an accurate short-range + +- Extended mode +- Lower resolution +- Disparity shift +- Closer baseline distance (OAK-D-SR) + +Long range stereo depth +*********************** + +- Subpixel mode +- Narrow FOV lenses/wider baseline distance -> OAK-D-LR + + + +Fixing noisy pointcloud +*********************** + +- First check Depth is noisy +- Voxalization + remove statistical outliers +- Invalidation a few pixels around the corner of depth image, invalidating left part (eg. 30 pixels) +- Brightness filter (remove black part from rectification) +- Decimation filter? + +Decimation filter - for PCL, you don't really want 1 million points (Although it sounds good from marketing POV), as it's too much data to process. +Decimation filter helps here, I'd go as far as must have. It has capabilities of filtering also, if you change it to median mode vs default pixel skipping, +not just reducing image size. It also makes the other filters faster, since there will be less data to process. + +Best practices in certain environments +************************************** + +- In high dynamic range env (like outside), use brightness filter (img above) +- In more static env, temporal filter + + + + + + + +.. include:: /includes/footer-short.rst \ No newline at end of file diff --git a/docs/source/tutorials/low-latency.rst b/docs/source/tutorials/low-latency.rst index 72358b398..978e467cb 100644 --- a/docs/source/tutorials/low-latency.rst +++ b/docs/source/tutorials/low-latency.rst @@ -44,7 +44,7 @@ disabled for these tests (:code:`pipeline.setXLinkChunkSize(0)`). For an example - `link `__ - **Time-to-Host** is measured time between frame timestamp (:code:`imgFrame.getTimestamp()`) and host timestamp when the frame is received (:code:`dai.Clock.now()`). -- **Histogram** shows how much Time-to-Host varies frame to frame. Y axis represents number of frame that occured at that time while the X axis represents microseconds. +- **Histogram** shows how much Time-to-Host varies frame to frame. Y axis represents number of frame that occurred at that time while the X axis represents microseconds. - **Bandwidth** is calculated bandwidth required to stream specified frames at specified FPS. Encoded frames From e05772c028c22430545067518a217dd7c5095924 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 3 Mar 2023 23:20:31 +0200 Subject: [PATCH 195/385] Enable interrupt mode: Update BMI driver with fixes from: https://github.com/boschsensortec/BMI270-Sensor-API/pull/16 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index ae75d7270..0a7956bc7 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit ae75d72701344eecfc047ebb6117af20244d37ea +Subproject commit 0a7956bc705c3ebea305a9ba1559ff0e023fad77 From ba9c00f4c2eef54194978e46483bccd8b670b43e Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 3 Mar 2023 23:42:12 +0200 Subject: [PATCH 196/385] Add API to set trackingPerClass in ObjectTracker node --- depthai-core | 2 +- src/pipeline/node/ObjectTrackerBindings.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index ae75d7270..f6c6f0035 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit ae75d72701344eecfc047ebb6117af20244d37ea +Subproject commit f6c6f00355ddeb9e1f98d0379bfa0b3026e9d39c diff --git a/src/pipeline/node/ObjectTrackerBindings.cpp b/src/pipeline/node/ObjectTrackerBindings.cpp index 75f1362fb..b2b2b76a3 100644 --- a/src/pipeline/node/ObjectTrackerBindings.cpp +++ b/src/pipeline/node/ObjectTrackerBindings.cpp @@ -65,6 +65,7 @@ void bind_objecttracker(pybind11::module& m, void* pCallstack){ .def("setDetectionLabelsToTrack", &ObjectTracker::setDetectionLabelsToTrack, py::arg("labels"), DOC(dai, node, ObjectTracker, setDetectionLabelsToTrack)) .def("setTrackerType", &ObjectTracker::setTrackerType, py::arg("type"), DOC(dai, node, ObjectTracker, setTrackerType)) .def("setTrackerIdAssignmentPolicy", &ObjectTracker::setTrackerIdAssignmentPolicy, py::arg("type"), DOC(dai, node, ObjectTracker, setTrackerIdAssignmentPolicy)) + .def("setTrackingPerClass", &ObjectTracker::setTrackingPerClass, py::arg("trackingPerClass"), DOC(dai, node, ObjectTracker, setTrackingPerClass)) ; daiNodeModule.attr("ObjectTracker").attr("Properties") = objectTrackerProperties; From 5c6499d76b86697dbaeb7b240d0689094594a9ca Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sat, 4 Mar 2023 15:38:07 +0100 Subject: [PATCH 197/385] Started working on long-range stereo depth perception docs --- .../components/disparity_confidence.jpg | Bin 0 -> 23212 bytes .../images/components/layered-pointcloud.png | Bin 0 -> 88391 bytes docs/source/components/nodes/stereo_depth.rst | 123 ++------- .../tutorials/configuring-stereo-depth.rst | 235 +++++++++++++++--- 4 files changed, 222 insertions(+), 136 deletions(-) create mode 100644 docs/source/_static/images/components/disparity_confidence.jpg create mode 100644 docs/source/_static/images/components/layered-pointcloud.png diff --git a/docs/source/_static/images/components/disparity_confidence.jpg b/docs/source/_static/images/components/disparity_confidence.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3d8d4b3b4c3f6b7e160d9c8aab1b6640bd1efa83 GIT binary patch literal 23212 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/images/components/layered-pointcloud.png b/docs/source/_static/images/components/layered-pointcloud.png new file mode 100644 index 0000000000000000000000000000000000000000..20ee291297b6dfae2fc7a11a54ebbd223862e8f3 GIT binary patch literal 88391 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�c=Jz^|Fju?A55OVJ4b61G!y_r*(lKbLZ?| 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 literal 0 HcmV?d00001 diff --git a/docs/source/components/nodes/stereo_depth.rst b/docs/source/components/nodes/stereo_depth.rst index 4136e9aee..5c2580ac3 100644 --- a/docs/source/components/nodes/stereo_depth.rst +++ b/docs/source/components/nodes/stereo_depth.rst @@ -47,28 +47,29 @@ Inputs and Outputs .. tab:: **Inputs** - - :code:`left` - :ref:`ImgFrame` from the left :ref:`MonoCamera` - - :code:`right` - :ref:`ImgFrame` from the right :ref:`MonoCamera` - - :code:`inputConfig` - :ref:`StereoDepthConfig` + - ``left`` - :ref:`ImgFrame` from the left stereo camera + - ``right`` - :ref:`ImgFrame` from the right stereo camera + - ``inputConfig`` - :ref:`StereoDepthConfig` .. tab:: **Outputs** - - :code:`confidenceMap` - :ref:`ImgFrame` - - :code:`rectifiedLeft` - :ref:`ImgFrame` - - :code:`syncedLeft` - :ref:`ImgFrame` - - :code:`depth` - :ref:`ImgFrame`: UINT16 values - depth in depth units (millimeter by default) - - :code:`disparity` - :ref:`ImgFrame`: UINT8 or UINT16 if Subpixel mode - - :code:`rectifiedRight` - :ref:`ImgFrame` - - :code:`syncedRight` - :ref:`ImgFrame` - - :code:`outConfig` - :ref:`StereoDepthConfig` + - ``confidenceMap`` - :ref:`ImgFrame` + - ``rectifiedLeft`` - :ref:`ImgFrame` + - ``syncedLeft`` - :ref:`ImgFrame` + - ``depth`` - :ref:`ImgFrame`: UINT16 values - depth in depth units (millimeter by default) + - ``disparity`` - :ref:`ImgFrame`: UINT8 or UINT16 if Subpixel mode + - ``rectifiedRight`` - :ref:`ImgFrame` + - ``syncedRight`` - :ref:`ImgFrame` + - ``outConfig`` - :ref:`StereoDepthConfig` .. tab:: **Debug outputs** - - :code:`debugDispLrCheckIt1` - :ref:`ImgFrame` - - :code:`debugDispLrCheckIt2` - :ref:`ImgFrame` - - :code:`debugExtDispLrCheckIt1` - :ref:`ImgFrame` - - :code:`debugExtDispLrCheckIt2` - :ref:`ImgFrame` - - :code:`debugDispCostDump` - :ref:`ImgFrame` + - ``debugDispLrCheckIt1`` - :ref:`ImgFrame` + - ``debugDispLrCheckIt2`` - :ref:`ImgFrame` + - ``debugExtDispLrCheckIt1`` - :ref:`ImgFrame` + - ``debugExtDispLrCheckIt2`` - :ref:`ImgFrame` + - ``debugDispCostDump`` - :ref:`ImgFrame` + - ``confidenceMap`` - :ref:`ImgFrame` Internal block diagram of StereoDepth node ========================================== @@ -323,39 +324,6 @@ or roughly 35cm. See `these examples `__ for how to enable Extended Disparity. -Disparity shift to lower min depth perception ---------------------------------------------- - -Another option to perceive closer depth range is to use disparity shift. Disparity shift will shift the starting point -of the disparity search, which will significantly decrease max depth (MazZ) perception, but it will also decrease min depth (MinZ) perception. -Disparity shift can be combined with extended/subpixel/LR-check modes. - -.. image:: https://user-images.githubusercontent.com/18037362/189375017-2fa137d2-ad6b-46de-8899-6304bbc6c9d7.png - -**Left graph** shows min and max disparity and depth for OAK-D (7.5cm baseline, 800P resolution, ~70° HFOV) by default (disparity shift=0). See :ref:`Calculate depth using disparity map`. -Since hardware (stereo block) has a fixed 95 pixel disparity search, DepthAI will search from 0 pixels (depth=INF) to 95 pixels (depth=71cm). - -**Limitations**: -**Right graph** shows the same, but at disparity shift set to 30 pixels. This means that disparity search will be from 30 pixels (depth=2.2m) to 125 pixels (depth=50cm). -This also means that depth will be very accurate at the short range (**theoretically** below 5mm depth error). - - -- Because of the inverse relationship between disparity and depth, MaxZ will decrease much faster than MinZ as the disparity shift is increased. Therefore, it is **advised not to use a larger than necessary disparity shift**. -- Tradeoff in reducing the MinZ this way is that objects at **distances farther away than MaxZ will not be seen**. -- Because of the point above, **we only recommend using disparity shift when MaxZ is known**, such as having a depth camera mounted above a table pointing down at the table surface. -- Output disparity map is not expanded, only the depth map. So if disparity shift is set to 50, and disparity value obtained is 90, the real disparity is 140. - -**Compared to Extended disparity**, disparity shift: - -- **(+)** Is faster, as it doesn't require an extra computation, which means there's also no extra latency -- **(-)** Reduces the MaxZ (significantly), while extended disparity only reduces MinZ. - -Disparity shift can be combined with extended disparity. - -.. doxygenfunction:: dai::StereoDepthConfig::setDisparityShift - :project: depthai-core - :no-link: - Max stereo depth distance ========================= @@ -378,63 +346,6 @@ So using this formula for existing models the *theoretical* max distance is: If greater precision for long range measurements is required, consider enabling Subpixel Disparity or using a larger baseline distance between mono cameras. For a custom baseline, you could consider using `OAK-FFC `__ device or design your own baseboard PCB with required baseline. For more information see Subpixel Disparity under the Stereo Mode tab in :ref:`this table `. -Limitation -========== - -Since depth is calculated from disparity, which requires the pixels to overlap, there is inherently a vertical -band on the left side of the left mono camera and on the right side of the right mono camera, where depth -cannot be calculated, since it is seen by only 1 camera. That band is marked with :code:`B` -on the following picture. - -.. image:: https://user-images.githubusercontent.com/59799831/135310921-67726c28-07e7-4ffa-bc8d-74861049517e.png - -Meaning of variables on the picture: - -- ``BL [cm]`` - Baseline of stereo cameras. -- ``Dv [cm]`` - Minimum distace where both cameras see an object (thus where depth can be calculated). -- ``B [pixels]`` - Width of the band where depth cannot be calculated. -- ``W [pixels]`` - Width of mono in pixels camera or amount of horizontal pixels, also noted as :code:`HPixels` in other formulas. -- ``D [cm]`` - Distance from the **camera plane** to an object (see image :ref:`here `). -- ``F [cm]`` - Width of image at the distance ``D``. - -.. image:: https://user-images.githubusercontent.com/59799831/135310972-c37ba40b-20ad-4967-92a7-c71078bcef99.png - -With the use of the :code:`tan` function, the following formulas can be obtained: - -- :code:`F = 2 * D * tan(HFOV/2)` -- :code:`Dv = (BL/2) * tan(90 - HFOV/2)` - -In order to obtain :code:`B`, we can use :code:`tan` function again (same as for :code:`F`), but this time -we must also multiply it by the ratio between :code:`W` and :code:`F` in order to convert units to pixels. -That gives the following formula: - -.. code-block:: python - - B = 2 * Dv * tan(HFOV/2) * W / F - B = 2 * Dv * tan(HFOV/2) * W / (2 * D * tan(HFOV/2)) - B = W * Dv / D # pixels - -Example: If we are using OAK-D, which has a :code:`HFOV` of 72°, a baseline (:code:`BL`) of 7.5 cm and -:code:`640x400 (400P)` resolution is used, therefore :code:`W = 640` and an object is :code:`D = 100` cm away, we can -calculate :code:`B` in the following way: - -.. code-block:: - - Dv = 7.5 / 2 * tan(90 - 72/2) = 3.75 * tan(54°) = 5.16 cm - B = 640 * 5.16 / 100 = 33 # pixels - -Credit for calculations and images goes to our community member gregflurry, which he made on -`this `__ -forum post. - -.. note:: - - OAK-D-PRO will include both IR dot projector and IR LED, which will enable operation in no light. - IR LED is used to illuminate the whole area (for mono/color frames), while IR dot projector is mostly - for accurate disparity matching - to have good quality depth maps on blank surfaces as well. For outdoors, - the IR laser dot projector is only relevant at night. For more information see the development progress - `here `__. - Measuring real-world object dimensions ====================================== diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst index e1b6c43a5..c9cdcd03e 100644 --- a/docs/source/tutorials/configuring-stereo-depth.rst +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -41,6 +41,10 @@ Let's first look at how the depth is calculated: which limits the minimal depth perception. Baseline is the distance between two cameras of the stereo camera pair. You can read camera's focal length (in pixels) from calibration, see :ref:`tutorial here ` +Disparity and depth are inversely related. As disparity decreases, depth increases exponentially depending on baseline and focal length. +Meaning, if the disparity value is close to zero, then a small change in disparity generates a large change in depth. +Similarly, if the disparity value is big, then some change in disparity doesn't lead to a large change in depth (better accuracy). + Here's a graph showing disparity vs depth for OAK-D (7.5cm baseline distance) at 800P: .. figure:: /_static/images/components/disp_to_depth.jpg @@ -49,6 +53,20 @@ Here's a graph showing disparity vs depth for OAK-D (7.5cm baseline distance) at Note the value of disparity depth data is stored in *uint16*, where 0 means that the distance is invalid/unknown. +How baseline distance and focal length affect depth +--------------------------------------------------- + +Looking at the depth formula above, we can see that either larger baseline distance, or larger focal length will result +in further depth at the same disparity, which means that the depth accuracy will be better. + +Focal length is the distance between the camera lens and the image sensor. The larger the focal length, the narrower the FOV. + +So to get **long range depth** perception, you can **increase the baseline distance and/or decrease the FOV**. + +.. note:: + + Wider FOV will result in worse depth accuracy, even at shorter range (but at shorter range it won't be as noticeable). + 2. Fixing noisy depth ********************* @@ -70,8 +88,7 @@ low-visual-interest surfaces (blank surfaces with little to no texture), such as Our `Pro version `__ of OAK cameras have onboard **IR laser dot projector**, which projects thousands of little dots on the scene, which helps the stereo matching algorithm as it provides more texture to the scene. -.. - .. image:: dot projector vs no dot projector gif +.. image:: https://user-images.githubusercontent.com/18037362/222730554-a6c8d4d3-cb0b-422e-8474-6a979e73727a.gif The technique that we use is called ASV (`Conventional Active Stereo Vision `__) as stereo matching is performed on the device the same way as on a passive stereo OAK-D. @@ -182,56 +199,220 @@ There are a few ways to improve depth accuracy: - (mentioned above) :ref:`Fixing noisy depth <2. Fixing noisy depth>` - depth should be high quality in order to be accurate - (mentioned above) :ref:`Stereo depth confidence threshold` should we low(er) in order to get the best accuracy -- +- :ref:`Move camera closer to the object` for best depth accuracy +- Enable :ref:`Stereo Subpixel mode`, especially if object/scene isn't close to MinZ of the camera +Move camera closer to the object +-------------------------------- +Looking at :ref:`Depth from disparity` section, from the graph it's clear that at the 95 disparity pixels (close distance), +depth change between disparity pixels (eg. 95->94) is the lowest, so the **depth accuracy is the best**. -Looking at :ref:`Depth from disparity` section, from the graph it's clear that at the 95 disparity pixels (close distance, about 70cm), -depth change between disparity pixels (eg. 95->94) is the lowest, so the depth accuracy is the highest. +.. image:: /_static/images/components/theoretical_error.jpg -.. figure:: /_static/images/components/theoretical_error.jpg +It's clear that depth accuracy decreases exponentially with the distance from the camera. Note that with :ref:`Stereo Subpixel mode` +enabled you can have better depth accuracy (even at a longer distance) but it only works to some extent. -It's clear that depth accuracy decreases exponentially with the distance from the camera. Note that With :ref:`Stereo Subpixel mode` -you can have a better depth accuracy at a longer distance but it only works to some extent. +So to conclude, **object/scene you are measuring** should be **as close as possible to MinZ** (minimal depth perception) of the camera +for **best depth accuracy**. You can find MinZ specification for each device in `hardware docs `__. +Stereo Subpixel mode +-------------------- +Let's first start at what Stereo Subpixel mode is and how it works. For image subpixel explanation, see `What's subpixel? `__). -Stereo Subpixel mode -~~~~~~~~~~~~~~~~~~~~ +.. note:: -(see `What's subpixel? `__) + The stereo depth pipeline is very complex (see :ref:`Internal block diagram of StereoDepth node`), and we will simplify it here for better understanding. It actually doesn't use confidence (eg. ``stereoDepth.confidenceMap`` output), but cost dump, which is what is used to calculate confidence values. +When calculating disparity depth, stereo matching algorithm assign a "confidence" for each disparity pixel, which means each pixel +of the depth image contains 96 bytes (for confidence). If you are interested in all these cost values, you can use ``stereoDepth.debugDispCostDump`` output, +just note it's a very large output (eg. 1280*800*96 => 98MB for each frame). +.. image:: /_static/images/components/disparity_confidence.jpg -Disparity and depth are inversely related. As disparity decreases, depth increases exponentially depending on baseline and focal length. Meaning, if the disparity value is close to zero, then a small change in disparity generates a large change in depth. Similarly, if the disparity value is big, then large changes in disparity do not lead to a large change in depth. +Stereo Subpixel mode will calculate subpixel disparity by looking at the confidence values of the 2 neighboring disparity pixels in each direction. +In above example graph, in normal mode, Stereo Depth would just get the max disparity = 34 pixels, but in Subpixel +mode, it will return a bit more, eg. 37.375 pixels, as confidences for pixels 35 and 36 are quite high as well. -* Baseline / distance to objects +**TL;DR:** Stereo Subpixel mode should always provide more accurate depth, but will consume additional HW resources (see :ref:`Stereo depth FPS` for impact). -Lower baseline enables us to detect the depth at a closer distance as long as the object is visible in both the frames. However, this reduces the accuracy for large distances due to less pixels representing the object and disparity decreasing towards 0 much faster. -So the common norm is to adjust the baseline according to how far/close we want to be able to detect objects. +Stereo subpixel affect on layering +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Default stereo depth output has 0..95 disparity pixels, which would produce 96 unique depth values. This can especially be seen when using pointcloud representation + and seeing how there are discrete "layers" of points, instead of a smooth transition: + +.. image:: /_static/images/components/layered-pointcloud.png + +This layering can especially be seen at longer distances, where these layers are exponentially further apart. + +But with Stereo Subpixel mode enabled, there are many more unique values possible, which produces a more granular depth steps, and thus smoother a pointcloud. + +.. math:: + + 94 * 2^3 [subpixel bits] + 2 [min/max value] = 754 unique values + 94 * 2^4 [subpixel bits] + 2 [min/max value] = 1506 unique values + 94 * 2^5 [subpixel bits] + 2 [min/max value] = 3010 unique values + +One can change the number of subpixel bits by setting ``stereoDepth.setSubpixelFractionalBits(int)`` parameter to 3, 4 or 5 bits. 4. Short range stereo depth *************************** -To get an accurate short-range +To get accurate short-range depth, you'd first need to follow :ref:`3. Improving depth accuracy` steps. +For most normal FOV OV9282 OAK-D* cameras, you'd want to have object/scene about 70cm away from the camera, +where you'd get below 2% error (with good :ref:`Scene Texture`), so ± 1.5cm error. + +But how to get an even better depth accuracy, eg. **sub-cm stereo depth accuracy**? +As we have learned at :Ref:`How baseline distance and focal length affect depth`, we would want to +have closer baseline distance and/or narrower FOV lenses. + +That's why, for short range depth perception, **we suggest using** `OAK-D SR `__, +which has 2 cm baseline distance, 800P resolution, and is ideal for depth sensing of up to 1 meter. + +Going back to :ref:`Depth from disparity`, minimal depth perception (**MinZ**) is defined by the following formula, where disparity is 95 pixels +(maximum number of pixel for disparity search): + +.. math:: + + depth = focal_length * baseline / disparity + MinZ = focal_length * baseline / 95 + +How to get lower MinZ +--------------------- + +You can get lower MinZ for OAK cameras by: + +- :ref:`Lowering resolution ` +- Enabling :ref:`Stereo Extended Disparity mode` +- Using :ref:`Disparity shift` - suggested in controlled environment, where MaxZ is known + +Lowering resolution to decrease MinZ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Above we have a formula for MinZ, and by lowering resolution, we are lowering focal length (in pixels), so let's look at the formula again: + +.. math:: + + MinZ = focal_length * baseline / 95 + MinZ [800P OAK-D] = 882.5 * 7.5 / 95 = 70 cm + MinZ [400P OAK-D] = 441 * 7.5 / 95 = 35 cm + +As you can see, by lowering resolution by 2, we are also lowering MinZ by 2. Note that because you have less pixels, you will also have lower depth accuracy (in cm). + +Stereo Extended Disparity mode +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Very similar to :ref:`Lowering resolution to decrease MinZ`, Extended mode runs stereo depth pipeline twice (thus consuming more HW resources); once with resolution of +the frame that was passed to :ref:`StereoDepth` node, and once with resolution downscaled by 2, then combine the 2 output disparity maps. + +Disparity shift +~~~~~~~~~~~~~~~ + +In a controller environment, where MaxZ is known in advance, to perceive closer depth range it's advised to use disparity shift, as it doesn't decrease depth accuracy +as other 2 methods above do. + +Disparity shift will shift the starting point of the disparity search, which will significantly decrease max depth (MaxZ) perception, but +it will also decrease min depth (MinZ) perception. Disparity shift can be combined with extended/subpixel/LR-check modes. + +.. image:: https://user-images.githubusercontent.com/18037362/189375017-2fa137d2-ad6b-46de-8899-6304bbc6c9d7.png + +**Left graph** shows min and max disparity and depth for OAK-D (7.5cm baseline, 800P resolution, ~70° HFOV) by default (disparity shift=0). See :ref:`Calculate depth using disparity map`. +Since hardware (stereo block) has a fixed 95 pixel disparity search, DepthAI will search from 0 pixels (depth=INF) to 95 pixels (depth=71cm). + +**Limitations**: +**Right graph** shows the same, but at disparity shift set to 30 pixels. This means that disparity search will be from 30 pixels (depth=2.2m) to 125 pixels (depth=50cm). +This also means that depth will be very accurate at the short range (**theoretically** below 5mm depth error). + + +- Because of the inverse relationship between disparity and depth, MaxZ will decrease much faster than MinZ as the disparity shift is increased. Therefore, it is **advised not to use a larger than necessary disparity shift**. +- Tradeoff in reducing the MinZ this way is that objects at **distances farther away than MaxZ will not be seen**. +- Because of the point above, **we only recommend using disparity shift when MaxZ is known**, such as having a depth camera mounted above a table pointing down at the table surface. +- Output disparity map is not expanded, only the depth map. So if disparity shift is set to 50, and disparity value obtained is 90, the real disparity is 140. + +**Compared to Extended disparity**, disparity shift: + +- **(+)** Is faster, as it doesn't require an extra computation, which means there's also no extra latency +- **(-)** Reduces the MaxZ (significantly), while extended disparity only reduces MinZ. + +Disparity shift can be combined with extended disparity. + +.. doxygenfunction:: dai::StereoDepthConfig::setDisparityShift + :project: depthai-core + :no-link: + +Close range depth limitations +----------------------------- + +Since depth is calculated from disparity, which requires the pixels to overlap, there is inherently a vertical +band on the left side of the left mono camera and on the right side of the right mono camera, where depth +can not be calculated, since it is seen by only 1 stereo camera. + +At very close distance, even when enabling :ref:`Stereo Extended Disparity mode` and :ref:`Lowering resolution `, +you will notice this vertical band of invalid depth pixel. + +.. image:: https://user-images.githubusercontent.com/59799831/135310921-67726c28-07e7-4ffa-bc8d-74861049517e.png + +Meaning of variables on the picture: + +- ``BL [cm]`` - Baseline of stereo cameras. +- ``Dv [cm]`` - Minimum distance where both cameras see an object (thus where depth can be calculated). +- ``W [pixels]`` - Width of mono in pixels camera or amount of horizontal pixels, also noted as :code:`HPixels` in other formulas. +- ``D [cm]`` - Distance from the **camera plane** to an object (see image :ref:`here `). + +.. image:: https://user-images.githubusercontent.com/59799831/135310972-c37ba40b-20ad-4967-92a7-c71078bcef99.png + +With the use of the :code:`tan` function, the following formulas can be obtained: + + +.. math:: + F = 2 * D * tan(HFOV/2) + Dv = (BL/2) * tan(90 - HFOV/2) + +In order to obtain :code:`B`, we can use :code:`tan` function again (same as for :code:`F`), but this time +we must also multiply it by the ratio between :code:`W` and :code:`F` in order to convert units to pixels. +That gives the following formula: + +.. math:: + + B = 2 * Dv * tan(HFOV/2) * W / F + B = 2 * Dv * tan(HFOV/2) * W / (2 * D * tan(HFOV/2)) + B = W * Dv / D # pixels + +Example: If we are using OAK-D, which has a :code:`HFOV` of 72°, a baseline (:code:`BL`) of 7.5 cm and +:code:`640x400 (400P)` resolution is used, therefore :code:`W = 640` and an object is :code:`D = 100` cm away, we can +calculate :code:`B` in the following way: + +.. math:: + + Dv = 7.5 / 2 * tan(90 - 72/2) = 3.75 * tan(54°) = 5.16 cm + B = 640 * 5.16 / 100 = 33 # pixels + +Credit for calculations and images goes to our community member gregflurry, which he made on +`this `__ +forum post. + +5. Long range stereo depth +************************** + +To get accurate long-range depth, you'd first need to follow :ref:`3. Improving depth accuracy` steps. + + + -- Extended mode -- Lower resolution -- Disparity shift -- Closer baseline distance (OAK-D-SR) -Long range stereo depth -*********************** - Subpixel mode - Narrow FOV lenses/wider baseline distance -> OAK-D-LR -Fixing noisy pointcloud -*********************** +6. Fixing noisy pointcloud +************************** + +:ref:`Stereo subpixel affect on layering` - First check Depth is noisy - Voxalization + remove statistical outliers @@ -249,10 +430,4 @@ Best practices in certain environments - In high dynamic range env (like outside), use brightness filter (img above) - In more static env, temporal filter - - - - - - .. include:: /includes/footer-short.rst \ No newline at end of file From 9ceb05ebf33f931e9b40e0406c04e47bacd4ff96 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 6 Mar 2023 01:51:36 +0200 Subject: [PATCH 198/385] Update FW with IMU fix for BNO --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 0bf416df5..14fed3c11 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 0bf416df58599e4e0eb4207b8ae37379bee083e7 +Subproject commit 14fed3c118cff2f663838056731f707f7c7b039c From bb95b30b0a145b8696af2180efd8d4c09883f7a6 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 6 Mar 2023 04:50:54 +0200 Subject: [PATCH 199/385] Add IMU versioning; firmware versioning, firmware update status callbacks --- depthai-core | 2 +- examples/IMU/imu_firmware_update.py | 15 ++++++++++++++- examples/IMU/imu_rotation_vector.py | 13 ++++++++++++- examples/IMU/imu_version.py | 9 +++++++++ src/DeviceBindings.cpp | 3 +++ src/pipeline/datatype/IMUDataBindings.cpp | 7 +++++++ 6 files changed, 46 insertions(+), 3 deletions(-) create mode 100755 examples/IMU/imu_version.py diff --git a/depthai-core b/depthai-core index 14fed3c11..6489b1e1c 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 14fed3c118cff2f663838056731f707f7c7b039c +Subproject commit 6489b1e1cffdcc25148d450493354a698bcc678b diff --git a/examples/IMU/imu_firmware_update.py b/examples/IMU/imu_firmware_update.py index af8cb6be1..4d047a21a 100755 --- a/examples/IMU/imu_firmware_update.py +++ b/examples/IMU/imu_firmware_update.py @@ -5,6 +5,12 @@ import time import math +device = dai.Device() + +imuVersion = device.getConnectedIMUVersion() +imuFirmwareVersion = device.getIMUFirmwareVersion() +print(f"IMU version: {imuVersion}, firmware version: {imuFirmwareVersion}") + print("Warning! Flashing IMU firmware can potentially soft brick your device and should be done with caution.") print("Do not unplug your device while the IMU firmware is flashing.") print("Type 'y' and press enter to proceed, otherwise exits: ") @@ -39,11 +45,18 @@ imu.enableFirmwareUpdate(True) # Pipeline is defined, now we can connect to the device -with dai.Device(pipeline) as device: +with device: + device.startPipeline(pipeline) def timeDeltaToMilliS(delta) -> float: return delta.total_seconds()*1000 + fwUpdatePending = True + while fwUpdatePending: + fwUpdatePending, percentage = device.getIMUFirmwareUpdateStatus() + print(f"IMU FW update status: {percentage:.1f}%") + time.sleep(1) + # Output queue for imu bulk packets imuQueue = device.getOutputQueue(name="imu", maxSize=50, blocking=False) baseTs = None diff --git a/examples/IMU/imu_rotation_vector.py b/examples/IMU/imu_rotation_vector.py index 7628b79cf..c88b17b4c 100755 --- a/examples/IMU/imu_rotation_vector.py +++ b/examples/IMU/imu_rotation_vector.py @@ -5,6 +5,16 @@ import time import math +device = dai.Device() + +imuVersion = device.getConnectedIMUVersion() +imuFirmwareVersion = device.getIMUFirmwareVersion() +print(f"IMU version: {imuVersion}, firmware version: {imuFirmwareVersion}") + +if imuVersion != dai.IMUVersion.BNO086: + print("Rotation vector output is supported only by BNO086!") + exit(1) + # Create pipeline pipeline = dai.Pipeline() @@ -28,7 +38,8 @@ imu.out.link(xlinkOut.input) # Pipeline is defined, now we can connect to the device -with dai.Device(pipeline) as device: +with device: + device.startPipeline(pipeline) def timeDeltaToMilliS(delta) -> float: return delta.total_seconds()*1000 diff --git a/examples/IMU/imu_version.py b/examples/IMU/imu_version.py new file mode 100755 index 000000000..146aac775 --- /dev/null +++ b/examples/IMU/imu_version.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 + +import depthai as dai + +device = dai.Device() + +imuVersion = device.getConnectedIMUVersion() +imuFirmwareVersion = device.getIMUFirmwareVersion() +print(f"IMU version: {imuVersion}, firmware version: {imuFirmwareVersion}") diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 095995a00..f8b1cbd70 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -595,6 +595,9 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def("getConnectedCameras", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedCameras(); }, DOC(dai, DeviceBase, getConnectedCameras)) .def("getConnectedCameraFeatures", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedCameraFeatures(); }, DOC(dai, DeviceBase, getConnectedCameraFeatures)) .def("getCameraSensorNames", [](DeviceBase& d) { py::gil_scoped_release release; return d.getCameraSensorNames(); }, DOC(dai, DeviceBase, getCameraSensorNames)) + .def("getConnectedIMUVersion", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedIMUVersion(); }, DOC(dai, DeviceBase, getConnectedIMUVersion)) + .def("getIMUFirmwareVersion", [](DeviceBase& d) { py::gil_scoped_release release; return d.getIMUFirmwareVersion(); }, DOC(dai, DeviceBase, getIMUFirmwareVersion)) + .def("getIMUFirmwareUpdateStatus", [](DeviceBase& d) { py::gil_scoped_release release; return d.getIMUFirmwareUpdateStatus(); }, DOC(dai, DeviceBase, getIMUFirmwareUpdateStatus)) .def("getDdrMemoryUsage", [](DeviceBase& d) { py::gil_scoped_release release; return d.getDdrMemoryUsage(); }, DOC(dai, DeviceBase, getDdrMemoryUsage)) .def("getCmxMemoryUsage", [](DeviceBase& d) { py::gil_scoped_release release; return d.getCmxMemoryUsage(); }, DOC(dai, DeviceBase, getCmxMemoryUsage)) .def("getLeonCssHeapUsage", [](DeviceBase& d) { py::gil_scoped_release release; return d.getLeonCssHeapUsage(); }, DOC(dai, DeviceBase, getLeonCssHeapUsage)) diff --git a/src/pipeline/datatype/IMUDataBindings.cpp b/src/pipeline/datatype/IMUDataBindings.cpp index 4aedeafe6..47b78c98c 100644 --- a/src/pipeline/datatype/IMUDataBindings.cpp +++ b/src/pipeline/datatype/IMUDataBindings.cpp @@ -16,6 +16,7 @@ void bind_imudata(pybind11::module& m, void* pCallstack){ using namespace dai; + py::enum_ imuVersion(m, "IMUVersion"); py::class_> imuReport(m, "IMUReport", DOC(dai, IMUReport)); py::enum_ imuReportAccuracy(imuReport, "Accuracy"); py::class_> imuReportAccelerometer(m, "IMUReportAccelerometer", DOC(dai, IMUReportAccelerometer)); @@ -40,6 +41,12 @@ void bind_imudata(pybind11::module& m, void* pCallstack){ /////////////////////////////////////////////////////////////////////// // Metadata / raw + imuVersion + .value("NONE", IMUVersion::NONE) + .value("BNO086", IMUVersion::BNO086) + .value("BMI270", IMUVersion::BMI270) + ; + imuReport .def(py::init<>()) .def_readwrite("sequence", &IMUReport::sequence) From f5841a5dceec7564512bb25759bc60bdc9315b4f Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 6 Mar 2023 17:44:59 +0100 Subject: [PATCH 200/385] Added brightness filter, long range stereo depth and fixing noising pointcloud sections to the docs --- .../tutorials/configuring-stereo-depth.rst | 86 ++++++++++++++----- 1 file changed, 65 insertions(+), 21 deletions(-) diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst index c9cdcd03e..60fbdfe30 100644 --- a/docs/source/tutorials/configuring-stereo-depth.rst +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -177,6 +177,25 @@ Spatial filter Spatial Edge-Preserving Filter will fill invalid depth pixels with valid neighboring depth pixels. It performs a series of 1D horizontal and vertical passes or iterations, to enhance the smoothness of the reconstructed data. It is based on `this research paper `__. +Brightness filter +~~~~~~~~~~~~~~~~~ + +Brightness filter will filter out (invalidate, by setting to 0) all depth pixels for which input stereo camera image pixels are outside the configured +min/max brightness threshold values. This filter is useful when you have high dynamic range scene, like outside on a bright day, or in general whenever +stereo camera pair can directly see a light source: + +.. figure:: https://user-images.githubusercontent.com/18037362/216110871-fe807fc0-858d-4c4d-bbae-3a8eff35645d.png + + Direct light source (ceiling light) - depth pixels are invalid + +It also helps with rectification "artifacts", especially when you have Wide FOV lenses and you apply alpha param. When there's no available pixel, +StereoDepth node will set that area to 0 (black) by default, but can be changed with ``stereoDepth.setRectifyEdgeFillColor(int8)``. This black area can then be +invalidated with brightness filter, as seen below: + +.. figure:: https://user-images.githubusercontent.com/18037362/223171135-734babe6-72b4-4aa1-9741-9fd8b4552555.jpeg + + Invalidating depth where we have rectification "artifacts" + Threshold filter ~~~~~~~~~~~~~~~~ @@ -187,18 +206,21 @@ Decimation filter ~~~~~~~~~~~~~~~~~ Decimation Filter will sub-samples the depth map, which means it reduces the depth scene complexity and allows other filters to run faster. Setting -*decimationFactor* to 2 will downscale 1280x800 depth map to 640x400. +*decimationFactor* to 2 will downscale 1280x800 depth map to 640x400. We can either select pixel skipping, median, or mean decimation mode, and the latter two +modes help with filtering as well. + +It's also very useful for :ref:`for pointclouds `. 3. Improving depth accuracy *************************** -The above chapter () was focused on noise, which isn't necessary the only reason for +The above chapter we focused on noise, which isn't necessary the only reason for inaccurate depth. There are a few ways to improve depth accuracy: - (mentioned above) :ref:`Fixing noisy depth <2. Fixing noisy depth>` - depth should be high quality in order to be accurate -- (mentioned above) :ref:`Stereo depth confidence threshold` should we low(er) in order to get the best accuracy +- (mentioned above) :ref:`Stereo depth confidence threshold` should be low(er) in order to get the best accuracy - :ref:`Move camera closer to the object` for best depth accuracy - Enable :ref:`Stereo Subpixel mode`, especially if object/scene isn't close to MinZ of the camera @@ -397,37 +419,59 @@ forum post. 5. Long range stereo depth ************************** -To get accurate long-range depth, you'd first need to follow :ref:`3. Improving depth accuracy` steps. +To get accurate long-range depth, we should first check :ref:`3. Improving depth accuracy` steps, +as they are especially applicable to long-range depth. +For long-range depth, we should also consider the following: +- Narrow FOV lenses +- Wide baseline distance between stereo cameras +That's why for long range, **we suggest using** `OAK-D LR `__, +which has a (larger) baseline distance of 15cm and default FOV of 60°. It has `M12 mount lenses `__, +so users can replace these with even narrower (or wider) FOV lenses. +6. Fixing noisy pointcloud +************************** +For noisy pointcloud we suggest a few approaches: -- Subpixel mode -- Narrow FOV lenses/wider baseline distance -> OAK-D-LR +* (mentioned above) Start with the :ref:`Fixing noisy depth <2. Fixing noisy depth>` chapter, as otherwise noise will produce points all over the pointcloud +* (mentioned above) Continue with :ref:`Improving depth accuracy <3. Improving depth accuracy>` chapter - depth inaccuracy will be easily visible in pointcloud + * Enable Stereo subpixel mode, especially due to :ref:`Stereo subpixel affect on layering` +* :ref:`Decimation filter for pointcloud` for faster processing (FPS) and additional filtering +* :ref:`Invalidating pixels around the corner` should help to reduce noise around the corners of the depth frame +* :ref:`Host-side pointcloud filtering` for additional filtering +Decimation filter for pointcloud +-------------------------------- -6. Fixing noisy pointcloud -************************** +:ref:`Decimation filter` is especially useful for pointclouds, you don't really want 1 million points (even though it sounds good for marketing), +as it's too much data to process. Decimation filter helps here, and should be enabled when working with pointclouds. + +When using decimation filter for pointcloud you should enable **median/mean mode decimation**, as it will provide additional filtering (compared to pixel skipping mode). +It also makes other :ref:`Stereo postprocessing filters` faster, since there will be less data to process. -:ref:`Stereo subpixel affect on layering` +Invalidating pixels around the corner +------------------------------------- -- First check Depth is noisy -- Voxalization + remove statistical outliers -- Invalidation a few pixels around the corner of depth image, invalidating left part (eg. 30 pixels) -- Brightness filter (remove black part from rectification) -- Decimation filter? +There are often invalid/noisy pixels around the corners, and we have seen that some customers preventively invalidate a few pixels (eg. 3) all around the corner of depth +image. We also suggest enabling :ref:`Brightness filter`, especially due to rectification "artifacts". -Decimation filter - for PCL, you don't really want 1 million points (Although it sounds good from marketing POV), as it's too much data to process. -Decimation filter helps here, I'd go as far as must have. It has capabilities of filtering also, if you change it to median mode vs default pixel skipping, -not just reducing image size. It also makes the other filters faster, since there will be less data to process. +Host-side pointcloud filtering +------------------------------ -Best practices in certain environments -************************************** +Besides device-side :ref:`Stereo postprocessing filters`, we also suggest running host-side pointcloud filtering (with eg. `Open3D `__, or `PCL `__ library). + +We especially suggest using pointcloud voxalization and removing statistical outliers techniques, `example here `__ for both of these. + + +.. + Best practices in certain environments + ************************************** -- In high dynamic range env (like outside), use brightness filter (img above) -- In more static env, temporal filter + - In high dynamic range env (like outside), use brightness filter (img above) + - In more static env, temporal filter .. include:: /includes/footer-short.rst \ No newline at end of file From 30ceb4a346f662dd0927efacbae6fcc8c5243473 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 6 Mar 2023 18:45:55 +0200 Subject: [PATCH 201/385] Update FW with fix for BMI timestamp --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 14fed3c11..0655df128 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 14fed3c118cff2f663838056731f707f7c7b039c +Subproject commit 0655df12801f45436c9d1119339ea1b4cc1ca1cf From 5c97a103c206c9f7a1602acb533f407dd1a3caf9 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 6 Mar 2023 19:34:54 +0100 Subject: [PATCH 202/385] Moved some docs around, fixed docs errors --- docs/source/components/nodes/stereo_depth.rst | 60 ------------------- .../tutorials/configuring-stereo-depth.rst | 13 ++-- 2 files changed, 8 insertions(+), 65 deletions(-) diff --git a/docs/source/components/nodes/stereo_depth.rst b/docs/source/components/nodes/stereo_depth.rst index 5c2580ac3..49a66c975 100644 --- a/docs/source/components/nodes/stereo_depth.rst +++ b/docs/source/components/nodes/stereo_depth.rst @@ -286,66 +286,6 @@ as: For the final disparity map, a filtering is applied based on the confidence threshold value: the pixels that have their confidence score larger than the threshold get invalidated, i.e. their disparity value is set to zero. You can set the confidence threshold with :code:`stereo.initialConfig.setConfidenceThreshold()`. - -Min stereo depth distance -========================= - -If the depth results for close-in objects look weird, this is likely because they are below the minimum depth-perception distance of the device. - -To calculate this minimum distance, use the :ref:`depth formula ` and choose the maximum value for disparity_in_pixels parameter (keep in mind it is inveresly related, so maximum value will yield the smallest result). - -For example OAK-D has a baseline of **7.5cm**, focal_length_in_pixels of **882.5 pixels** and the default maximum value for disparity_in_pixels is **95**. By using the :ref:`depth formula ` we get: - -.. code-block:: python - - min_distance = focal_length_in_pixels * baseline / disparity_in_pixels = 882.5 * 7.5cm / 95 = 69.67cm - -or roughly 70cm. - -However this distance can be cut in 1/2 (to around 35cm for the OAK-D) with the following options: - -1. Changing the resolution to 640x400, instead of the standard 1280x800. - -2. Enabling Extended Disparity. - -Extended Disparity mode increases the levels of disparity to 191 from the standard 96 pixels, thereby 1/2-ing the minimum depth. It does so by computing the 96-pixel disparities on the original 1280x720 and on the downscaled 640x360 image, which are then merged to a 191-level disparity. For more information see the Extended Disparity tab in :ref:`this table `. - -Using the previous OAK-D example, disparity_in_pixels now becomes **190** and the minimum distance is: - -.. code-block:: python - - min_distance = focal_length_in_pixels * baseline / disparity_in_pixels = 882.5 * 7.5cm / 190 = 34.84cm - -or roughly 35cm. - -.. note:: - - Applying both of those options is possible, which would set the minimum depth to 1/4 of the standard settings, but at such short distances the minimum depth is limited by focal length, which is 19.6cm, since OAK-D mono cameras have fixed focus distance: 19.6cm - infinity. - -See `these examples `__ for how to enable Extended Disparity. - -Max stereo depth distance -========================= - -The maximum depth perception distance depends on the :ref:`accuracy of the depth perception `. The formula used to calculate this distance is an approximation, but is as follows: - -.. code-block:: python - - Dm = (baseline/2) * tan((90 - HFOV / HPixels)*pi/180) - -So using this formula for existing models the *theoretical* max distance is: - -.. code-block:: python - - # For OAK-D (7.5cm baseline) - Dm = (7.5/2) * tan((90 - 71.9/1280)*pi/180) = 3825.03cm = 38.25 meters - - # For OAK-D-CM4 (9cm baseline) - Dm = (9/2) * tan((90 - 71.9/1280)*pi/180) = 4590.04cm = 45.9 meters - -If greater precision for long range measurements is required, consider enabling Subpixel Disparity or using a larger baseline distance between mono cameras. For a custom baseline, you could consider using `OAK-FFC `__ device or design your own baseboard PCB with required baseline. For more information see Subpixel Disparity under the Stereo Mode tab in :ref:`this table `. - - Measuring real-world object dimensions ====================================== diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst index 60fbdfe30..7d25254da 100644 --- a/docs/source/tutorials/configuring-stereo-depth.rst +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -284,14 +284,14 @@ One can change the number of subpixel bits by setting ``stereoDepth.setSubpixelF *************************** To get accurate short-range depth, you'd first need to follow :ref:`3. Improving depth accuracy` steps. -For most normal FOV OV9282 OAK-D* cameras, you'd want to have object/scene about 70cm away from the camera, +For most normal-FOV, OV9282 OAK-D* cameras, you'd want to have object/scene about 70cm away from the camera, where you'd get below 2% error (with good :ref:`Scene Texture`), so ± 1.5cm error. But how to get an even better depth accuracy, eg. **sub-cm stereo depth accuracy**? As we have learned at :Ref:`How baseline distance and focal length affect depth`, we would want to -have closer baseline distance and/or narrower FOV lenses. +have a closer baseline distance and/or narrower FOV lenses. -That's why, for short range depth perception, **we suggest using** `OAK-D SR `__, +That's why for the short range depth perception **we suggest using** `OAK-D SR `__, which has 2 cm baseline distance, 800P resolution, and is ideal for depth sensing of up to 1 meter. Going back to :ref:`Depth from disparity`, minimal depth perception (**MinZ**) is defined by the following formula, where disparity is 95 pixels @@ -305,12 +305,15 @@ Going back to :ref:`Depth from disparity`, minimal depth perception (**MinZ**) i How to get lower MinZ --------------------- -You can get lower MinZ for OAK cameras by: +If the depth results for close-in objects look weird, this is likely because they are below MinZ distance of the OAK camera. You can get lower MinZ for OAK cameras by either: - :ref:`Lowering resolution ` - Enabling :ref:`Stereo Extended Disparity mode` - Using :ref:`Disparity shift` - suggested in controlled environment, where MaxZ is known +Both of these last 2 options can be enabled at the same time, which would set the minimum depth to 1/4 of the standard settings, but at such short distances the MinZ +could be limited by the focal length. + Lowering resolution to decrease MinZ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -341,7 +344,7 @@ it will also decrease min depth (MinZ) perception. Disparity shift can be combin .. image:: https://user-images.githubusercontent.com/18037362/189375017-2fa137d2-ad6b-46de-8899-6304bbc6c9d7.png -**Left graph** shows min and max disparity and depth for OAK-D (7.5cm baseline, 800P resolution, ~70° HFOV) by default (disparity shift=0). See :ref:`Calculate depth using disparity map`. +**Left graph** shows min and max disparity and depth for OAK-D (7.5cm baseline, 800P resolution, ~70° HFOV) by default (disparity shift=0). See :ref:`Depth from disparity`. Since hardware (stereo block) has a fixed 95 pixel disparity search, DepthAI will search from 0 pixels (depth=INF) to 95 pixels (depth=71cm). **Limitations**: From 6df282f71c4516446c39028b3f966bf852847f10 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 6 Mar 2023 22:41:11 +0100 Subject: [PATCH 203/385] Added doc page to toctree --- docs/source/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 3dc8e8fb2..cb2651049 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -71,9 +71,9 @@ node functionalities are presented with code. :hidden: :caption: Tutorials: - tutorials/hello_world.rst tutorials/standalone_mode.rst tutorials/message_syncing.rst + tutorials/configuring-stereo-depth.rst tutorials/multiple.rst tutorials/maximize_fov.rst tutorials/debugging.rst @@ -81,6 +81,7 @@ node functionalities are presented with code. tutorials/dispaying_detections.rst tutorials/image_quality.rst tutorials/low-latency.rst + tutorials/hello_world.rst .. toctree:: :maxdepth: 1 From c49bc66f7b9ca2f67e24c2a74fca90a5ede0709d Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 7 Mar 2023 00:03:05 +0100 Subject: [PATCH 204/385] Fixed some typos --- .../tutorials/configuring-stereo-depth.rst | 134 ++++++++++-------- 1 file changed, 71 insertions(+), 63 deletions(-) diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst index 7d25254da..263749cdc 100644 --- a/docs/source/tutorials/configuring-stereo-depth.rst +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -19,8 +19,8 @@ This documentation is divided into 6 chapters: `Stereo depth vision `__ works by calculating the disparity between two images taken from slightly different points. -Stereo vision works a lot like our eyes. Our brains (subconsciously) estimate the depth of objects and scenes based on the difference of what our left eye sees -versus what our right eye sees. On OAK-D cameras, it's exactly the same; we have left and right camera (of the stereo camera pair) +Stereo vision works a lot like our eyes. Our brains (subconsciously) estimate the depth of objects and scenes based on the difference between what our left eye sees +versus what our right eye sees. On OAK-D cameras, it's exactly the same; we have left and right cameras (of the stereo camera pair) and the OAK does on-device disparity matching to estimate the depth of objects and scenes. Disparity refers to the distance between two corresponding points in the left and right image of a stereo pair. @@ -34,14 +34,14 @@ Let's first look at how the depth is calculated: .. math:: - depth [mm] = focal_length [pixels] * baseline [mm] / disparity [pixels] + depth [m] = focalLength [pix] * baseline [m] / disparity [pix] `RVC2 `__-based cameras have a **0..95 disparity search** range, which limits the minimal depth perception. Baseline is the distance between two cameras of the -stereo camera pair. You can read camera's focal length (in pixels) from calibration, see :ref:`tutorial here ` +stereo camera pair. You can read the camera's focal length (in pixels) from calibration, see the :ref:`tutorial here ` -Disparity and depth are inversely related. As disparity decreases, depth increases exponentially depending on baseline and focal length. +Disparity and depth are inversely related. As disparity decreases, the depth increases exponentially depending on the baseline and focal length. Meaning, if the disparity value is close to zero, then a small change in disparity generates a large change in depth. Similarly, if the disparity value is big, then some change in disparity doesn't lead to a large change in depth (better accuracy). @@ -56,16 +56,16 @@ Note the value of disparity depth data is stored in *uint16*, where 0 means that How baseline distance and focal length affect depth --------------------------------------------------- -Looking at the depth formula above, we can see that either larger baseline distance, or larger focal length will result +Looking at the depth formula above, we can see that either a larger baseline distance or a larger focal length will result in further depth at the same disparity, which means that the depth accuracy will be better. Focal length is the distance between the camera lens and the image sensor. The larger the focal length, the narrower the FOV. -So to get **long range depth** perception, you can **increase the baseline distance and/or decrease the FOV**. +So to get **long-range depth** perception, you can **increase the baseline distance and/or decrease the FOV**. .. note:: - Wider FOV will result in worse depth accuracy, even at shorter range (but at shorter range it won't be as noticeable). + Wider FOV will result in worse depth accuracy, even at shorter ranges (where accuracy drop isn't as noticeable). 2. Fixing noisy depth ********************* @@ -80,12 +80,10 @@ A few topics we have noticed that are relevant for stereo depth quality are: Scene Texture ------------- -Due to the way the stereo matching algorithm works, **passive stereo depth requires** to have a **good texture** in the scene, otherwise the depth will be noisy/invalid. +Due to the way the stereo matching algorithm works, **passive stereo depth requires** to have a **good texture** in the scene, otherwise, the depth will be noisy/invalid. low-visual-interest surfaces (blank surfaces with little to no texture), such as a wall or floor. -**Solution** - -Our `Pro version `__ of OAK cameras have onboard **IR laser dot projector**, +**Solution:** Our `Pro version `__ of OAK cameras have onboard **IR laser dot projector**, which projects thousands of little dots on the scene, which helps the stereo matching algorithm as it provides more texture to the scene. .. image:: https://user-images.githubusercontent.com/18037362/222730554-a6c8d4d3-cb0b-422e-8474-6a979e73727a.gif @@ -98,15 +96,15 @@ Stereo depth confidence threshold --------------------------------- When calculating the disparity, each pixel in the disparity map gets assigned a confidence value :code:`0..255` by the stereo matching algorithm. -This confidence score is kind-of inverted (if say comparing with NN): +This confidence score is kind of inverted (if, say, comparing with NN confidences): - **0** - maximum confidence that it holds a valid value - **255** - minimum confidence, so there is more chance that the value is incorrect -For the final disparity map, a filtering is applied based on the confidence threshold value: the pixels that have their confidence score larger than +For the final disparity map, filtering is applied based on the confidence threshold value: the pixels that have their confidence score larger than the threshold get invalidated, i.e. their disparity value is set to zero. You can set the confidence threshold via the API below. -This means that with the confidence threshold users can prioritize **fill-rate or accuracy**. +This means that with the confidence threshold, users can prioritize **fill-rate or accuracy**. .. code-block:: python @@ -126,7 +124,7 @@ This means that with the confidence threshold users can prioritize **fill-rate o Stereo camera pair noise ------------------------ -If input left/right images are noisy, the disparity map will be noisy as well. So prerequisite for good depth are high IQ (see :ref:`Image Quality ` docs) +If input left/right images are noisy, the disparity map will be noisy as well. So the prerequisite for good depth are high IQ (see :ref:`Image Quality ` docs) left/right stereo images. Active stereo (`Pro version `__ of OAK cameras) mostly alleviates this issue, but for passive stereo cameras, there are a few things you can do to improve the quality of the stereo camera pair. @@ -137,16 +135,16 @@ which leads to better SNR (signal-to-noise ratio). For better low-light performance, it's advised to use longer exposure times instead of higher gain (ISO) as it will improve SNR. Sometimes this means lowering camera FPS - at 30 FPS, you can use 1/30s exposure time, at 15 FPS, you can use 1/15s exposure time, etc. For more information, see :ref:`Low-light increased sensitivity`. -Another potential improvement is to tweak sensor's ISP settings, like chroma & luma denoise, and sharpness. For more information, see :ref:`Color camera ISP configuration`. +Another potential improvement is to tweak the sensor's ISP settings, like chroma & luma denoise, and sharpness. For more information, see the :ref:`Color camera ISP configuration`. Stereo postprocessing filters ----------------------------- -Stereo depth node has a few postprocessing filters that **run on-device**, which can be enabled to improve the quality of the disparity map. For **implementation -(API) details**, see :ref:`StereoDepth configurable blocks `. For an example, see :ref:`Depth Post-Processing` example. +The :ref:`StereoDepth`` node has a few postprocessing filters that **run on-device**, which can be enabled to improve the quality of the disparity map. For **implementation +(API) details**, see :ref:`StereoDepth configurable blocks `. For an example, see the :ref:`Depth Post-Processing` example. -As these filters run on device, it has a **decent performance cost**, which mean that at high-resolution frames (1MP) these might bottleneck the FPS. To improve -the cost, one should consider using lower-resolution frames (eg. 400P) or use :ref:`Decimation filter`. Due to additional processing, these filters also introduce +As these filters run on the device, it has a some **performance cost**, which means that at high-resolution frames (1MP) these might bottleneck the FPS. To improve +the cost, one should consider using lower-resolution frames (eg. 400P) and/or using :ref:`Decimation filter`. Due to additional processing, these filters also introduce :ref:`additional latency `. Median filter @@ -168,7 +166,7 @@ Temporal Filter is intended to improve the depth data persistency by manipulatin the data, adjusting the depth values while also updating the tracking history. In cases where the pixel data is missing or invalid, the filter uses a user-defined persistency mode to decide whether the missing value should be improved -with stored data. Note that due to its reliance on historic data the filter may introduce +with stored data. Note that due to its reliance on historic data, the filter may introduce visible motion blurring/smearing artifacts, and therefore is best-suited for **static scenes**. Spatial filter @@ -181,7 +179,7 @@ Brightness filter ~~~~~~~~~~~~~~~~~ Brightness filter will filter out (invalidate, by setting to 0) all depth pixels for which input stereo camera image pixels are outside the configured -min/max brightness threshold values. This filter is useful when you have high dynamic range scene, like outside on a bright day, or in general whenever +min/max brightness threshold values. This filter is useful when you have a high dynamic range scene, like outside on a bright day, or in general whenever stereo camera pair can directly see a light source: .. figure:: https://user-images.githubusercontent.com/18037362/216110871-fe807fc0-858d-4c4d-bbae-3a8eff35645d.png @@ -199,35 +197,34 @@ invalidated with brightness filter, as seen below: Threshold filter ~~~~~~~~~~~~~~~~ -Threshold filter will filter out all depth pixels outside the configured min/max threshold values. In controlled environment, where you know exactly how far the scene +Threshold filter will filter out all depth pixels outside the configured min/max threshold values. In a controlled environment, where you know exactly how far the scene can be (eg. 30cm - 2m) it's advised to use this filter. Decimation filter ~~~~~~~~~~~~~~~~~ -Decimation Filter will sub-samples the depth map, which means it reduces the depth scene complexity and allows other filters to run faster. Setting +Decimation Filter will sub-sample the depth map, which means it reduces the depth scene complexity and allows other filters to run faster. Setting *decimationFactor* to 2 will downscale 1280x800 depth map to 640x400. We can either select pixel skipping, median, or mean decimation mode, and the latter two modes help with filtering as well. -It's also very useful for :ref:`for pointclouds `. +It's also very useful :ref:`for pointclouds `. 3. Improving depth accuracy *************************** -The above chapter we focused on noise, which isn't necessary the only reason for -inaccurate depth. +The above chapter we focused on noise, which isn't necessarily the only reason for inaccurate depth. There are a few ways to improve depth accuracy: - (mentioned above) :ref:`Fixing noisy depth <2. Fixing noisy depth>` - depth should be high quality in order to be accurate - (mentioned above) :ref:`Stereo depth confidence threshold` should be low(er) in order to get the best accuracy -- :ref:`Move camera closer to the object` for best depth accuracy -- Enable :ref:`Stereo Subpixel mode`, especially if object/scene isn't close to MinZ of the camera +- :ref:`Move the camera closer to the object` for the best depth accuracy +- Enable :ref:`Stereo Subpixel mode`, especially if the object/scene isn't close to MinZ of the camera -Move camera closer to the object --------------------------------- +Move the camera closer to the object +------------------------------------ -Looking at :ref:`Depth from disparity` section, from the graph it's clear that at the 95 disparity pixels (close distance), +Looking at the :ref:`Depth from disparity` section, from the graph it's clear that at the 95 disparity pixels (close distance), depth change between disparity pixels (eg. 95->94) is the lowest, so the **depth accuracy is the best**. @@ -237,12 +234,12 @@ It's clear that depth accuracy decreases exponentially with the distance from th enabled you can have better depth accuracy (even at a longer distance) but it only works to some extent. So to conclude, **object/scene you are measuring** should be **as close as possible to MinZ** (minimal depth perception) of the camera -for **best depth accuracy**. You can find MinZ specification for each device in `hardware docs `__. +for **best depth accuracy**. You can find MinZ specification for each device in the `hardware documentation `__. Stereo Subpixel mode -------------------- -Let's first start at what Stereo Subpixel mode is and how it works. For image subpixel explanation, see `What's subpixel? `__). +Let's first start with what Stereo Subpixel mode is and how it works. For image subpixel explanation, see `What's subpixel? `__). .. note:: @@ -255,12 +252,12 @@ just note it's a very large output (eg. 1280*800*96 => 98MB for each frame). .. image:: /_static/images/components/disparity_confidence.jpg Stereo Subpixel mode will calculate subpixel disparity by looking at the confidence values of the 2 neighboring disparity pixels in each direction. -In above example graph, in normal mode, Stereo Depth would just get the max disparity = 34 pixels, but in Subpixel +In the above example graph, in normal mode, StereoDepth would just get the max disparity = 34 pixels, but in Subpixel mode, it will return a bit more, eg. 37.375 pixels, as confidences for pixels 35 and 36 are quite high as well. **TL;DR:** Stereo Subpixel mode should always provide more accurate depth, but will consume additional HW resources (see :ref:`Stereo depth FPS` for impact). -Stereo subpixel affect on layering +Stereo subpixel effect on layering ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Default stereo depth output has 0..95 disparity pixels, which would produce 96 unique depth values. This can especially be seen when using pointcloud representation @@ -270,12 +267,15 @@ Default stereo depth output has 0..95 disparity pixels, which would produce 96 u This layering can especially be seen at longer distances, where these layers are exponentially further apart. -But with Stereo Subpixel mode enabled, there are many more unique values possible, which produces a more granular depth steps, and thus smoother a pointcloud. +But with Stereo Subpixel mode enabled, there are many more unique values possible, which produces more granular depth steps, and thus smoother a pointcloud. .. math:: - 94 * 2^3 [subpixel bits] + 2 [min/max value] = 754 unique values + +.. math:: 94 * 2^4 [subpixel bits] + 2 [min/max value] = 1506 unique values + +.. math:: 94 * 2^5 [subpixel bits] + 2 [min/max value] = 3010 unique values One can change the number of subpixel bits by setting ``stereoDepth.setSubpixelFractionalBits(int)`` parameter to 3, 4 or 5 bits. @@ -284,22 +284,23 @@ One can change the number of subpixel bits by setting ``stereoDepth.setSubpixelF *************************** To get accurate short-range depth, you'd first need to follow :ref:`3. Improving depth accuracy` steps. -For most normal-FOV, OV9282 OAK-D* cameras, you'd want to have object/scene about 70cm away from the camera, +For most normal-FOV, OV9282 OAK-D* cameras, you'd want to have the object/scene about 70cm away from the camera, where you'd get below 2% error (with good :ref:`Scene Texture`), so ± 1.5cm error. But how to get an even better depth accuracy, eg. **sub-cm stereo depth accuracy**? As we have learned at :Ref:`How baseline distance and focal length affect depth`, we would want to have a closer baseline distance and/or narrower FOV lenses. -That's why for the short range depth perception **we suggest using** `OAK-D SR `__, +That's why for the short-range depth perception **we suggest using** `OAK-D SR `__, which has 2 cm baseline distance, 800P resolution, and is ideal for depth sensing of up to 1 meter. -Going back to :ref:`Depth from disparity`, minimal depth perception (**MinZ**) is defined by the following formula, where disparity is 95 pixels +Going back to :ref:`Depth from disparity`, minimal depth perception (**MinZ**) is defined by the following formula, where the disparity is 95 pixels (maximum number of pixel for disparity search): .. math:: - depth = focal_length * baseline / disparity + +.. math:: MinZ = focal_length * baseline / 95 How to get lower MinZ @@ -309,7 +310,7 @@ If the depth results for close-in objects look weird, this is likely because the - :ref:`Lowering resolution ` - Enabling :ref:`Stereo Extended Disparity mode` -- Using :ref:`Disparity shift` - suggested in controlled environment, where MaxZ is known +- Using :ref:`Disparity shift` - suggested in a controlled environment, where MaxZ is known Both of these last 2 options can be enabled at the same time, which would set the minimum depth to 1/4 of the standard settings, but at such short distances the MinZ could be limited by the focal length. @@ -317,43 +318,46 @@ could be limited by the focal length. Lowering resolution to decrease MinZ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Above we have a formula for MinZ, and by lowering resolution, we are lowering focal length (in pixels), so let's look at the formula again: +Above we have a formula for MinZ, and by lowering the resolution, we are lowering focal length (in pixels), so let's look at the formula again: .. math:: + MinZ = focalLength * baseline / 95 - MinZ = focal_length * baseline / 95 +.. math:: MinZ [800P OAK-D] = 882.5 * 7.5 / 95 = 70 cm + +.. math:: MinZ [400P OAK-D] = 441 * 7.5 / 95 = 35 cm -As you can see, by lowering resolution by 2, we are also lowering MinZ by 2. Note that because you have less pixels, you will also have lower depth accuracy (in cm). +As you can see, by lowering resolution by 2, we are also lowering MinZ by 2. Note that because you have fewer pixels, you will also have lower depth accuracy (in cm). Stereo Extended Disparity mode ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Very similar to :ref:`Lowering resolution to decrease MinZ`, Extended mode runs stereo depth pipeline twice (thus consuming more HW resources); once with resolution of -the frame that was passed to :ref:`StereoDepth` node, and once with resolution downscaled by 2, then combine the 2 output disparity maps. +the frame that was passed to :ref:`StereoDepth` node, and once with resolution downscaled by 2, then combines the 2 output disparity maps. Disparity shift ~~~~~~~~~~~~~~~ -In a controller environment, where MaxZ is known in advance, to perceive closer depth range it's advised to use disparity shift, as it doesn't decrease depth accuracy -as other 2 methods above do. +In a controlled environment, where MaxZ is known in advance, to perceive closer depth range it's advised to use disparity shift, as it doesn't decrease depth accuracy +as the other 2 methods above do. -Disparity shift will shift the starting point of the disparity search, which will significantly decrease max depth (MaxZ) perception, but -it will also decrease min depth (MinZ) perception. Disparity shift can be combined with extended/subpixel/LR-check modes. +Disparity shift will shift the starting point of the disparity search, which will significantly decrease MaxZ, but +it will also decrease the MinZ. Disparity shift can be combined with extended/subpixel/LR-check modes. .. image:: https://user-images.githubusercontent.com/18037362/189375017-2fa137d2-ad6b-46de-8899-6304bbc6c9d7.png -**Left graph** shows min and max disparity and depth for OAK-D (7.5cm baseline, 800P resolution, ~70° HFOV) by default (disparity shift=0). See :ref:`Depth from disparity`. +The **Left graph** shows min and max disparity and depth for OAK-D (7.5cm baseline, 800P resolution, ~70° HFOV) by default (disparity shift=0). See :ref:`Depth from disparity`. Since hardware (stereo block) has a fixed 95 pixel disparity search, DepthAI will search from 0 pixels (depth=INF) to 95 pixels (depth=71cm). **Limitations**: -**Right graph** shows the same, but at disparity shift set to 30 pixels. This means that disparity search will be from 30 pixels (depth=2.2m) to 125 pixels (depth=50cm). +The **Right graph** shows the same, but at disparity shift set to 30 pixels. This means that disparity search will be from 30 pixels (depth=2.2m) to 125 pixels (depth=50cm). This also means that depth will be very accurate at the short range (**theoretically** below 5mm depth error). -- Because of the inverse relationship between disparity and depth, MaxZ will decrease much faster than MinZ as the disparity shift is increased. Therefore, it is **advised not to use a larger than necessary disparity shift**. -- Tradeoff in reducing the MinZ this way is that objects at **distances farther away than MaxZ will not be seen**. +- Because of the inverse relationship between disparity and depth, MaxZ will decrease much faster than MinZ as the disparity shift is increased. Therefore, it is **advised not to use a larger-than-necessary disparity shift**. +- Teh tradeoff in reducing the MinZ this way is that objects at **distances farther away than MaxZ will not be seen**. - Because of the point above, **we only recommend using disparity shift when MaxZ is known**, such as having a depth camera mounted above a table pointing down at the table surface. - Output disparity map is not expanded, only the depth map. So if disparity shift is set to 50, and disparity value obtained is 90, the real disparity is 140. @@ -391,9 +395,10 @@ Meaning of variables on the picture: With the use of the :code:`tan` function, the following formulas can be obtained: - .. math:: F = 2 * D * tan(HFOV/2) + +.. math:: Dv = (BL/2) * tan(90 - HFOV/2) In order to obtain :code:`B`, we can use :code:`tan` function again (same as for :code:`F`), but this time @@ -401,13 +406,16 @@ we must also multiply it by the ratio between :code:`W` and :code:`F` in order t That gives the following formula: .. math:: - B = 2 * Dv * tan(HFOV/2) * W / F + +.. math:: B = 2 * Dv * tan(HFOV/2) * W / (2 * D * tan(HFOV/2)) + +.. math:: B = W * Dv / D # pixels -Example: If we are using OAK-D, which has a :code:`HFOV` of 72°, a baseline (:code:`BL`) of 7.5 cm and -:code:`640x400 (400P)` resolution is used, therefore :code:`W = 640` and an object is :code:`D = 100` cm away, we can +Example: If we are using OAK-D, which has a HFOV of 72°, a baseline (:code:`BL`) of 7.5 cm and +640x400 (400P) resolution is used, therefore :code:`W = 640` and an object is :code:`D = 100` cm away, we can calculate :code:`B` in the following way: .. math:: @@ -439,9 +447,9 @@ so users can replace these with even narrower (or wider) FOV lenses. For noisy pointcloud we suggest a few approaches: -* (mentioned above) Start with the :ref:`Fixing noisy depth <2. Fixing noisy depth>` chapter, as otherwise noise will produce points all over the pointcloud -* (mentioned above) Continue with :ref:`Improving depth accuracy <3. Improving depth accuracy>` chapter - depth inaccuracy will be easily visible in pointcloud - * Enable Stereo subpixel mode, especially due to :ref:`Stereo subpixel affect on layering` +* (mentioned above) Start with the :ref:`Fixing noisy depth <2. Fixing noisy depth>` chapter, as otherwise, noise will produce points all over the pointcloud +* (mentioned above) Continue with the :ref:`Improving depth accuracy <3. Improving depth accuracy>` chapter - depth inaccuracy will be easily visible in pointcloud + * Enable Stereo subpixel mode, especially due to the :ref:`Stereo subpixel effect on layering` * :ref:`Decimation filter for pointcloud` for faster processing (FPS) and additional filtering * :ref:`Invalidating pixels around the corner` should help to reduce noise around the corners of the depth frame From 9fcde642923e3a8d3ca098a2b5d8be095281e56e Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 7 Mar 2023 01:09:18 +0200 Subject: [PATCH 205/385] Update FW: IMU support for OAK-D-SR --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 0655df128..e3164680e 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 0655df12801f45436c9d1119339ea1b4cc1ca1cf +Subproject commit e3164680e5612523b8a10079cf91dab93d9b0caf From e7ff0026df806b6cb82baa253bcf3dae7bb75b6b Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 7 Mar 2023 11:58:37 +0100 Subject: [PATCH 206/385] Fixed docs building --- docs/source/tutorials/configuring-stereo-depth.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst index 263749cdc..febf7d927 100644 --- a/docs/source/tutorials/configuring-stereo-depth.rst +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -140,7 +140,7 @@ Another potential improvement is to tweak the sensor's ISP settings, like chroma Stereo postprocessing filters ----------------------------- -The :ref:`StereoDepth`` node has a few postprocessing filters that **run on-device**, which can be enabled to improve the quality of the disparity map. For **implementation +The :ref:`StereoDepth` node has a few postprocessing filters that **run on-device**, which can be enabled to improve the quality of the disparity map. For **implementation (API) details**, see :ref:`StereoDepth configurable blocks `. For an example, see the :ref:`Depth Post-Processing` example. As these filters run on the device, it has a some **performance cost**, which means that at high-resolution frames (1MP) these might bottleneck the FPS. To improve From 7cfb8d43fc505fbf2b26e7dfd49ee4e54835fe66 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 7 Mar 2023 11:59:37 +0100 Subject: [PATCH 207/385] Updated YoloDetectionNetwork docs --- docs/source/components/nodes/yolo_detection_network.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/source/components/nodes/yolo_detection_network.rst b/docs/source/components/nodes/yolo_detection_network.rst index f47827cd6..e01bb2bb1 100644 --- a/docs/source/components/nodes/yolo_detection_network.rst +++ b/docs/source/components/nodes/yolo_detection_network.rst @@ -1,9 +1,8 @@ YoloDetectionNetwork ==================== -Yolo detection network node is very similar to :ref:`NeuralNetwork` (in fact it extends it). The only difference is that this node -is specifically for the **(tiny) Yolo V3/V4** NN and it decodes the result of the NN on device. This means that :code:`Out` of this node is not a -:ref:`NNData` (a byte array) but a :ref:`ImgDetections` that can easily be used in your code. +Yolo detection network extends :ref:`NeuralNetwork` node by also adding **YOLO NN result decoding**, which happens on the OAK device. +This means that :code:`Out` of this node is not a :ref:`NNData` (a byte array) but a :ref:`ImgDetections` that can easily be used in your code. How to place it ############### From 191551c93bccd69575364f1c21bc6edbc09e56a0 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 7 Mar 2023 14:56:52 +0100 Subject: [PATCH 208/385] Addressed PR comments --- .../tutorials/configuring-stereo-depth.rst | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst index febf7d927..76da3da9c 100644 --- a/docs/source/tutorials/configuring-stereo-depth.rst +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -34,7 +34,7 @@ Let's first look at how the depth is calculated: .. math:: - depth [m] = focalLength [pix] * baseline [m] / disparity [pix] + depth [mm] = focalLength [pix] * baseline [mm] / disparity [pix] `RVC2 `__-based cameras have a **0..95 disparity search** range, @@ -51,7 +51,7 @@ Here's a graph showing disparity vs depth for OAK-D (7.5cm baseline distance) at `Full chart here `__ -Note the value of disparity depth data is stored in *uint16*, where 0 means that the distance is invalid/unknown. +Note the value of depth data is stored in *uint16*, where 0 means that the distance is invalid/unknown. How baseline distance and focal length affect depth --------------------------------------------------- @@ -106,16 +106,35 @@ the threshold get invalidated, i.e. their disparity value is set to zero. You ca This means that with the confidence threshold, users can prioritize **fill-rate or accuracy**. -.. code-block:: python +.. tabs:: - stereo_depth = pipeline.create(dai.node.StereoDepth) - stereo_depth.initialConfig.setConfidenceThreshold(int) + .. tab:: Python - # Or, alternatively, set the Stereo Preset Mode: - # Prioritize fill-rate, sets Confidence threshold to 245 - stereo_depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) - # Prioritize accuracy, sets Confidence threshold to 200 - stereo_depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_ACCURACY) + .. code-block:: python + + # Create the StereoDepth node + stereo_depth = pipeline.create(dai.node.StereoDepth) + stereo_depth.initialConfig.setConfidenceThreshold(threshold) + + # Or, alternatively, set the Stereo Preset Mode: + # Prioritize fill-rate, sets Confidence threshold to 245 + stereo_depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) + # Prioritize accuracy, sets Confidence threshold to 200 + stereo_depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_ACCURACY) + + .. tab:: C++ + + .. code-block:: cpp + + // Create the StereoDepth node + auto stereo_depth = pipeline.create(); + stereo_depth->initialConfig.setConfidenceThreshold(threshold); + + // Or, alternatively, set the Stereo Preset Mode: + // Prioritize fill-rate, sets Confidence threshold to 245 + stereo_depth->setDefaultProfilePreset(dai::node::StereoDepth::Preset::HIGH_DENSITY); + // Prioritize accuracy, sets Confidence threshold to 200 + stereo_depth->setDefaultProfilePreset(dai::node::StereoDepth::Preset::HIGH_ACCURACY); .. @@ -230,7 +249,7 @@ depth change between disparity pixels (eg. 95->94) is the lowest, so the **depth .. image:: /_static/images/components/theoretical_error.jpg -It's clear that depth accuracy decreases exponentially with the distance from the camera. Note that with :ref:`Stereo Subpixel mode` +Depth accuracy decreases exponentially with the distance from the camera. Note that with :ref:`Stereo Subpixel mode` enabled you can have better depth accuracy (even at a longer distance) but it only works to some extent. So to conclude, **object/scene you are measuring** should be **as close as possible to MinZ** (minimal depth perception) of the camera From f12d6d17b2f538842491bcf7fd78cbfe1dd5871d Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 7 Mar 2023 17:30:08 +0200 Subject: [PATCH 209/385] Fix 'default constrictible' error on some compilers --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index e3164680e..560b7539d 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit e3164680e5612523b8a10079cf91dab93d9b0caf +Subproject commit 560b7539d0c0aab43a42eb591c2b407306a45a40 From d218edafb63b04a51227693bfcb7170c527203b1 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 7 Mar 2023 18:15:09 +0200 Subject: [PATCH 210/385] Update core --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 560b7539d..4ed3413b6 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 560b7539d0c0aab43a42eb591c2b407306a45a40 +Subproject commit 4ed3413b6d3bf030988a0384e02eda72371bb8b0 From b5627696ca4c16a17a04683180a3172e588f9816 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 7 Mar 2023 19:39:45 +0200 Subject: [PATCH 211/385] Add IMU FW update RPC --- depthai-core | 2 +- examples/IMU/imu_firmware_update.py | 76 +++++------------------ examples/IMU/imu_version.py | 2 +- src/DeviceBindings.cpp | 4 +- src/pipeline/datatype/IMUDataBindings.cpp | 7 --- 5 files changed, 19 insertions(+), 72 deletions(-) diff --git a/depthai-core b/depthai-core index 6489b1e1c..49aaf1d49 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 6489b1e1cffdcc25148d450493354a698bcc678b +Subproject commit 49aaf1d4932bd2091629131bc6bb4064e9b69c0e diff --git a/examples/IMU/imu_firmware_update.py b/examples/IMU/imu_firmware_update.py index 4d047a21a..ac0ef8ed3 100755 --- a/examples/IMU/imu_firmware_update.py +++ b/examples/IMU/imu_firmware_update.py @@ -7,9 +7,10 @@ device = dai.Device() -imuVersion = device.getConnectedIMUVersion() +imuVersion = device.getConnectedIMU() imuFirmwareVersion = device.getIMUFirmwareVersion() -print(f"IMU version: {imuVersion}, firmware version: {imuFirmwareVersion}") +latestImuFirmwareVersion = device.getLatestAvailableIMUFirmwareVersion() +print(f"IMU version: {imuVersion}, firmware version: {imuFirmwareVersion}, latest available firmware version: {latestImuFirmwareVersion}") print("Warning! Flashing IMU firmware can potentially soft brick your device and should be done with caution.") print("Do not unplug your device while the IMU firmware is flashing.") @@ -18,70 +19,21 @@ print("Prompt declined, exiting...") exit(-1) -# Create pipeline -pipeline = dai.Pipeline() - -# Define sources and outputs -imu = pipeline.create(dai.node.IMU) -xlinkOut = pipeline.create(dai.node.XLinkOut) - -xlinkOut.setStreamName("imu") - -# enable ACCELEROMETER_RAW at 500 hz rate -imu.enableIMUSensor(dai.IMUSensor.ACCELEROMETER_RAW, 500) -# enable GYROSCOPE_RAW at 400 hz rate -imu.enableIMUSensor(dai.IMUSensor.GYROSCOPE_RAW, 400) -# it's recommended to set both setBatchReportThreshold and setMaxBatchReports to 20 when integrating in a pipeline with a lot of input/output connections -# above this threshold packets will be sent in batch of X, if the host is not blocked and USB bandwidth is available -imu.setBatchReportThreshold(1) -# maximum number of IMU packets in a batch, if it's reached device will block sending until host can receive it -# if lower or equal to batchReportThreshold then the sending is always blocking on device -# useful to reduce device's CPU load and number of lost packets, if CPU load is high on device side due to multiple nodes -imu.setMaxBatchReports(10) - -# Link plugins IMU -> XLINK -imu.out.link(xlinkOut.input) - -imu.enableFirmwareUpdate(True) - # Pipeline is defined, now we can connect to the device with device: - device.startPipeline(pipeline) - def timeDeltaToMilliS(delta) -> float: - return delta.total_seconds()*1000 + started = device.startIMUFirmwareUpdate() + if not started: + print("Couldn't start IMU firmware update") + exit(1) - fwUpdatePending = True - while fwUpdatePending: + while True: fwUpdatePending, percentage = device.getIMUFirmwareUpdateStatus() print(f"IMU FW update status: {percentage:.1f}%") - time.sleep(1) - - # Output queue for imu bulk packets - imuQueue = device.getOutputQueue(name="imu", maxSize=50, blocking=False) - baseTs = None - while True: - imuData = imuQueue.get() # blocking call, will wait until a new data has arrived - - imuPackets = imuData.packets - for imuPacket in imuPackets: - acceleroValues = imuPacket.acceleroMeter - gyroValues = imuPacket.gyroscope - - acceleroTs = acceleroValues.getTimestampDevice() - gyroTs = gyroValues.getTimestampDevice() - if baseTs is None: - baseTs = acceleroTs if acceleroTs < gyroTs else gyroTs - acceleroTs = timeDeltaToMilliS(acceleroTs - baseTs) - gyroTs = timeDeltaToMilliS(gyroTs - baseTs) - - imuF = "{:.06f}" - tsF = "{:.03f}" - - print(f"Accelerometer timestamp: {tsF.format(acceleroTs)} ms") - print(f"Accelerometer [m/s^2]: x: {imuF.format(acceleroValues.x)} y: {imuF.format(acceleroValues.y)} z: {imuF.format(acceleroValues.z)}") - print(f"Gyroscope timestamp: {tsF.format(gyroTs)} ms") - print(f"Gyroscope [rad/s]: x: {imuF.format(gyroValues.x)} y: {imuF.format(gyroValues.y)} z: {imuF.format(gyroValues.z)} ") - - if cv2.waitKey(1) == ord('q'): + if fwUpdatePending == False and percentage == 100: + print("Firmware update successful!") break + if fwUpdatePending == False and percentage != 100: + print("Firmware update failed!") + break + time.sleep(1) diff --git a/examples/IMU/imu_version.py b/examples/IMU/imu_version.py index 146aac775..5533b6fe4 100755 --- a/examples/IMU/imu_version.py +++ b/examples/IMU/imu_version.py @@ -4,6 +4,6 @@ device = dai.Device() -imuVersion = device.getConnectedIMUVersion() +imuVersion = device.getConnectedIMU() imuFirmwareVersion = device.getIMUFirmwareVersion() print(f"IMU version: {imuVersion}, firmware version: {imuFirmwareVersion}") diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index f8b1cbd70..a73648c99 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -595,8 +595,10 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def("getConnectedCameras", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedCameras(); }, DOC(dai, DeviceBase, getConnectedCameras)) .def("getConnectedCameraFeatures", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedCameraFeatures(); }, DOC(dai, DeviceBase, getConnectedCameraFeatures)) .def("getCameraSensorNames", [](DeviceBase& d) { py::gil_scoped_release release; return d.getCameraSensorNames(); }, DOC(dai, DeviceBase, getCameraSensorNames)) - .def("getConnectedIMUVersion", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedIMUVersion(); }, DOC(dai, DeviceBase, getConnectedIMUVersion)) + .def("getConnectedIMU", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedIMU(); }, DOC(dai, DeviceBase, getConnectedIMU)) .def("getIMUFirmwareVersion", [](DeviceBase& d) { py::gil_scoped_release release; return d.getIMUFirmwareVersion(); }, DOC(dai, DeviceBase, getIMUFirmwareVersion)) + .def("getLatestAvailableIMUFirmwareVersion", [](DeviceBase& d) { py::gil_scoped_release release; return d.getLatestAvailableIMUFirmwareVersion(); }, DOC(dai, DeviceBase, getLatestAvailableIMUFirmwareVersion)) + .def("startIMUFirmwareUpdate", [](DeviceBase& d, bool forceUpdate) { py::gil_scoped_release release; return d.startIMUFirmwareUpdate(forceUpdate); }, py::arg("forceUpdate") = false, DOC(dai, DeviceBase, startIMUFirmwareUpdate)) .def("getIMUFirmwareUpdateStatus", [](DeviceBase& d) { py::gil_scoped_release release; return d.getIMUFirmwareUpdateStatus(); }, DOC(dai, DeviceBase, getIMUFirmwareUpdateStatus)) .def("getDdrMemoryUsage", [](DeviceBase& d) { py::gil_scoped_release release; return d.getDdrMemoryUsage(); }, DOC(dai, DeviceBase, getDdrMemoryUsage)) .def("getCmxMemoryUsage", [](DeviceBase& d) { py::gil_scoped_release release; return d.getCmxMemoryUsage(); }, DOC(dai, DeviceBase, getCmxMemoryUsage)) diff --git a/src/pipeline/datatype/IMUDataBindings.cpp b/src/pipeline/datatype/IMUDataBindings.cpp index 47b78c98c..4aedeafe6 100644 --- a/src/pipeline/datatype/IMUDataBindings.cpp +++ b/src/pipeline/datatype/IMUDataBindings.cpp @@ -16,7 +16,6 @@ void bind_imudata(pybind11::module& m, void* pCallstack){ using namespace dai; - py::enum_ imuVersion(m, "IMUVersion"); py::class_> imuReport(m, "IMUReport", DOC(dai, IMUReport)); py::enum_ imuReportAccuracy(imuReport, "Accuracy"); py::class_> imuReportAccelerometer(m, "IMUReportAccelerometer", DOC(dai, IMUReportAccelerometer)); @@ -41,12 +40,6 @@ void bind_imudata(pybind11::module& m, void* pCallstack){ /////////////////////////////////////////////////////////////////////// // Metadata / raw - imuVersion - .value("NONE", IMUVersion::NONE) - .value("BNO086", IMUVersion::BNO086) - .value("BMI270", IMUVersion::BMI270) - ; - imuReport .def(py::init<>()) .def_readwrite("sequence", &IMUReport::sequence) From f585476b3b2380022a2508c70ab11837290d80c7 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 7 Mar 2023 20:12:15 +0200 Subject: [PATCH 212/385] Update core --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 49aaf1d49..155580557 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 49aaf1d4932bd2091629131bc6bb4064e9b69c0e +Subproject commit 155580557e250365bf8eb294217bb2765cb1997e From f60624235a4dea520e127f69967c36ad1c42ed02 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 7 Mar 2023 23:25:09 +0100 Subject: [PATCH 213/385] This may or may not fix the RTD docs building --- .readthedocs.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 6de4e5931..14ba0d775 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -25,8 +25,7 @@ sphinx: # configuration: mkdocs.yml # Optionally build your docs in additional formats such as PDF -formats: - - pdf +formats: [] # Optionally set the version of Python and requirements required to build your docs python: From 1def4c4410b628b14f8691ce6eea9292d5d6db1c Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 8 Mar 2023 12:43:48 +0100 Subject: [PATCH 214/385] Updated yolo docs --- docs/source/samples/SpatialDetection/spatial_tiny_yolo.rst | 2 +- docs/source/samples/Yolo/tiny_yolo.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/samples/SpatialDetection/spatial_tiny_yolo.rst b/docs/source/samples/SpatialDetection/spatial_tiny_yolo.rst index 76b552a34..e0643ccbe 100644 --- a/docs/source/samples/SpatialDetection/spatial_tiny_yolo.rst +++ b/docs/source/samples/SpatialDetection/spatial_tiny_yolo.rst @@ -1,7 +1,7 @@ RGB & TinyYolo with spatial data ================================ -This example shows how to run TinyYoloV3 and v4 on the RGB input frame, and how to display both the RGB +This example shows how to run Yolo on the RGB input frame, and how to display both the RGB preview, detections, depth map and spatial information (X,Y,Z). It's similar to example :ref:`RGB & MobilenetSSD with spatial data` except it is running TinyYolo network. X,Y,Z coordinates are relative to the center of depth map. diff --git a/docs/source/samples/Yolo/tiny_yolo.rst b/docs/source/samples/Yolo/tiny_yolo.rst index bac9e595a..dd1f23ff2 100644 --- a/docs/source/samples/Yolo/tiny_yolo.rst +++ b/docs/source/samples/Yolo/tiny_yolo.rst @@ -1,7 +1,7 @@ RGB & Tiny YOLO =============== -This example shows how to run Tiny YOLOv4 or YOLOv3 on the RGB input frame, and how to display both the RGB +This example shows how to run YOLO on the RGB input frame, and how to display both the RGB preview and the metadata results from the YOLO model on the preview. Decoding is done on the `RVC `__ instead on the host computer. From 38a10e7dea0bb61c3ff357d5d1647f8d54a2bbbc Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 8 Mar 2023 16:14:16 +0200 Subject: [PATCH 215/385] Update API; examples --- depthai-core | 2 +- examples/IMU/imu_firmware_update.py | 16 ++++++++-------- examples/IMU/imu_rotation_vector.py | 6 +++--- examples/IMU/imu_version.py | 4 ++-- src/DeviceBindings.cpp | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/depthai-core b/depthai-core index 155580557..53dfa7eb7 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 155580557e250365bf8eb294217bb2765cb1997e +Subproject commit 53dfa7eb71f0c07d0bc2c1c6617b194705eefe03 diff --git a/examples/IMU/imu_firmware_update.py b/examples/IMU/imu_firmware_update.py index ac0ef8ed3..e45da1fd6 100755 --- a/examples/IMU/imu_firmware_update.py +++ b/examples/IMU/imu_firmware_update.py @@ -9,8 +9,8 @@ imuVersion = device.getConnectedIMU() imuFirmwareVersion = device.getIMUFirmwareVersion() -latestImuFirmwareVersion = device.getLatestAvailableIMUFirmwareVersion() -print(f"IMU version: {imuVersion}, firmware version: {imuFirmwareVersion}, latest available firmware version: {latestImuFirmwareVersion}") +embeddedIMUFirmwareVersion = device.getEmbeddedIMUFirmwareVersion() +print(f"IMU version: {imuVersion}, firmware version: {imuFirmwareVersion}, embedded firmware version: {embeddedIMUFirmwareVersion}") print("Warning! Flashing IMU firmware can potentially soft brick your device and should be done with caution.") print("Do not unplug your device while the IMU firmware is flashing.") @@ -28,12 +28,12 @@ exit(1) while True: - fwUpdatePending, percentage = device.getIMUFirmwareUpdateStatus() + fwUpdateFinished, percentage = device.getIMUFirmwareUpdateStatus() print(f"IMU FW update status: {percentage:.1f}%") - if fwUpdatePending == False and percentage == 100: - print("Firmware update successful!") - break - if fwUpdatePending == False and percentage != 100: - print("Firmware update failed!") + if fwUpdateFinished: + if percentage == 100: + print("Firmware update successful!") + else: + print("Firmware update failed!") break time.sleep(1) diff --git a/examples/IMU/imu_rotation_vector.py b/examples/IMU/imu_rotation_vector.py index c88b17b4c..0eba964b4 100755 --- a/examples/IMU/imu_rotation_vector.py +++ b/examples/IMU/imu_rotation_vector.py @@ -7,11 +7,11 @@ device = dai.Device() -imuVersion = device.getConnectedIMUVersion() +imuType = device.getConnectedIMUVersion() imuFirmwareVersion = device.getIMUFirmwareVersion() -print(f"IMU version: {imuVersion}, firmware version: {imuFirmwareVersion}") +print(f"IMU version: {imuType}, firmware version: {imuFirmwareVersion}") -if imuVersion != dai.IMUVersion.BNO086: +if imuType != "BNO086": print("Rotation vector output is supported only by BNO086!") exit(1) diff --git a/examples/IMU/imu_version.py b/examples/IMU/imu_version.py index 5533b6fe4..a98e921bf 100755 --- a/examples/IMU/imu_version.py +++ b/examples/IMU/imu_version.py @@ -4,6 +4,6 @@ device = dai.Device() -imuVersion = device.getConnectedIMU() +imuType = device.getConnectedIMU() imuFirmwareVersion = device.getIMUFirmwareVersion() -print(f"IMU version: {imuVersion}, firmware version: {imuFirmwareVersion}") +print(f"IMU type: {imuType}, firmware version: {imuFirmwareVersion}") diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index a73648c99..1acfc5459 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -597,7 +597,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def("getCameraSensorNames", [](DeviceBase& d) { py::gil_scoped_release release; return d.getCameraSensorNames(); }, DOC(dai, DeviceBase, getCameraSensorNames)) .def("getConnectedIMU", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedIMU(); }, DOC(dai, DeviceBase, getConnectedIMU)) .def("getIMUFirmwareVersion", [](DeviceBase& d) { py::gil_scoped_release release; return d.getIMUFirmwareVersion(); }, DOC(dai, DeviceBase, getIMUFirmwareVersion)) - .def("getLatestAvailableIMUFirmwareVersion", [](DeviceBase& d) { py::gil_scoped_release release; return d.getLatestAvailableIMUFirmwareVersion(); }, DOC(dai, DeviceBase, getLatestAvailableIMUFirmwareVersion)) + .def("getEmbeddedIMUFirmwareVersion", [](DeviceBase& d) { py::gil_scoped_release release; return d.getEmbeddedIMUFirmwareVersion(); }, DOC(dai, DeviceBase, getEmbeddedIMUFirmwareVersion)) .def("startIMUFirmwareUpdate", [](DeviceBase& d, bool forceUpdate) { py::gil_scoped_release release; return d.startIMUFirmwareUpdate(forceUpdate); }, py::arg("forceUpdate") = false, DOC(dai, DeviceBase, startIMUFirmwareUpdate)) .def("getIMUFirmwareUpdateStatus", [](DeviceBase& d) { py::gil_scoped_release release; return d.getIMUFirmwareUpdateStatus(); }, DOC(dai, DeviceBase, getIMUFirmwareUpdateStatus)) .def("getDdrMemoryUsage", [](DeviceBase& d) { py::gil_scoped_release release; return d.getDdrMemoryUsage(); }, DOC(dai, DeviceBase, getDdrMemoryUsage)) From bb5644f25f3ef8959d4d7ea9c56d0b9bd220e5a0 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 8 Mar 2023 16:23:26 +0200 Subject: [PATCH 216/385] Update FW with deprecation warning for enableFirmwareUpdate --- depthai-core | 2 +- examples/IMU/imu_rotation_vector.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depthai-core b/depthai-core index 53dfa7eb7..12b198a67 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 53dfa7eb71f0c07d0bc2c1c6617b194705eefe03 +Subproject commit 12b198a67f9c7a56747666679f00763c35cf64e3 diff --git a/examples/IMU/imu_rotation_vector.py b/examples/IMU/imu_rotation_vector.py index 0eba964b4..90e6f56e6 100755 --- a/examples/IMU/imu_rotation_vector.py +++ b/examples/IMU/imu_rotation_vector.py @@ -7,7 +7,7 @@ device = dai.Device() -imuType = device.getConnectedIMUVersion() +imuType = device.getConnectedIMU() imuFirmwareVersion = device.getIMUFirmwareVersion() print(f"IMU version: {imuType}, firmware version: {imuFirmwareVersion}") From 2f91547fd959ac5962f64d1f3528aef7a292c5c7 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 8 Mar 2023 18:14:45 +0200 Subject: [PATCH 217/385] Change imu version to imu type --- depthai-core | 2 +- examples/IMU/imu_firmware_update.py | 2 +- examples/IMU/imu_rotation_vector.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/depthai-core b/depthai-core index 12b198a67..f319dc8f2 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 12b198a67f9c7a56747666679f00763c35cf64e3 +Subproject commit f319dc8f235718a7cb452952ffdc285e2165dddb diff --git a/examples/IMU/imu_firmware_update.py b/examples/IMU/imu_firmware_update.py index e45da1fd6..0461a6123 100755 --- a/examples/IMU/imu_firmware_update.py +++ b/examples/IMU/imu_firmware_update.py @@ -10,7 +10,7 @@ imuVersion = device.getConnectedIMU() imuFirmwareVersion = device.getIMUFirmwareVersion() embeddedIMUFirmwareVersion = device.getEmbeddedIMUFirmwareVersion() -print(f"IMU version: {imuVersion}, firmware version: {imuFirmwareVersion}, embedded firmware version: {embeddedIMUFirmwareVersion}") +print(f"IMU type: {imuVersion}, firmware version: {imuFirmwareVersion}, embedded firmware version: {embeddedIMUFirmwareVersion}") print("Warning! Flashing IMU firmware can potentially soft brick your device and should be done with caution.") print("Do not unplug your device while the IMU firmware is flashing.") diff --git a/examples/IMU/imu_rotation_vector.py b/examples/IMU/imu_rotation_vector.py index 90e6f56e6..444079248 100755 --- a/examples/IMU/imu_rotation_vector.py +++ b/examples/IMU/imu_rotation_vector.py @@ -9,7 +9,7 @@ imuType = device.getConnectedIMU() imuFirmwareVersion = device.getIMUFirmwareVersion() -print(f"IMU version: {imuType}, firmware version: {imuFirmwareVersion}") +print(f"IMU type: {imuType}, firmware version: {imuFirmwareVersion}") if imuType != "BNO086": print("Rotation vector output is supported only by BNO086!") From a557259f49b2b231010d83c72e3bb49dc86cefb3 Mon Sep 17 00:00:00 2001 From: zrezke Date: Wed, 8 Mar 2023 14:17:28 +0100 Subject: [PATCH 218/385] cam test gui --- utilities/cam_test.py | 172 ++++++++++++++++--------- utilities/cam_test_gui.py | 255 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 370 insertions(+), 57 deletions(-) create mode 100644 utilities/cam_test_gui.py diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 44b4e6294..ca48429f5 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -37,22 +37,28 @@ import cv2 import argparse -import depthai as dai import collections import time from itertools import cycle from pathlib import Path +import sys +import cam_test_gui + def socket_type_pair(arg): socket, type = arg.split(',') - if not (socket in ['rgb', 'left', 'right', 'camd']): raise ValueError("") - if not (type in ['m', 'mono', 'c', 'color']): raise ValueError("") + if not (socket in ['rgb', 'left', 'right', 'camd']): + raise ValueError("") + if not (type in ['m', 'mono', 'c', 'color']): + raise ValueError("") is_color = True if type in ['c', 'color'] else False return [socket, is_color] + parser = argparse.ArgumentParser() parser.add_argument('-cams', '--cameras', type=socket_type_pair, nargs='+', - default=[['rgb', True], ['left', False], ['right', False], ['camd', True]], + default=[['rgb', True], ['left', False], + ['right', False], ['camd', True]], help="Which camera sockets to enable, and type: c[olor] / m[ono]. " "E.g: -cams rgb,m right,c . Default: rgb,c left,m right,m camd,c") parser.add_argument('-mres', '--mono-resolution', type=int, default=800, choices={480, 400, 720, 800}, @@ -71,8 +77,25 @@ def socket_type_pair(arg): help="Make OpenCV windows resizable. Note: may introduce some artifacts") parser.add_argument('-tun', '--camera-tuning', type=Path, help="Path to custom camera tuning database") +parser.add_argument('-d', '--device', default="", type=str, + help="Optional MX ID of the device to connect to.") + +parser.add_argument('-ctimeout', '--connection-timeout', default=30000, + help="Connection timeout in ms. Default: %(default)s (sets DEPTHAI_CONNECTION_TIMEOUT environment variable)") + +parser.add_argument('-btimeout', '--boot-timeout', default=30000, + help="Boot timeout in ms. Default: %(default)s (sets DEPTHAI_BOOT_TIMEOUT environment variable)") + args = parser.parse_args() +# Set timeouts before importing depthai +os.environ["DEPTHAI_CONNECTION_TIMEOUT"] = str(args.connection_timeout) +os.environ["DEPTHAI_BOOT_TIMEOUT"] = str(args.boot_timeout) +import depthai as dai + +if len(sys.argv) == 1: + cam_test_gui.main() + cam_list = [] cam_type_color = {} print("Enabled cameras:") @@ -85,24 +108,24 @@ def socket_type_pair(arg): print("DepthAI path:", dai.__file__) cam_socket_opts = { - 'rgb' : dai.CameraBoardSocket.RGB, # Or CAM_A - 'left' : dai.CameraBoardSocket.LEFT, # Or CAM_B - 'right': dai.CameraBoardSocket.RIGHT, # Or CAM_C - 'camd' : dai.CameraBoardSocket.CAM_D, + 'rgb': dai.CameraBoardSocket.RGB, # Or CAM_A + 'left': dai.CameraBoardSocket.LEFT, # Or CAM_B + 'right': dai.CameraBoardSocket.RIGHT, # Or CAM_C + 'camd': dai.CameraBoardSocket.CAM_D, } cam_socket_to_name = { - 'RGB' : 'rgb', - 'LEFT' : 'left', + 'RGB': 'rgb', + 'LEFT': 'left', 'RIGHT': 'right', 'CAM_D': 'camd', } rotate = { - 'rgb' : args.rotate in ['all', 'rgb'], - 'left' : args.rotate in ['all', 'mono'], + 'rgb': args.rotate in ['all', 'rgb'], + 'left': args.rotate in ['all', 'mono'], 'right': args.rotate in ['all', 'mono'], - 'camd' : args.rotate in ['all', 'rgb'], + 'camd': args.rotate in ['all', 'rgb'], } mono_res_opts = { @@ -134,9 +157,11 @@ def __init__(self, window_size=30): self.fps = 0 def update(self, timestamp=None): - if timestamp == None: timestamp = time.monotonic() + if timestamp == None: + timestamp = time.monotonic() count = len(self.dq) - if count > 0: self.fps = count / (timestamp - self.dq[0]) + if count > 0: + self.fps = count / (timestamp - self.dq[0]) self.dq.append(timestamp) def get(self): @@ -145,7 +170,7 @@ def get(self): # Start defining a pipeline pipeline = dai.Pipeline() # Uncomment to get better throughput -#pipeline.setXLinkChunkSize(0) +# pipeline.setXLinkChunkSize(0) control = pipeline.createXLinkIn() control.setStreamName('control') @@ -159,7 +184,7 @@ def get(self): cam[c] = pipeline.createColorCamera() cam[c].setResolution(color_res_opts[args.color_resolution]) cam[c].setIspScale(1, args.isp_downscale) - #cam[c].initialControl.setManualFocus(85) # TODO + # cam[c].initialControl.setManualFocus(85) # TODO cam[c].isp.link(xout[c].input) else: cam[c] = pipeline.createMonoCamera() @@ -167,13 +192,13 @@ def get(self): cam[c].out.link(xout[c].input) cam[c].setBoardSocket(cam_socket_opts[c]) # Num frames to capture on trigger, with first to be discarded (due to degraded quality) - #cam[c].initialControl.setExternalTrigger(2, 1) - #cam[c].initialControl.setStrobeExternal(48, 1) - #cam[c].initialControl.setFrameSyncMode(dai.CameraControl.FrameSyncMode.INPUT) + # cam[c].initialControl.setExternalTrigger(2, 1) + # cam[c].initialControl.setStrobeExternal(48, 1) + # cam[c].initialControl.setFrameSyncMode(dai.CameraControl.FrameSyncMode.INPUT) - #cam[c].initialControl.setManualExposure(15000, 400) # exposure [us], iso + # cam[c].initialControl.setManualExposure(15000, 400) # exposure [us], iso # When set, takes effect after the first 2 frames - #cam[c].initialControl.setManualWhiteBalance(4000) # light temperature in K, 1000..12000 + # cam[c].initialControl.setManualWhiteBalance(4000) # light temperature in K, 1000..12000 control.out.link(cam[c].inputControl) if rotate[c]: cam[c].setImageOrientation(dai.CameraImageOrientation.ROTATE_180_DEG) @@ -183,13 +208,19 @@ def get(self): if args.camera_tuning: pipeline.setCameraTuningBlobPath(str(args.camera_tuning)) + # Pipeline is defined, now we can connect to the device -with dai.Device(pipeline) as device: - #print('Connected cameras:', [c.name for c in device.getConnectedCameras()]) +device = dai.Device.getDeviceByMxId(args.device) +dai_device_args = [pipeline] +if device[0]: + dai_device_args.append(device[1]) +with dai.Device(*dai_device_args) as device: + # print('Connected cameras:', [c.name for c in device.getConnectedCameras()]) print('Connected cameras:') cam_name = {} for p in device.getConnectedCameraFeatures(): - print(f' -socket {p.socket.name:6}: {p.sensorName:6} {p.width:4} x {p.height:4} focus:', end='') + print( + f' -socket {p.socket.name:6}: {p.sensorName:6} {p.width:4} x {p.height:4} focus:', end='') print('auto ' if p.hasAutofocus else 'fixed', '- ', end='') print(*[type.name for type in p.supportedTypes]) cam_name[cam_socket_to_name[p.socket.name]] = p.sensorName @@ -237,9 +268,12 @@ def get(self): dotIntensity = 0 floodIntensity = 0 - awb_mode = cycle([item for name, item in vars(dai.CameraControl.AutoWhiteBalanceMode).items() if name.isupper()]) - anti_banding_mode = cycle([item for name, item in vars(dai.CameraControl.AntiBandingMode).items() if name.isupper()]) - effect_mode = cycle([item for name, item in vars(dai.CameraControl.EffectMode).items() if name.isupper()]) + awb_mode = cycle([item for name, item in vars( + dai.CameraControl.AutoWhiteBalanceMode).items() if name.isupper()]) + anti_banding_mode = cycle([item for name, item in vars( + dai.CameraControl.AntiBandingMode).items() if name.isupper()]) + effect_mode = cycle([item for name, item in vars( + dai.CameraControl.EffectMode).items() if name.isupper()]) ae_comp = 0 ae_lock = False @@ -252,7 +286,8 @@ def get(self): chroma_denoise = 0 control = 'none' - print("Cam:", *[' ' + c.ljust(8) for c in cam_list], "[host | capture timestamp]") + print("Cam:", *[' ' + c.ljust(8) + for c in cam_list], "[host | capture timestamp]") capture_list = [] while True: @@ -265,21 +300,25 @@ def get(self): if c in capture_list: width, height = pkt.getWidth(), pkt.getHeight() capture_file_name = ('capture_' + c + '_' + cam_name[c] - + '_' + str(width) + 'x' + str(height) - + '_exp_' + str(int(pkt.getExposureTime().total_seconds()*1e6)) - + '_iso_' + str(pkt.getSensitivity()) - + '_lens_' + str(pkt.getLensPosition()) - + '_' + capture_time - + '_' + str(pkt.getSequenceNum()) - + ".png" - ) + + '_' + str(width) + 'x' + str(height) + + '_exp_' + + str(int( + pkt.getExposureTime().total_seconds()*1e6)) + + '_iso_' + str(pkt.getSensitivity()) + + '_lens_' + + str(pkt.getLensPosition()) + + '_' + capture_time + + '_' + str(pkt.getSequenceNum()) + + ".png" + ) print("\nSaving:", capture_file_name) cv2.imwrite(capture_file_name, frame) capture_list.remove(c) cv2.imshow(c, frame) print("\rFPS:", - *["{:6.2f}|{:6.2f}".format(fps_host[c].get(), fps_capt[c].get()) for c in cam_list], + *["{:6.2f}|{:6.2f}".format(fps_host[c].get(), + fps_capt[c].get()) for c in cam_list], end='', flush=True) key = cv2.waitKey(1) @@ -297,7 +336,8 @@ def get(self): elif key == ord('f'): print("Autofocus enable, continuous") ctrl = dai.CameraControl() - ctrl.setAutoFocusMode(dai.CameraControl.AutoFocusMode.CONTINUOUS_VIDEO) + ctrl.setAutoFocusMode( + dai.CameraControl.AutoFocusMode.CONTINUOUS_VIDEO) controlQueue.send(ctrl) elif key == ord('e'): print("Autoexposure enable") @@ -305,18 +345,24 @@ def get(self): ctrl.setAutoExposureEnable() controlQueue.send(ctrl) elif key in [ord(','), ord('.')]: - if key == ord(','): lensPos -= LENS_STEP - if key == ord('.'): lensPos += LENS_STEP + if key == ord(','): + lensPos -= LENS_STEP + if key == ord('.'): + lensPos += LENS_STEP lensPos = clamp(lensPos, lensMin, lensMax) print("Setting manual focus, lens position: ", lensPos) ctrl = dai.CameraControl() ctrl.setManualFocus(lensPos) controlQueue.send(ctrl) elif key in [ord('i'), ord('o'), ord('k'), ord('l')]: - if key == ord('i'): expTime -= EXP_STEP - if key == ord('o'): expTime += EXP_STEP - if key == ord('k'): sensIso -= ISO_STEP - if key == ord('l'): sensIso += ISO_STEP + if key == ord('i'): + expTime -= EXP_STEP + if key == ord('o'): + expTime += EXP_STEP + if key == ord('k'): + sensIso -= ISO_STEP + if key == ord('l'): + sensIso += ISO_STEP expTime = clamp(expTime, expMin, expMax) sensIso = clamp(sensIso, sensMin, sensMax) print("Setting manual exposure, time: ", expTime, "iso: ", sensIso) @@ -356,21 +402,33 @@ def get(self): floodIntensity = 0 device.setIrFloodLightBrightness(floodIntensity) elif key >= 0 and chr(key) in '34567890[]': - if key == ord('3'): control = 'awb_mode' - elif key == ord('4'): control = 'ae_comp' - elif key == ord('5'): control = 'anti_banding_mode' - elif key == ord('6'): control = 'effect_mode' - elif key == ord('7'): control = 'brightness' - elif key == ord('8'): control = 'contrast' - elif key == ord('9'): control = 'saturation' - elif key == ord('0'): control = 'sharpness' - elif key == ord('['): control = 'luma_denoise' - elif key == ord(']'): control = 'chroma_denoise' + if key == ord('3'): + control = 'awb_mode' + elif key == ord('4'): + control = 'ae_comp' + elif key == ord('5'): + control = 'anti_banding_mode' + elif key == ord('6'): + control = 'effect_mode' + elif key == ord('7'): + control = 'brightness' + elif key == ord('8'): + control = 'contrast' + elif key == ord('9'): + control = 'saturation' + elif key == ord('0'): + control = 'sharpness' + elif key == ord('['): + control = 'luma_denoise' + elif key == ord(']'): + control = 'chroma_denoise' print("Selected control:", control) elif key in [ord('-'), ord('_'), ord('+'), ord('=')]: change = 0 - if key in [ord('-'), ord('_')]: change = -1 - if key in [ord('+'), ord('=')]: change = 1 + if key in [ord('-'), ord('_')]: + change = -1 + if key in [ord('+'), ord('=')]: + change = 1 ctrl = dai.CameraControl() if control == 'none': print("Please select a control first using keys 3..9 0 [ ]") diff --git a/utilities/cam_test_gui.py b/utilities/cam_test_gui.py new file mode 100644 index 000000000..8a968966d --- /dev/null +++ b/utilities/cam_test_gui.py @@ -0,0 +1,255 @@ +from typing import List +import PyQt5.sip +from PyQt5 import QtCore, QtGui, QtWidgets +import subprocess +import depthai as dai +import sys +import os +import signal + + +class CamTestGui: + + CAM_SOCKET_OPTIONS = ["rgb", "left", "right", "camd"] + CAM_TYPE_OPTIONS = ["color", "mono"] + MONO_RESOLUTION_OPTIONS = ["400", "480", "720", "800"] + COLOR_RESOLUTION_OPTIONS = ["720", "800", + "1080", "1200", "4k", "5mp", "12mp", "48mp"] + ROTATE_OPTIONS = ["disabled", "all", "rgb", "mono"] + DEPTHAI_CONNECT_TIMEOUT_DEFAULT = 30000 + DEPTHAI_BOOTUP_TIMEOUT_DEFAULT = 30000 + + def remove_camera(self, layout: QtWidgets.QHBoxLayout): + for i in reversed(range(layout.count())): + layout.itemAt(i).widget().setParent(None) + self.cameras_list.removeItem(layout) + + def add_camera(self, camera: str = None, camera_type: str = None): + layout = QtWidgets.QHBoxLayout() + cam_combo = QtWidgets.QComboBox() + cam_combo.addItems(self.CAM_SOCKET_OPTIONS) + cam_combo.setCurrentIndex( + self.CAM_SOCKET_OPTIONS.index(camera) if camera else 0) + layout.addWidget(cam_combo) + cam_type_combo = QtWidgets.QComboBox() + cam_type_combo.addItems(self.CAM_TYPE_OPTIONS) + cam_type_combo.setCurrentIndex( + self.CAM_TYPE_OPTIONS.index(camera_type) if camera_type else 0) + layout.addWidget(cam_type_combo) + self.cameras_list.addLayout(layout) + remove_button = QtWidgets.QPushButton("Remove") + remove_button.clicked.connect(lambda: self.remove_camera(layout)) + layout.addWidget(remove_button) + + def set_default_cameras(self): + self.add_camera("rgb", "color") + self.add_camera("left", "mono") + self.add_camera("right", "mono") + self.add_camera("camd", "color") + + def __init__(self, app: "Application"): + self.app = app + self.app.setWindowTitle("Camera Test") + self.main_widget = QtWidgets.QWidget() + self.main_layout = QtWidgets.QVBoxLayout() + self.main_widget.setLayout(self.main_layout) + self.app.setCentralWidget(self.main_widget) + self.label_cameras = QtWidgets.QLabel("Cameras") + self.main_layout.addWidget(self.label_cameras) + + self.cameras_list = QtWidgets.QVBoxLayout() + self.main_layout.addLayout(self.cameras_list) + self.set_default_cameras() + + self.add_cam_button = QtWidgets.QPushButton("Add Camera") + self.add_cam_button.clicked.connect(self.add_camera) + self.main_layout.addWidget(self.add_cam_button) + + self.mono_resolution_label = QtWidgets.QLabel("Mono Resolution") + self.main_layout.addWidget(self.mono_resolution_label) + self.mono_resolution_combo = QtWidgets.QComboBox() + self.mono_resolution_combo.addItems(self.MONO_RESOLUTION_OPTIONS) + self.main_layout.addWidget(self.mono_resolution_combo) + self.mono_resolution_combo.setCurrentIndex(3) + + self.label_color_resolution = QtWidgets.QLabel("Color Resolution") + self.main_layout.addWidget(self.label_color_resolution) + self.combo_color_resolution = QtWidgets.QComboBox() + self.combo_color_resolution.addItems(self.COLOR_RESOLUTION_OPTIONS) + self.main_layout.addWidget(self.combo_color_resolution) + self.combo_color_resolution.setCurrentIndex(2) + + self.label_rotate = QtWidgets.QLabel("Rotate") + self.main_layout.addWidget(self.label_rotate) + self.combo_rotate = QtWidgets.QComboBox() + self.combo_rotate.addItems(self.ROTATE_OPTIONS) + self.main_layout.addWidget(self.combo_rotate) + + self.label_fps = QtWidgets.QLabel("FPS") + self.main_layout.addWidget(self.label_fps) + self.spin_fps = QtWidgets.QSpinBox() + self.spin_fps.setMinimum(1) + self.spin_fps.setMaximum(120) + self.spin_fps.setValue(30) + self.main_layout.addWidget(self.spin_fps) + + self.label_isp_downscale = QtWidgets.QLabel("ISP Downscale") + self.main_layout.addWidget(self.label_isp_downscale) + self.spin_isp_downscale = QtWidgets.QSpinBox() + self.spin_isp_downscale.setMinimum(1) + self.spin_isp_downscale.setMaximum(4) + self.spin_isp_downscale.setValue(1) + self.main_layout.addWidget(self.spin_isp_downscale) + + self.label_resizable_windows = QtWidgets.QLabel("Resizable Windows") + self.main_layout.addWidget(self.label_resizable_windows) + self.check_resizable_windows = QtWidgets.QCheckBox() + self.main_layout.addWidget(self.check_resizable_windows) + + self.label_camera_tuning = QtWidgets.QLabel("Camera Tuning") + self.main_layout.addWidget(self.label_camera_tuning) + self.camera_tuning_path = QtWidgets.QLineEdit() + self.main_layout.addWidget(self.camera_tuning_path) + + self.label_connect_timeout = QtWidgets.QLabel("Connect Timeout (ms)") + self.main_layout.addWidget(self.label_connect_timeout) + self.spin_connect_timeout = QtWidgets.QSpinBox() + self.spin_connect_timeout.setMinimum(1) + self.spin_connect_timeout.setMaximum(60000) + self.spin_connect_timeout.setValue(self.DEPTHAI_CONNECT_TIMEOUT_DEFAULT) + self.main_layout.addWidget(self.spin_connect_timeout) + + self.label_boot_timeout = QtWidgets.QLabel("Bootup Timeout (ms)") + self.main_layout.addWidget(self.label_boot_timeout) + self.spin_boot_timeout = QtWidgets.QSpinBox() + self.spin_boot_timeout.setMinimum(1) + self.spin_boot_timeout.setMaximum(60000) + self.spin_boot_timeout.setValue(self.DEPTHAI_BOOTUP_TIMEOUT_DEFAULT) + self.main_layout.addWidget(self.spin_boot_timeout) + + self.label_available_devices = QtWidgets.QLabel("Available Devices") + self.main_layout.addWidget(self.label_available_devices) + self.available_devices_combo = QtWidgets.QComboBox() + self.main_layout.addWidget(self.available_devices_combo) + + self.connect_button = QtWidgets.QPushButton("Connect") + self.connect_button.clicked.connect(self.app.connect) + self.main_layout.addWidget(self.connect_button) + + self.disconnect_button = QtWidgets.QPushButton("Disconnect") + self.disconnect_button.clicked.connect(self.app.disconnect) + self.main_layout.addWidget(self.disconnect_button) + self.disconnect_button.setHidden(True) + + + + def handle_disconnect(self): + self.connect_button.setDisabled(False) + self.disconnect_button.setDisabled(True) + self.disconnect_button.setHidden(True) + self.connect_button.setHidden(False) + self.add_cam_button.setDisabled(False) + self.mono_resolution_combo.setDisabled(False) + self.combo_color_resolution.setDisabled(False) + self.combo_rotate.setDisabled(False) + self.spin_fps.setDisabled(False) + self.spin_isp_downscale.setDisabled(False) + self.check_resizable_windows.setDisabled(False) + self.camera_tuning_path.setDisabled(False) + self.available_devices_combo.setDisabled(False) + for i in range(self.cameras_list.count()): + self.cameras_list.itemAt(i).itemAt(2).widget().setDisabled(False) + + def handle_connect(self): + self.connect_button.setDisabled(True) + self.disconnect_button.setDisabled(False) + self.disconnect_button.setHidden(False) + self.connect_button.setHidden(True) + self.add_cam_button.setDisabled(True) + self.mono_resolution_combo.setDisabled(True) + self.combo_color_resolution.setDisabled(True) + self.combo_rotate.setDisabled(True) + self.spin_fps.setDisabled(True) + self.spin_isp_downscale.setDisabled(True) + self.check_resizable_windows.setDisabled(True) + self.camera_tuning_path.setDisabled(True) + self.available_devices_combo.setDisabled(True) + for i in range(self.cameras_list.count()): + self.cameras_list.itemAt(i).itemAt(2).widget().setDisabled(True) + +class Application(QtWidgets.QMainWindow): + + def __init__(self): + super().__init__() + self.available_devices: List[dai.DeviceInfo] = [] + self.ui = CamTestGui(self) + self.query_devices() + self.query_devices_timer = QtCore.QTimer() + self.query_devices_timer.timeout.connect(self.query_devices) + self.query_devices_timer.start(1000) + self.test_process = None + + def construct_args_from_gui(self) -> List[str]: + if not self.available_devices: + return [] + self.device = self.available_devices[self.ui.available_devices_combo.currentIndex()].mxid + cmd = [f"{os.path.dirname(__file__)}/cam_test.py"] + cmd.append("--cameras") + for i in range(self.ui.cameras_list.count()): + hbox = self.ui.cameras_list.itemAt(i) + cam_combo = hbox.itemAt(0) + cam_type_combo = hbox.itemAt(1) + cam = cam_combo.widget().currentText() + cam_type = cam_type_combo.widget().currentText() + cmd.append(f"{cam},{cam_type[0]}") + cmd.append("-mres") + cmd.append(self.ui.mono_resolution_combo.currentText()) + cmd.append("-cres") + cmd.append(self.ui.combo_color_resolution.currentText()) + if self.ui.combo_rotate.currentText() != "disabled": + cmd.append("-rot") + cmd.append(self.ui.combo_rotate.currentText()) + cmd.append("-fps") + cmd.append(str(self.ui.spin_fps.value())) + cmd.append("-ds") + cmd.append(str(self.ui.spin_isp_downscale.value())) + if self.ui.check_resizable_windows.isChecked(): + cmd.append("-rs") + if self.ui.camera_tuning_path.text(): + cmd.append("-tun") + cmd.append(self.ui.camera_tuning_path.text()) + cmd.append("--device") + cmd.append(self.device) + cmd.append("--connection-timeout") + cmd.append(str(self.ui.spin_connect_timeout.value())) + cmd.append("--boot-timeout") + cmd.append(str(self.ui.spin_boot_timeout.value())) + return cmd + + def connect(self): + args = self.construct_args_from_gui() + if not args: + return + self.test_process = QtCore.QProcess() + self.test_process.finished.connect(self.disconnect) + self.test_process.start(sys.executable, args) + self.query_devices_timer.stop() + self.ui.handle_connect() + + def disconnect(self): + self.query_devices_timer.start() + self.ui.handle_disconnect() + if self.test_process.state() == QtCore.QProcess.Running: + self.test_process.terminate() + + def query_devices(self): + self.ui.available_devices_combo.clear() + self.available_devices = dai.Device.getAllAvailableDevices() + self.ui.available_devices_combo.addItems(list(map(lambda d: f"{d.name} ({d.getMxId()})", self.available_devices))) + +def main(): + signal.signal(signal.SIGINT, signal.SIG_DFL) + app = QtWidgets.QApplication(sys.argv) + application = Application() + application.show() + sys.exit(app.exec_()) From e646d7079dbc339bdf614d18d3189ead4170f020 Mon Sep 17 00:00:00 2001 From: zrezke Date: Wed, 8 Mar 2023 14:29:13 +0100 Subject: [PATCH 219/385] use sys.executable properly, remove unused imports --- utilities/cam_test_gui.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/utilities/cam_test_gui.py b/utilities/cam_test_gui.py index 8a968966d..844645534 100644 --- a/utilities/cam_test_gui.py +++ b/utilities/cam_test_gui.py @@ -1,10 +1,7 @@ from typing import List -import PyQt5.sip from PyQt5 import QtCore, QtGui, QtWidgets -import subprocess import depthai as dai import sys -import os import signal @@ -193,7 +190,7 @@ def construct_args_from_gui(self) -> List[str]: if not self.available_devices: return [] self.device = self.available_devices[self.ui.available_devices_combo.currentIndex()].mxid - cmd = [f"{os.path.dirname(__file__)}/cam_test.py"] + cmd = [] cmd.append("--cameras") for i in range(self.ui.cameras_list.count()): hbox = self.ui.cameras_list.itemAt(i) From 5b36ec170aaec70a834b171898bf0e8cc0aea938 Mon Sep 17 00:00:00 2001 From: zrezke Date: Wed, 8 Mar 2023 14:54:23 +0100 Subject: [PATCH 220/385] add isp3afps support --- utilities/cam_test_gui.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/utilities/cam_test_gui.py b/utilities/cam_test_gui.py index 844645534..4e310914c 100644 --- a/utilities/cam_test_gui.py +++ b/utilities/cam_test_gui.py @@ -90,6 +90,14 @@ def __init__(self, app: "Application"): self.spin_fps.setValue(30) self.main_layout.addWidget(self.spin_fps) + self.label_isp3afps = QtWidgets.QLabel("ISP3 AFPS") + self.main_layout.addWidget(self.label_isp3afps) + self.spin_isp3afps = QtWidgets.QSpinBox() + self.spin_isp3afps.setMinimum(1) + self.spin_isp3afps.setMaximum(120) + self.spin_isp3afps.setValue(0) + self.main_layout.addWidget(self.spin_isp3afps) + self.label_isp_downscale = QtWidgets.QLabel("ISP Downscale") self.main_layout.addWidget(self.label_isp_downscale) self.spin_isp_downscale = QtWidgets.QSpinBox() @@ -102,7 +110,7 @@ def __init__(self, app: "Application"): self.main_layout.addWidget(self.label_resizable_windows) self.check_resizable_windows = QtWidgets.QCheckBox() self.main_layout.addWidget(self.check_resizable_windows) - + self.label_camera_tuning = QtWidgets.QLabel("Camera Tuning") self.main_layout.addWidget(self.label_camera_tuning) self.camera_tuning_path = QtWidgets.QLineEdit() @@ -113,7 +121,8 @@ def __init__(self, app: "Application"): self.spin_connect_timeout = QtWidgets.QSpinBox() self.spin_connect_timeout.setMinimum(1) self.spin_connect_timeout.setMaximum(60000) - self.spin_connect_timeout.setValue(self.DEPTHAI_CONNECT_TIMEOUT_DEFAULT) + self.spin_connect_timeout.setValue( + self.DEPTHAI_CONNECT_TIMEOUT_DEFAULT) self.main_layout.addWidget(self.spin_connect_timeout) self.label_boot_timeout = QtWidgets.QLabel("Bootup Timeout (ms)") @@ -138,8 +147,6 @@ def __init__(self, app: "Application"): self.main_layout.addWidget(self.disconnect_button) self.disconnect_button.setHidden(True) - - def handle_disconnect(self): self.connect_button.setDisabled(False) self.disconnect_button.setDisabled(True) @@ -150,6 +157,7 @@ def handle_disconnect(self): self.combo_color_resolution.setDisabled(False) self.combo_rotate.setDisabled(False) self.spin_fps.setDisabled(False) + self.spin_isp3afps.setDisabled(False) self.spin_isp_downscale.setDisabled(False) self.check_resizable_windows.setDisabled(False) self.camera_tuning_path.setDisabled(False) @@ -167,6 +175,7 @@ def handle_connect(self): self.combo_color_resolution.setDisabled(True) self.combo_rotate.setDisabled(True) self.spin_fps.setDisabled(True) + self.spin_isp3afps.setDisabled(True) self.spin_isp_downscale.setDisabled(True) self.check_resizable_windows.setDisabled(True) self.camera_tuning_path.setDisabled(True) @@ -174,6 +183,7 @@ def handle_connect(self): for i in range(self.cameras_list.count()): self.cameras_list.itemAt(i).itemAt(2).widget().setDisabled(True) + class Application(QtWidgets.QMainWindow): def __init__(self): @@ -189,7 +199,8 @@ def __init__(self): def construct_args_from_gui(self) -> List[str]: if not self.available_devices: return [] - self.device = self.available_devices[self.ui.available_devices_combo.currentIndex()].mxid + self.device = self.available_devices[self.ui.available_devices_combo.currentIndex( + )].mxid cmd = [] cmd.append("--cameras") for i in range(self.ui.cameras_list.count()): @@ -208,6 +219,8 @@ def construct_args_from_gui(self) -> List[str]: cmd.append(self.ui.combo_rotate.currentText()) cmd.append("-fps") cmd.append(str(self.ui.spin_fps.value())) + cmd.append("-isp3afps") + cmd.append(str(self.ui.spin_isp3afps.value())) cmd.append("-ds") cmd.append(str(self.ui.spin_isp_downscale.value())) if self.ui.check_resizable_windows.isChecked(): @@ -242,7 +255,9 @@ def disconnect(self): def query_devices(self): self.ui.available_devices_combo.clear() self.available_devices = dai.Device.getAllAvailableDevices() - self.ui.available_devices_combo.addItems(list(map(lambda d: f"{d.name} ({d.getMxId()})", self.available_devices))) + self.ui.available_devices_combo.addItems( + list(map(lambda d: f"{d.name} ({d.getMxId()})", self.available_devices))) + def main(): signal.signal(signal.SIGINT, signal.SIG_DFL) From 898d04fa2427160e60dfde8aa72e41895c87490e Mon Sep 17 00:00:00 2001 From: zrezke Date: Wed, 8 Mar 2023 15:04:35 +0100 Subject: [PATCH 221/385] minor fixes --- utilities/cam_test_gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utilities/cam_test_gui.py b/utilities/cam_test_gui.py index 4e310914c..b8debc61d 100644 --- a/utilities/cam_test_gui.py +++ b/utilities/cam_test_gui.py @@ -93,7 +93,7 @@ def __init__(self, app: "Application"): self.label_isp3afps = QtWidgets.QLabel("ISP3 AFPS") self.main_layout.addWidget(self.label_isp3afps) self.spin_isp3afps = QtWidgets.QSpinBox() - self.spin_isp3afps.setMinimum(1) + self.spin_isp3afps.setMinimum(0) self.spin_isp3afps.setMaximum(120) self.spin_isp3afps.setValue(0) self.main_layout.addWidget(self.spin_isp3afps) @@ -242,7 +242,7 @@ def connect(self): return self.test_process = QtCore.QProcess() self.test_process.finished.connect(self.disconnect) - self.test_process.start(sys.executable, args) + self.test_process.start(sys.executable, sys.argv + args) self.query_devices_timer.stop() self.ui.handle_connect() From 63cd8b7b7ad3da90ede6905a4768c8a9b2e624a7 Mon Sep 17 00:00:00 2001 From: zrezke Date: Wed, 8 Mar 2023 15:19:55 +0100 Subject: [PATCH 222/385] forward channels and detect pyinstaller env to run cam_test.py correctly --- utilities/cam_test_gui.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/utilities/cam_test_gui.py b/utilities/cam_test_gui.py index b8debc61d..46eb7a008 100644 --- a/utilities/cam_test_gui.py +++ b/utilities/cam_test_gui.py @@ -241,8 +241,13 @@ def connect(self): if not args: return self.test_process = QtCore.QProcess() + # Forward stdout + self.test_process.setProcessChannelMode(QtCore.QProcess.ProcessChannelMode.ForwardedChannels) self.test_process.finished.connect(self.disconnect) - self.test_process.start(sys.executable, sys.argv + args) + if getattr(sys, 'frozen', False): + self.test_process.start(sys.executable, args) + else: + self.test_process.start(sys.executable, sys.argv + args) self.query_devices_timer.stop() self.ui.handle_connect() From 50fbc3a398288dbc532845d201c41ebacc9ac5fc Mon Sep 17 00:00:00 2001 From: zrezke Date: Wed, 8 Mar 2023 15:50:28 +0100 Subject: [PATCH 223/385] windows support --- utilities/README.md | 16 ++++++++++++++++ utilities/cam_test_gui.py | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/utilities/README.md b/utilities/README.md index 0f87c2e34..2c039f025 100644 --- a/utilities/README.md +++ b/utilities/README.md @@ -24,3 +24,19 @@ pyinstaller --onefile -w --icon=assets/icon.ico --add-data="assets/icon.ico;asse ``` Optionally, append `--runtime-tmpdir [path or .]` to modify where the temporary directory should be created when launched. + + +## Cam Test +### Standalone executable +Requirements: +``` +# Linux/macOS +python3 -m pip install pyinstaller +# Windows +python -m pip install pyinstaller +``` + +To build standalone executable issue the following command: +```sh +pyinstaller --onefile -w cam_test.py --hidden-import PyQt5.sip +``` diff --git a/utilities/cam_test_gui.py b/utilities/cam_test_gui.py index 46eb7a008..9e9f51960 100644 --- a/utilities/cam_test_gui.py +++ b/utilities/cam_test_gui.py @@ -184,16 +184,34 @@ def handle_connect(self): self.cameras_list.itemAt(i).itemAt(2).widget().setDisabled(True) +class WorkerSignals(QtCore.QObject): + finished = QtCore.pyqtSignal(list) + +class Worker(QtCore.QRunnable): + + def __init__(self, fn, *args, **kwargs): + super().__init__() + self.fn = fn + self.args = args + self.kwargs = kwargs + self.signals = WorkerSignals() + + @QtCore.pyqtSlot() + def run(self): + result = self.fn(*self.args, **self.kwargs) + self.signals.finished.emit(result) + + class Application(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.available_devices: List[dai.DeviceInfo] = [] self.ui = CamTestGui(self) - self.query_devices() self.query_devices_timer = QtCore.QTimer() self.query_devices_timer.timeout.connect(self.query_devices) - self.query_devices_timer.start(1000) + self.query_devices_timer.start(2000) + self.query_devices() self.test_process = None def construct_args_from_gui(self) -> List[str]: @@ -255,13 +273,21 @@ def disconnect(self): self.query_devices_timer.start() self.ui.handle_disconnect() if self.test_process.state() == QtCore.QProcess.Running: - self.test_process.terminate() + self.test_process.kill() def query_devices(self): + self.query_devices_timer.stop() + pool = QtCore.QThreadPool.globalInstance() + query_devices_worker = Worker(dai.Device.getAllAvailableDevices) + query_devices_worker.signals.finished.connect(self.on_finish_query_devices) + pool.start(query_devices_worker) + + def on_finish_query_devices(self, result): self.ui.available_devices_combo.clear() - self.available_devices = dai.Device.getAllAvailableDevices() + self.available_devices = result self.ui.available_devices_combo.addItems( list(map(lambda d: f"{d.name} ({d.getMxId()})", self.available_devices))) + self.query_devices_timer.start() def main(): From e02e0b947f92d97153677923a16c769acdee52de Mon Sep 17 00:00:00 2001 From: zrezke Date: Thu, 9 Mar 2023 13:20:16 +0100 Subject: [PATCH 224/385] tried to start detached, the problem is pyinstaller launches two instances of the program --- utilities/cam_test.py | 8 ++++++++ utilities/cam_test_gui.py | 31 +++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index ca48429f5..fb8bbf999 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -43,6 +43,7 @@ from pathlib import Path import sys import cam_test_gui +import signal def socket_type_pair(arg): @@ -208,6 +209,13 @@ def get(self): if args.camera_tuning: pipeline.setCameraTuningBlobPath(str(args.camera_tuning)) +def exit_cleanly(signum, frame): + print("Exiting cleanly") + cv2.destroyAllWindows() + sys.exit(0) + +signal.signal(signal.SIGINT, exit_cleanly) + # Pipeline is defined, now we can connect to the device device = dai.Device.getDeviceByMxId(args.device) diff --git a/utilities/cam_test_gui.py b/utilities/cam_test_gui.py index 9e9f51960..9a0fe89f4 100644 --- a/utilities/cam_test_gui.py +++ b/utilities/cam_test_gui.py @@ -3,6 +3,7 @@ import depthai as dai import sys import signal +import os class CamTestGui: @@ -147,6 +148,11 @@ def __init__(self, app: "Application"): self.main_layout.addWidget(self.disconnect_button) self.disconnect_button.setHidden(True) + self.process_label = QtWidgets.QLabel("Process") + self.pid_label = QtWidgets.QLabel("") + self.main_layout.addWidget(self.process_label) + self.main_layout.addWidget(self.pid_label) + def handle_disconnect(self): self.connect_button.setDisabled(False) self.disconnect_button.setDisabled(True) @@ -163,9 +169,16 @@ def handle_disconnect(self): self.camera_tuning_path.setDisabled(False) self.available_devices_combo.setDisabled(False) for i in range(self.cameras_list.count()): + self.cameras_list.itemAt(i).itemAt(0).widget().setDisabled(False) + self.cameras_list.itemAt(i).itemAt(1).widget().setDisabled(False) self.cameras_list.itemAt(i).itemAt(2).widget().setDisabled(False) + self.spin_connect_timeout.setDisabled(False) + self.spin_boot_timeout.setDisabled(False) + def handle_connect(self): + self.spin_boot_timeout.setDisabled(True) + self.spin_connect_timeout.setDisabled(True) self.connect_button.setDisabled(True) self.disconnect_button.setDisabled(False) self.disconnect_button.setHidden(False) @@ -181,6 +194,8 @@ def handle_connect(self): self.camera_tuning_path.setDisabled(True) self.available_devices_combo.setDisabled(True) for i in range(self.cameras_list.count()): + self.cameras_list.itemAt(i).itemAt(0).widget().setDisabled(True) + self.cameras_list.itemAt(i).itemAt(1).widget().setDisabled(True) self.cameras_list.itemAt(i).itemAt(2).widget().setDisabled(True) @@ -262,18 +277,26 @@ def connect(self): # Forward stdout self.test_process.setProcessChannelMode(QtCore.QProcess.ProcessChannelMode.ForwardedChannels) self.test_process.finished.connect(self.disconnect) + started_successfully = False + # Start detached process with the function that also returns the PID if getattr(sys, 'frozen', False): - self.test_process.start(sys.executable, args) + started_successfully, self.test_process_pid = self.test_process.startDetached(sys.executable, args, "") else: - self.test_process.start(sys.executable, sys.argv + args) + started_successfully, self.test_process_pid = self.test_process.startDetached(sys.executable, sys.argv + args, "") + if not started_successfully: + self.test_process_pid = None + self.disconnect() + return + self.ui.pid_label.setText(f"PID: {self.test_process_pid}") self.query_devices_timer.stop() self.ui.handle_connect() def disconnect(self): + if self.test_process_pid: + os.kill(self.test_process_pid, signal.SIGINT) + self.test_process_pid = None self.query_devices_timer.start() self.ui.handle_disconnect() - if self.test_process.state() == QtCore.QProcess.Running: - self.test_process.kill() def query_devices(self): self.query_devices_timer.stop() From d99a49e289ae0a167e608682d49b83d9d667cb7c Mon Sep 17 00:00:00 2001 From: zrezke Date: Thu, 9 Mar 2023 13:56:47 +0100 Subject: [PATCH 225/385] small ui fix and readme fix --- utilities/README.md | 8 +++++--- utilities/cam_test_gui.py | 13 ++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/utilities/README.md b/utilities/README.md index 2c039f025..2209b1d0f 100644 --- a/utilities/README.md +++ b/utilities/README.md @@ -27,7 +27,7 @@ Optionally, append `--runtime-tmpdir [path or .]` to modify where the temporary ## Cam Test -### Standalone executable +### Bundled executable Requirements: ``` # Linux/macOS @@ -36,7 +36,9 @@ python3 -m pip install pyinstaller python -m pip install pyinstaller ``` -To build standalone executable issue the following command: +To build a bundled executable issue the following command: ```sh -pyinstaller --onefile -w cam_test.py --hidden-import PyQt5.sip +pyinstaller -w cam_test.py --hidden-import PyQt5.sip ``` + +The executable will be located in `dist/cam_test` folder. diff --git a/utilities/cam_test_gui.py b/utilities/cam_test_gui.py index 9a0fe89f4..27cbe9b3d 100644 --- a/utilities/cam_test_gui.py +++ b/utilities/cam_test_gui.py @@ -148,13 +148,9 @@ def __init__(self, app: "Application"): self.main_layout.addWidget(self.disconnect_button) self.disconnect_button.setHidden(True) - self.process_label = QtWidgets.QLabel("Process") - self.pid_label = QtWidgets.QLabel("") - self.main_layout.addWidget(self.process_label) - self.main_layout.addWidget(self.pid_label) - def handle_disconnect(self): - self.connect_button.setDisabled(False) + self.available_devices_combo.clear() + self.connect_button.setDisabled(True) self.disconnect_button.setDisabled(True) self.disconnect_button.setHidden(True) self.connect_button.setHidden(False) @@ -287,7 +283,6 @@ def connect(self): self.test_process_pid = None self.disconnect() return - self.ui.pid_label.setText(f"PID: {self.test_process_pid}") self.query_devices_timer.stop() self.ui.handle_connect() @@ -311,6 +306,10 @@ def on_finish_query_devices(self, result): self.ui.available_devices_combo.addItems( list(map(lambda d: f"{d.name} ({d.getMxId()})", self.available_devices))) self.query_devices_timer.start() + if self.available_devices: + self.ui.connect_button.setDisabled(False) + else: + self.ui.connect_button.setDisabled(True) def main(): From b399434a372192eb9f594af08e6b2f933d790e76 Mon Sep 17 00:00:00 2001 From: zrezke Date: Thu, 9 Mar 2023 20:11:45 +0100 Subject: [PATCH 226/385] added automode and made it a bit more failproof --- utilities/cam_test_gui.py | 99 +++++++++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 20 deletions(-) diff --git a/utilities/cam_test_gui.py b/utilities/cam_test_gui.py index 27cbe9b3d..77c91d0c6 100644 --- a/utilities/cam_test_gui.py +++ b/utilities/cam_test_gui.py @@ -49,9 +49,13 @@ def __init__(self, app: "Application"): self.app = app self.app.setWindowTitle("Camera Test") self.main_widget = QtWidgets.QWidget() + self.scroll_widget = QtWidgets.QScrollArea() + self.scroll_widget.setWidget(self.main_widget) + self.scroll_widget.setWidgetResizable(True) + self.scroll_widget.setMinimumHeight(500) self.main_layout = QtWidgets.QVBoxLayout() self.main_widget.setLayout(self.main_layout) - self.app.setCentralWidget(self.main_widget) + self.app.setCentralWidget(self.scroll_widget) self.label_cameras = QtWidgets.QLabel("Cameras") self.main_layout.addWidget(self.label_cameras) @@ -139,21 +143,33 @@ def __init__(self, app: "Application"): self.available_devices_combo = QtWidgets.QComboBox() self.main_layout.addWidget(self.available_devices_combo) + self.connect_layout = QtWidgets.QHBoxLayout() self.connect_button = QtWidgets.QPushButton("Connect") self.connect_button.clicked.connect(self.app.connect) - self.main_layout.addWidget(self.connect_button) + self.connect_layout.addWidget(self.connect_button) + self.check_auto_mode = QtWidgets.QCheckBox("Auto Mode") + self.check_auto_mode.setToolTip( + "Whenever a device is available, connect to it automatically") + self.connect_layout.addWidget(self.check_auto_mode) + self.check_auto_mode.setChecked(False) + self.main_layout.addLayout(self.connect_layout) self.disconnect_button = QtWidgets.QPushButton("Disconnect") self.disconnect_button.clicked.connect(self.app.disconnect) self.main_layout.addWidget(self.disconnect_button) self.disconnect_button.setHidden(True) + def handle_automode_changed(self, state): + self.disconnect_button.setHidden(bool(state)) + self.connect_button.setHidden(bool(state)) + def handle_disconnect(self): self.available_devices_combo.clear() - self.connect_button.setDisabled(True) - self.disconnect_button.setDisabled(True) - self.disconnect_button.setHidden(True) - self.connect_button.setHidden(False) + if not self.check_auto_mode.isChecked(): + self.connect_button.setDisabled(True) + self.disconnect_button.setDisabled(True) + self.disconnect_button.setHidden(True) + self.connect_button.setHidden(False) self.add_cam_button.setDisabled(False) self.mono_resolution_combo.setDisabled(False) self.combo_color_resolution.setDisabled(False) @@ -171,14 +187,14 @@ def handle_disconnect(self): self.spin_connect_timeout.setDisabled(False) self.spin_boot_timeout.setDisabled(False) - def handle_connect(self): self.spin_boot_timeout.setDisabled(True) self.spin_connect_timeout.setDisabled(True) - self.connect_button.setDisabled(True) - self.disconnect_button.setDisabled(False) - self.disconnect_button.setHidden(False) - self.connect_button.setHidden(True) + if not self.check_auto_mode.isChecked(): + self.connect_button.setDisabled(True) + self.disconnect_button.setDisabled(False) + self.disconnect_button.setHidden(False) + self.connect_button.setHidden(True) self.add_cam_button.setDisabled(True) self.mono_resolution_combo.setDisabled(True) self.combo_color_resolution.setDisabled(True) @@ -198,6 +214,7 @@ def handle_connect(self): class WorkerSignals(QtCore.QObject): finished = QtCore.pyqtSignal(list) + class Worker(QtCore.QRunnable): def __init__(self, fn, *args, **kwargs): @@ -206,7 +223,7 @@ def __init__(self, fn, *args, **kwargs): self.args = args self.kwargs = kwargs self.signals = WorkerSignals() - + @QtCore.pyqtSlot() def run(self): result = self.fn(*self.args, **self.kwargs) @@ -223,7 +240,24 @@ def __init__(self): self.query_devices_timer.timeout.connect(self.query_devices) self.query_devices_timer.start(2000) self.query_devices() - self.test_process = None + + self.test_process_pid = None + + # Once the test process is started, periodically check if it's still running (catches eg. camera unplugged) + self.check_test_process_timer = QtCore.QTimer() + self.check_test_process_timer.timeout.connect(self.check_test_process) + + self.ui.check_auto_mode.stateChanged.connect(self.automode_changed) + + def closeEvent(self, a0: QtGui.QCloseEvent) -> None: + if self.test_process_pid: + os.kill(self.test_process_pid, signal.SIGINT) + return super().closeEvent(a0) + + def automode_changed(self, state): + self.ui.handle_automode_changed(state) + if not state: + self.disconnect() def construct_args_from_gui(self) -> List[str]: if not self.available_devices: @@ -265,30 +299,46 @@ def construct_args_from_gui(self) -> List[str]: cmd.append(str(self.ui.spin_boot_timeout.value())) return cmd + def check_test_process(self): + # Raises OSError if a process with the given PID doesn't exist + try: + os.kill(self.test_process_pid, 0) + except (OSError, TypeError): + self.test_process_pid = None + self.disconnect() + self.check_test_process_timer.stop() + def connect(self): args = self.construct_args_from_gui() if not args: return + started_successfully = False + self.test_process = QtCore.QProcess() # Forward stdout - self.test_process.setProcessChannelMode(QtCore.QProcess.ProcessChannelMode.ForwardedChannels) - self.test_process.finished.connect(self.disconnect) - started_successfully = False + self.test_process.setProcessChannelMode( + QtCore.QProcess.ProcessChannelMode.ForwardedChannels) # Start detached process with the function that also returns the PID if getattr(sys, 'frozen', False): - started_successfully, self.test_process_pid = self.test_process.startDetached(sys.executable, args, "") + started_successfully, self.test_process_pid = self.test_process.startDetached( + sys.executable, args, "") else: - started_successfully, self.test_process_pid = self.test_process.startDetached(sys.executable, sys.argv + args, "") + started_successfully, self.test_process_pid = self.test_process.startDetached( + sys.executable, sys.argv + args, "") if not started_successfully: self.test_process_pid = None self.disconnect() return self.query_devices_timer.stop() + self.check_test_process_timer.start(1000) self.ui.handle_connect() def disconnect(self): if self.test_process_pid: - os.kill(self.test_process_pid, signal.SIGINT) + try: + os.kill(self.test_process_pid, signal.SIGINT) + except OSError: + self.test_process_pid = None self.test_process_pid = None self.query_devices_timer.start() self.ui.handle_disconnect() @@ -297,16 +347,25 @@ def query_devices(self): self.query_devices_timer.stop() pool = QtCore.QThreadPool.globalInstance() query_devices_worker = Worker(dai.Device.getAllAvailableDevices) - query_devices_worker.signals.finished.connect(self.on_finish_query_devices) + query_devices_worker.signals.finished.connect( + self.on_finish_query_devices) pool.start(query_devices_worker) def on_finish_query_devices(self, result): + current_device = self.ui.available_devices_combo.currentText() self.ui.available_devices_combo.clear() self.available_devices = result self.ui.available_devices_combo.addItems( list(map(lambda d: f"{d.name} ({d.getMxId()})", self.available_devices))) self.query_devices_timer.start() if self.available_devices: + if current_device: + index = self.ui.available_devices_combo.findText( + current_device) + if index != -1: + self.ui.available_devices_combo.setCurrentIndex(index) + if self.ui.check_auto_mode.isChecked(): + self.connect() self.ui.connect_button.setDisabled(False) else: self.ui.connect_button.setDisabled(True) From 43a20306cc4400ebbc1dd0364319569f76d191e9 Mon Sep 17 00:00:00 2001 From: zrezke Date: Thu, 9 Mar 2023 19:39:51 +0100 Subject: [PATCH 227/385] fix for windows --- utilities/cam_test.py | 6 +++++- utilities/cam_test_gui.py | 18 +++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index fb8bbf999..18e6c3fc7 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -300,7 +300,11 @@ def exit_cleanly(signum, frame): capture_list = [] while True: for c in cam_list: - pkt = q[c].tryGet() + try: + pkt = q[c].tryGet() + except Exception as e: + print(e) + exit_cleanly(0, 0) if pkt is not None: fps_host[c].update() fps_capt[c].update(pkt.getTimestamp().total_seconds()) diff --git a/utilities/cam_test_gui.py b/utilities/cam_test_gui.py index 77c91d0c6..0617d8b90 100644 --- a/utilities/cam_test_gui.py +++ b/utilities/cam_test_gui.py @@ -4,6 +4,7 @@ import sys import signal import os +import psutil class CamTestGui: @@ -300,24 +301,19 @@ def construct_args_from_gui(self) -> List[str]: return cmd def check_test_process(self): - # Raises OSError if a process with the given PID doesn't exist - try: - os.kill(self.test_process_pid, 0) - except (OSError, TypeError): - self.test_process_pid = None - self.disconnect() - self.check_test_process_timer.stop() + if self.test_process_pid and psutil.pid_exists(self.test_process_pid): + return + self.test_process_pid = None + self.disconnect() + self.check_test_process_timer.stop() def connect(self): args = self.construct_args_from_gui() if not args: return + started_successfully = False - self.test_process = QtCore.QProcess() - # Forward stdout - self.test_process.setProcessChannelMode( - QtCore.QProcess.ProcessChannelMode.ForwardedChannels) # Start detached process with the function that also returns the PID if getattr(sys, 'frozen', False): started_successfully, self.test_process_pid = self.test_process.startDetached( From a68bf1afe170d3766853258a8db99d205fd5fd9f Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Thu, 9 Mar 2023 21:40:51 +0200 Subject: [PATCH 228/385] Update core --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index f319dc8f2..60e7ee563 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit f319dc8f235718a7cb452952ffdc285e2165dddb +Subproject commit 60e7ee56361efa7e50cd42081f0249cac898202e From fe1ec79dc1e1ae0a3e8f739700629a8de2d82510 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sat, 11 Mar 2023 18:25:05 +0100 Subject: [PATCH 229/385] Fixed some ..math docs, as sphinx is quite strange with these --- .../calibration/calibration_reader.rst | 17 +++------- .../tutorials/configuring-stereo-depth.rst | 32 +++++++++++++------ 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/docs/source/samples/calibration/calibration_reader.rst b/docs/source/samples/calibration/calibration_reader.rst index 199922bf6..27970e861 100644 --- a/docs/source/samples/calibration/calibration_reader.rst +++ b/docs/source/samples/calibration/calibration_reader.rst @@ -27,24 +27,17 @@ Here's theoretical calculation of the focal length in pixels: .. math:: - focal_length_in_pixels = image_width_in_pixels * 0.5 / tan(HFOV * 0.5 * PI/180) + focalLength = width_px * 0.5 / tan(hfov * 0.5 * pi / 180) - // With 400P mono camera resolution where HFOV=71.9 degrees - focal_length_in_pixels = 640 * 0.5 / tan(71.9 * 0.5 * PI / 180) = 441.25 - - // With 800P mono camera resolution where HFOV=71.9 degrees - focal_length_in_pixels = 1280 * 0.5 / tan(71.9 * 0.5 * PI / 180) = 882.5 - -Examples for calculating the depth value, using the OAK-D (7.5cm baseline): +With 400P (640x400) camera resolution where HFOV=71.9 degrees: .. math:: - # For OAK-D @ 400P resolution and disparity of eg. 50 pixels - depth = 441.25 * 7.5 / 50 = 66.19 # cm + focalLength = 640 * 0.5 / tan(71.9 * 0.5 * PI / 180) = 441.25 - # For OAK-D @ 800P resolution and disparity of eg. 10 pixels - depth = 882.5 * 7.5 / 10 = 661.88 # cm +And for 800P (1280x800) camera resolution where HFOV=71.9 degrees: + focalLength = 1280 * 0.5 / tan(71.9 * 0.5 * PI / 180) = 882.5 Setup ##### diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst index 76da3da9c..a43b6fe63 100644 --- a/docs/source/tutorials/configuring-stereo-depth.rst +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -36,6 +36,13 @@ Let's first look at how the depth is calculated: depth [mm] = focalLength [pix] * baseline [mm] / disparity [pix] +.. dropdown:: + +Examples for calculating the depth value, using the OAK-D (7.5cm baseline OV9282), for 400P resolution and disparity of 50 pixels: + +.. math:: + + depth = 441.25 * 7.5 / 50 = 66.19 cm `RVC2 `__-based cameras have a **0..95 disparity search** range, which limits the minimal depth perception. Baseline is the distance between two cameras of the @@ -280,7 +287,7 @@ Stereo subpixel effect on layering ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Default stereo depth output has 0..95 disparity pixels, which would produce 96 unique depth values. This can especially be seen when using pointcloud representation - and seeing how there are discrete "layers" of points, instead of a smooth transition: +and seeing how there are discrete "layers" of points, instead of a smooth transition: .. image:: /_static/images/components/layered-pointcloud.png @@ -288,14 +295,19 @@ This layering can especially be seen at longer distances, where these layers are But with Stereo Subpixel mode enabled, there are many more unique values possible, which produces more granular depth steps, and thus smoother a pointcloud. +.. code-block:: python + + # Number of unique values based on subpixel bits setting. + Unique values = 94 * 2 ^ subpixel_bits + 2 (min/max value) + .. math:: - 94 * 2^3 [subpixel bits] + 2 [min/max value] = 754 unique values + 94 * 2^3 + 2 = 754 .. math:: - 94 * 2^4 [subpixel bits] + 2 [min/max value] = 1506 unique values + 94 * 2^4 + 2 = 1506 .. math:: - 94 * 2^5 [subpixel bits] + 2 [min/max value] = 3010 unique values + 94 * 2^5 + 2 = 3010 One can change the number of subpixel bits by setting ``stereoDepth.setSubpixelFractionalBits(int)`` parameter to 3, 4 or 5 bits. @@ -317,10 +329,10 @@ Going back to :ref:`Depth from disparity`, minimal depth perception (**MinZ**) i (maximum number of pixel for disparity search): .. math:: - depth = focal_length * baseline / disparity + depth = focalLength * baseline / disparity .. math:: - MinZ = focal_length * baseline / 95 + MinZ = focalLength * baseline / 95 How to get lower MinZ --------------------- @@ -343,10 +355,10 @@ Above we have a formula for MinZ, and by lowering the resolution, we are lowerin MinZ = focalLength * baseline / 95 .. math:: - MinZ [800P OAK-D] = 882.5 * 7.5 / 95 = 70 cm + MinZ [800P] = 882.5 * 7.5 / 95 = 70 cm .. math:: - MinZ [400P OAK-D] = 441 * 7.5 / 95 = 35 cm + MinZ [400P] = 441 * 7.5 / 95 = 35 cm As you can see, by lowering resolution by 2, we are also lowering MinZ by 2. Note that because you have fewer pixels, you will also have lower depth accuracy (in cm). @@ -431,7 +443,7 @@ That gives the following formula: B = 2 * Dv * tan(HFOV/2) * W / (2 * D * tan(HFOV/2)) .. math:: - B = W * Dv / D # pixels + B [pixels] = W * Dv / D Example: If we are using OAK-D, which has a HFOV of 72°, a baseline (:code:`BL`) of 7.5 cm and 640x400 (400P) resolution is used, therefore :code:`W = 640` and an object is :code:`D = 100` cm away, we can @@ -440,7 +452,7 @@ calculate :code:`B` in the following way: .. math:: Dv = 7.5 / 2 * tan(90 - 72/2) = 3.75 * tan(54°) = 5.16 cm - B = 640 * 5.16 / 100 = 33 # pixels + B = 640 * 5.16 / 100 = 33 Credit for calculations and images goes to our community member gregflurry, which he made on `this `__ From c422e2c6621c589b917d506a4a438454768a75e8 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sun, 12 Mar 2023 14:58:11 +0100 Subject: [PATCH 230/385] Fix docs building --- docs/source/samples/calibration/calibration_reader.rst | 7 +++++++ docs/source/tutorials/configuring-stereo-depth.rst | 2 -- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/source/samples/calibration/calibration_reader.rst b/docs/source/samples/calibration/calibration_reader.rst index 27970e861..93b363b67 100644 --- a/docs/source/samples/calibration/calibration_reader.rst +++ b/docs/source/samples/calibration/calibration_reader.rst @@ -29,6 +29,9 @@ Here's theoretical calculation of the focal length in pixels: focalLength = width_px * 0.5 / tan(hfov * 0.5 * pi / 180) +To get the HFOV you can use `this script `__, which also works for wide-FOV cameras and allows you to +specif alpha parameter. + With 400P (640x400) camera resolution where HFOV=71.9 degrees: .. math:: @@ -37,8 +40,12 @@ With 400P (640x400) camera resolution where HFOV=71.9 degrees: And for 800P (1280x800) camera resolution where HFOV=71.9 degrees: +.. math:: + focalLength = 1280 * 0.5 / tan(71.9 * 0.5 * PI / 180) = 882.5 + + Setup ##### diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst index a43b6fe63..22d21b544 100644 --- a/docs/source/tutorials/configuring-stereo-depth.rst +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -36,8 +36,6 @@ Let's first look at how the depth is calculated: depth [mm] = focalLength [pix] * baseline [mm] / disparity [pix] -.. dropdown:: - Examples for calculating the depth value, using the OAK-D (7.5cm baseline OV9282), for 400P resolution and disparity of 50 pixels: .. math:: From 8187862ce067acf5622c2381288fc2d1540bf03c Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 13 Mar 2023 12:58:01 +0100 Subject: [PATCH 231/385] Final touches to docs --- docs/source/tutorials/configuring-stereo-depth.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst index 22d21b544..1562fd5e4 100644 --- a/docs/source/tutorials/configuring-stereo-depth.rst +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -295,8 +295,8 @@ But with Stereo Subpixel mode enabled, there are many more unique values possibl .. code-block:: python - # Number of unique values based on subpixel bits setting. - Unique values = 94 * 2 ^ subpixel_bits + 2 (min/max value) + # Number of unique values based on subpixel bits setting + unique_values = 94 * 2 ^ subpixel_bits + 2 [min/max value] .. math:: 94 * 2^3 + 2 = 754 @@ -478,6 +478,7 @@ For noisy pointcloud we suggest a few approaches: * (mentioned above) Start with the :ref:`Fixing noisy depth <2. Fixing noisy depth>` chapter, as otherwise, noise will produce points all over the pointcloud * (mentioned above) Continue with the :ref:`Improving depth accuracy <3. Improving depth accuracy>` chapter - depth inaccuracy will be easily visible in pointcloud + * Enable Stereo subpixel mode, especially due to the :ref:`Stereo subpixel effect on layering` * :ref:`Decimation filter for pointcloud` for faster processing (FPS) and additional filtering From dd39439e442de4b3fd4cdd4d3954b08b6900cd0b Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Mon, 13 Mar 2023 18:03:14 +0100 Subject: [PATCH 232/385] [FW] Added missing bindings for CameraControl and ImgFrame --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 60e7ee563..e5dae8fd2 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 60e7ee56361efa7e50cd42081f0249cac898202e +Subproject commit e5dae8fd2f9e543bd99f9d1a73959398ef5bd0b4 From 4895b007bf8f79880ad024a9ac020f0f12b7d322 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 17 Feb 2023 18:16:12 +0100 Subject: [PATCH 233/385] RGB depth alignment add docs on high-res color stream (cherry picked from commit 092e109202e0f2230ae595a46e3e2d854617c4b2) --- docs/source/samples/StereoDepth/rgb_depth_aligned.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/samples/StereoDepth/rgb_depth_aligned.rst b/docs/source/samples/StereoDepth/rgb_depth_aligned.rst index fbdd16264..4c854cbe6 100644 --- a/docs/source/samples/StereoDepth/rgb_depth_aligned.rst +++ b/docs/source/samples/StereoDepth/rgb_depth_aligned.rst @@ -12,6 +12,9 @@ By default, the depth map will get scaled to match the resolution of the camera depth is aligned to the 1080P color sensor, StereoDepth will upscale depth to 1080P as well. Depth scaling can be avoided by configuring :ref:`StereoDepth`'s ``stereo.setOutputSize(width, height)``. +To align depth with **higher resolution color stream** (eg. 12MP), you need to limit the resolution of the depth map. You can +do that with ``stereo.setOutputSize(w,h)``. Code `example here `__. + Demo #### From cb982ff305bbd337219dce9f153368b6768381e3 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 17 Feb 2023 18:16:33 +0100 Subject: [PATCH 234/385] VideoENcoder docs elaboration on lossy codecs (cherry picked from commit 090831907abee4b2db306146ae9d129705644efd) --- docs/source/components/nodes/video_encoder.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/components/nodes/video_encoder.rst b/docs/source/components/nodes/video_encoder.rst index 060d36c1c..4f899de7a 100644 --- a/docs/source/components/nodes/video_encoder.rst +++ b/docs/source/components/nodes/video_encoder.rst @@ -2,7 +2,7 @@ VideoEncoder ============ VideoEncoder node is used to encode :ref:`ImgFrame` into either H264, H265, or MJPEG streams. Only NV12 or GRAY8 (which gets converted to NV12) format is -supported as an input. +supported as an input. All codecs are lossy (except lossless MJPEG), for more information please see `encoding quality docs `__. .. include:: /includes/container-encoding.rst From 6b6807c64012b18e7ae4868756093874e97ad251 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 13 Mar 2023 19:32:39 +0100 Subject: [PATCH 235/385] Added cameraSensorConfig python bindings. Not tested (cherry picked from commit 53f5d8489137d0c93cfad603715daf16f4b5f39c) --- src/pipeline/CommonBindings.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pipeline/CommonBindings.cpp b/src/pipeline/CommonBindings.cpp index 967721ed8..abb40023c 100644 --- a/src/pipeline/CommonBindings.cpp +++ b/src/pipeline/CommonBindings.cpp @@ -37,6 +37,7 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ py::enum_ cameraBoardSocket(m, "CameraBoardSocket", DOC(dai, CameraBoardSocket)); py::enum_ cameraSensorType(m, "CameraSensorType", DOC(dai, CameraSensorType)); py::enum_ cameraImageOrientation(m, "CameraImageOrientation", DOC(dai, CameraImageOrientation)); + py::class_ cameraSensorConfig(m, "CameraSensorConfig", DOC(dai, CameraSensorConfig)); py::class_ cameraFeatures(m, "CameraFeatures", DOC(dai, CameraFeatures)); py::class_ memoryInfo(m, "MemoryInfo", DOC(dai, MemoryInfo)); py::class_ chipTemperature(m, "ChipTemperature", DOC(dai, ChipTemperature)); @@ -172,6 +173,7 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("supportedTypes", &CameraFeatures::supportedTypes) .def_readwrite("hasAutofocus", &CameraFeatures::hasAutofocus) .def_readwrite("name", &CameraFeatures::name) + .def_readwrite("configs", &CameraFeatures::configs) .def("__repr__", [](CameraFeatures& camera) { std::stringstream stream; stream << camera; @@ -179,6 +181,16 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ }); ; + // CameraSensorConfig + cameraSensorConfig + .def(py::init<>()) + .def_readwrite("width", &CameraSensorConfig::width) + .def_readwrite("height", &CameraSensorConfig::height) + .def_readwrite("minFps", &CameraSensorConfig::minFps) + .def_readwrite("maxFps", &CameraSensorConfig::maxFps) + .def_readwrite("type", &CameraSensorConfig::type) + ; + // MemoryInfo memoryInfo .def(py::init<>()) From f3d8718c1a46265915503307e76d5984a1f07a7d Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 15 Mar 2023 16:23:53 +0100 Subject: [PATCH 236/385] Added dai.Node.Output missing bindings, not tested --- src/pipeline/node/NodeBindings.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pipeline/node/NodeBindings.cpp b/src/pipeline/node/NodeBindings.cpp index 222ca997c..ada75cbcf 100644 --- a/src/pipeline/node/NodeBindings.cpp +++ b/src/pipeline/node/NodeBindings.cpp @@ -230,6 +230,8 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("name", &Node::Output::name, DOC(dai, Node, Output, name)) .def_readwrite("type", &Node::Output::type, DOC(dai, Node, Output, type)) .def_readwrite("possibleDatatypes", &Node::Output::possibleDatatypes, DOC(dai, Node, Output, possibleDatatypes)) + .def("getParent", &Node::Output::getParent, DOC(dai, Node, Output, getParent)) + .def("isSamePipeline", &Node::Output::isSamePipeline, py::arg("input"), DOC(dai, Node, Output, isSamePipeline)) .def("canConnect", &Node::Output::canConnect, py::arg("input"), DOC(dai, Node, Output, canConnect)) .def("link", &Node::Output::link, py::arg("input"), DOC(dai, Node, Output, link)) .def("unlink", &Node::Output::unlink, py::arg("input"), DOC(dai, Node, Output, unlink)) From 429a3378e5ac5d693976a1c1c3da573230aff98b Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 15 Mar 2023 17:36:39 +0100 Subject: [PATCH 237/385] I hope docs will build now --- src/pipeline/node/NodeBindings.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pipeline/node/NodeBindings.cpp b/src/pipeline/node/NodeBindings.cpp index ada75cbcf..ef2bd8c2e 100644 --- a/src/pipeline/node/NodeBindings.cpp +++ b/src/pipeline/node/NodeBindings.cpp @@ -230,7 +230,8 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("name", &Node::Output::name, DOC(dai, Node, Output, name)) .def_readwrite("type", &Node::Output::type, DOC(dai, Node, Output, type)) .def_readwrite("possibleDatatypes", &Node::Output::possibleDatatypes, DOC(dai, Node, Output, possibleDatatypes)) - .def("getParent", &Node::Output::getParent, DOC(dai, Node, Output, getParent)) + .def("getParent", static_cast(&Node::Output::getParent), py::return_value_policy::reference_internal, DOC(dai, Node, Output, getParent)) + .def("getParent", static_cast(&Node::Output::getParent), py::return_value_policy::reference_internal, DOC(dai, Node, Output, getParent)) .def("isSamePipeline", &Node::Output::isSamePipeline, py::arg("input"), DOC(dai, Node, Output, isSamePipeline)) .def("canConnect", &Node::Output::canConnect, py::arg("input"), DOC(dai, Node, Output, canConnect)) .def("link", &Node::Output::link, py::arg("input"), DOC(dai, Node, Output, link)) From 49088615716bf8c404cc31acbc9cab82ad44e711 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Thu, 16 Mar 2023 15:42:07 +0200 Subject: [PATCH 238/385] Update FW with fix for calibration load example --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index e5dae8fd2..0c3d6be3b 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit e5dae8fd2f9e543bd99f9d1a73959398ef5bd0b4 +Subproject commit 0c3d6be3b3574867cc99d1ad85c3f75eade40f58 From b4f59d8d1a1d20e4489f79186b58cc705cd3d22c Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Sun, 19 Mar 2023 19:42:58 +0100 Subject: [PATCH 239/385] [FW] Removed UTF-8 degree sign from temperature prints --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index c2b96c81f..fc81e1b7d 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit c2b96c81f11bbd3625cbd18c634c60a969b675c3 +Subproject commit fc81e1b7d0c66813b9e75dbe83191d8c9e1db66e From e0bf36d8d7333539d56a24e44ed8ba7df1ffcb8d Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 20 Mar 2023 17:57:24 +0200 Subject: [PATCH 240/385] Move sipp buffer size from BoardConfig to GlobalProperties --- depthai-core | 2 +- src/DeviceBindings.cpp | 4 ---- src/pipeline/PipelineBindings.cpp | 4 ++++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/depthai-core b/depthai-core index fc81e1b7d..6b2963b78 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit fc81e1b7d0c66813b9e75dbe83191d8c9e1db66e +Subproject commit 6b2963b781e081974a5f9a5056ea81c56d6d2b2e diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 1acfc5459..673bac270 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -375,7 +375,6 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("flashBootedVid", &BoardConfig::USB::flashBootedVid) .def_readwrite("flashBootedPid", &BoardConfig::USB::flashBootedPid) .def_readwrite("maxSpeed", &BoardConfig::USB::maxSpeed) - .def_readwrite("isp3aMaxFps", &BoardConfig::USB::isp3aMaxFps, DOC(dai, BoardConfig, USB, isp3aMaxFps)) ; // Bind BoardConfig::Network @@ -383,7 +382,6 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def(py::init<>()) .def_readwrite("mtu", &BoardConfig::Network::mtu) .def_readwrite("xlinkTcpNoDelay", &BoardConfig::Network::xlinkTcpNoDelay) - .def_readwrite("isp3aMaxFps", &BoardConfig::Network::isp3aMaxFps, DOC(dai, BoardConfig, Network, isp3aMaxFps)) ; // GPIO Mode @@ -462,8 +460,6 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("sysctl", &BoardConfig::sysctl, DOC(dai, BoardConfig, sysctl)) .def_readwrite("watchdogTimeoutMs", &BoardConfig::watchdogTimeoutMs, DOC(dai, BoardConfig, watchdogTimeoutMs)) .def_readwrite("watchdogInitialDelayMs", &BoardConfig::watchdogInitialDelayMs, DOC(dai, BoardConfig, watchdogInitialDelayMs)) - .def_readwrite("sippBufferSize", &BoardConfig::sippBufferSize, DOC(dai, BoardConfig, sippBufferSize)) - .def_readwrite("sippDmaBufferSize", &BoardConfig::sippDmaBufferSize, DOC(dai, BoardConfig, sippDmaBufferSize)) .def_readwrite("gpio", &BoardConfig::gpio, DOC(dai, BoardConfig, gpio)) .def_readwrite("uart", &BoardConfig::uart, DOC(dai, BoardConfig, uart)) .def_readwrite("pcieInternalClock", &BoardConfig::pcieInternalClock, DOC(dai, BoardConfig, pcieInternalClock)) diff --git a/src/pipeline/PipelineBindings.cpp b/src/pipeline/PipelineBindings.cpp index 12eb4ee07..4068d81ae 100644 --- a/src/pipeline/PipelineBindings.cpp +++ b/src/pipeline/PipelineBindings.cpp @@ -74,6 +74,8 @@ void PipelineBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("cameraTuningBlobSize", &GlobalProperties::cameraTuningBlobSize, DOC(dai, GlobalProperties, cameraTuningBlobSize)) .def_readwrite("cameraTuningBlobUri", &GlobalProperties::cameraTuningBlobUri, DOC(dai, GlobalProperties, cameraTuningBlobUri)) .def_readwrite("xlinkChunkSize", &GlobalProperties::xlinkChunkSize, DOC(dai, GlobalProperties, xlinkChunkSize)) + .def_readwrite("sippBufferSize", &GlobalProperties::sippBufferSize, DOC(dai, GlobalProperties, sippBufferSize)) + .def_readwrite("sippDmaBufferSize", &GlobalProperties::sippDmaBufferSize, DOC(dai, GlobalProperties, sippDmaBufferSize)) ; // bind pipeline @@ -99,6 +101,8 @@ void PipelineBindings::bind(pybind11::module& m, void* pCallstack){ .def("getRequiredOpenVINOVersion", &Pipeline::getRequiredOpenVINOVersion, DOC(dai, Pipeline, getRequiredOpenVINOVersion)) .def("setCameraTuningBlobPath", &Pipeline::setCameraTuningBlobPath, py::arg("path"), DOC(dai, Pipeline, setCameraTuningBlobPath)) .def("setXLinkChunkSize", &Pipeline::setXLinkChunkSize, py::arg("sizeBytes"), DOC(dai, Pipeline, setXLinkChunkSize)) + .def("setSippBufferSize", &Pipeline::setSippBufferSize, py::arg("sizeBytes"), DOC(dai, Pipeline, setSippBufferSize)) + .def("setSippDmaBufferSize", &Pipeline::setSippDmaBufferSize, py::arg("sizeBytes"), DOC(dai, Pipeline, setSippDmaBufferSize)) .def("setCalibrationData", &Pipeline::setCalibrationData, py::arg("calibrationDataHandler"), DOC(dai, Pipeline, setCalibrationData)) .def("getCalibrationData", &Pipeline::getCalibrationData, DOC(dai, Pipeline, getCalibrationData)) .def("getDeviceConfig", &Pipeline::getDeviceConfig, DOC(dai, Pipeline, getDeviceConfig)) From 32a4974402997a17343f71f0da1cf27f12ad29f2 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 20 Mar 2023 18:09:33 +0200 Subject: [PATCH 241/385] Update core --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 6b2963b78..9a91f918d 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 6b2963b781e081974a5f9a5056ea81c56d6d2b2e +Subproject commit 9a91f918d1b931307820605216285e112bb46908 From 87c3b9bd621aad0fd30aa162a3cf9e88c2d0d581 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Thu, 23 Mar 2023 23:08:32 +0100 Subject: [PATCH 242/385] Updated nn latency docs --- docs/source/tutorials/low-latency.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/source/tutorials/low-latency.rst b/docs/source/tutorials/low-latency.rst index a4d33aaf0..7b359ae92 100644 --- a/docs/source/tutorials/low-latency.rst +++ b/docs/source/tutorials/low-latency.rst @@ -120,6 +120,30 @@ Reducing latency when running NN In the examples above we were only streaming frames, without doing anything else on the OAK camera. This section will focus on how to reduce latency when also running NN model on the OAK. +Resource utilization +-------------------- + +Configuring `hardware resources `__ +on RVC will result in lower latency, but also in lower FPS. + +By default, NN nodes have run 2 threads and 1 NCE/thread, and it's suggested to compile the model for half of the +available SHAVEs of the pipeline. + +To minimize the latency, we should allocate all resources to the single inference. To get lowest latency (which will result in a bit lower FPS), +we suggest the following: + +- Setting the number of threads to 1 +- Setting the number of NCE per thread to 2 +- Compiling the model for all available SHAVE cores - `documentation here `__) + +.. code-block:: python + + nn = pipeline.create(dai.node.NeuralNetwork) + # Same for Yolo/MobileNet (Spatial) Detection node + nn.setNumNCEPerInferenceThread(2) + nn.setNumInferenceThreads(1) + nn.setBlobPath('path/to/compiled/model_max_shaves.blob') + Lowering camera FPS to match NN FPS ----------------------------------- From 5d7eb49a03f27c987b8d2694d67541acd128e1a5 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 24 Mar 2023 17:39:55 +0100 Subject: [PATCH 243/385] Updating docs as per PR review --- docs/source/tutorials/low-latency.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/source/tutorials/low-latency.rst b/docs/source/tutorials/low-latency.rst index 7b359ae92..2e7e06f94 100644 --- a/docs/source/tutorials/low-latency.rst +++ b/docs/source/tutorials/low-latency.rst @@ -126,10 +126,14 @@ Resource utilization Configuring `hardware resources `__ on RVC will result in lower latency, but also in lower FPS. -By default, NN nodes have run 2 threads and 1 NCE/thread, and it's suggested to compile the model for half of the -available SHAVEs of the pipeline. +By default, NN nodes are running 2 threads, 1 NCE/thread, and we suggest compiling the model for half of the +available SHAVE cores of the pipeline. This configuration will provide best throughput, as all threads can run freely. +Compiling the model for more SHAVE cores will only provide marginal improvement, due to: -To minimize the latency, we should allocate all resources to the single inference. To get lowest latency (which will result in a bit lower FPS), +1. `Model optimizer`__ doing a great work at optimizing the model +2. On-deivce parallelization of NN operations (splitting the operation task between multiple SHAVEs) doesn't scale linearly due to " `memory wall `__ " + +To minimize the latency, though, we should allocate all resources to the single inference. To get lowest latency (which will result in much lower FPS), we suggest the following: - Setting the number of threads to 1 From a96b4fdb8959c902eac2c24e7707c31344150775 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sat, 25 Mar 2023 10:08:34 +0100 Subject: [PATCH 244/385] Fix building docs --- docs/source/tutorials/low-latency.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/tutorials/low-latency.rst b/docs/source/tutorials/low-latency.rst index 2e7e06f94..0961c9b1b 100644 --- a/docs/source/tutorials/low-latency.rst +++ b/docs/source/tutorials/low-latency.rst @@ -111,7 +111,7 @@ Encoded frames You can also reduce frame latency by using `Zero-Copy `__ branch of the DepthAI. This will pass pointers (at XLink level) to cv2.Mat instead of doing memcopy (as it currently does), so performance improvement would depend on the image sizes you are using. -(Note: API differs and not all functionality is available as is on the `message_zero_copy` branch) +(Note: API differs and not all functionality is available as-is on the `message_zero_copy` branch) Reducing latency when running NN @@ -130,8 +130,8 @@ By default, NN nodes are running 2 threads, 1 NCE/thread, and we suggest compili available SHAVE cores of the pipeline. This configuration will provide best throughput, as all threads can run freely. Compiling the model for more SHAVE cores will only provide marginal improvement, due to: -1. `Model optimizer`__ doing a great work at optimizing the model -2. On-deivce parallelization of NN operations (splitting the operation task between multiple SHAVEs) doesn't scale linearly due to " `memory wall `__ " +1. `Model optimizer `__ doing a great work at optimizing the model +2. On-device parallelization of NN operations (splitting the operation task between multiple SHAVEs) doesn't scale linearly due to " `memory wall `__ " To minimize the latency, though, we should allocate all resources to the single inference. To get lowest latency (which will result in much lower FPS), we suggest the following: From 4c651a06c65572310fec598459d724cd936d9d0c Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sun, 26 Mar 2023 15:01:46 +0200 Subject: [PATCH 245/385] Updated IMU information (cherry picked from commit 09c3bad0e70d0107e062b5b8e5c2dcc41fd340cf) --- docs/source/components/nodes/imu.rst | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/docs/source/components/nodes/imu.rst b/docs/source/components/nodes/imu.rst index 70b89612d..50a57fefe 100644 --- a/docs/source/components/nodes/imu.rst +++ b/docs/source/components/nodes/imu.rst @@ -5,10 +5,10 @@ IMU (`intertial measurement unit `__ (`datasheet here `__) 9-axis sensor, combining accelerometer, gyroscope, and magnetometer. It also does sensor fusion on the (IMU) chip itself. We have efficiently integrated `this driver `__ into the DepthAI. -- `BMI270 `__ 6-axis sensor, combining accelerometer and gyroscope +- `BMI270 `__ 6-axis sensor, combining accelerometer and gyroscope. -. The IMU chip is connected to the `RVC `__ -over SPI. +The IMU chip is connected to the `RVC `__ +over SPI. See `OAK Hardware documentation `__ to check whether your OAK camera has IMU integrated. How to place it @@ -83,22 +83,6 @@ Usage // useful to reduce device's CPU load and number of lost packets, if CPU load is high on device side due to multiple nodes imu->setMaxBatchReports(10); -IMU devices -########### - -List of devices that have an IMU sensor on-board: - -* `OAK-D `__ -* `OAK-D-PoE `__ -* `OAK-D CM4 PoE `__ -* `OAK-FFC-3P `__ -* `OAK-FFC-4P `__ -* `OAK-D Pro `__ (All varients) -* `OAK-D S2 `__ (All varients) -* `OAK-D S2 PoE `__ (All varients) -* `OAK-D Pro PoE `__ (All varients) - - IMU sensors ########### From 0ed96eb3d3a7ff72c53472ac316ea697bad53c4b Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 27 Mar 2023 21:25:45 +0300 Subject: [PATCH 246/385] [Stereo] Add option to invalidate edge pixels on disparity/depth frame --- depthai-core | 2 +- src/pipeline/datatype/StereoDepthConfigBindings.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 9a91f918d..9ad71097f 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 9a91f918d1b931307820605216285e112bb46908 +Subproject commit 9ad71097fb00494ae0a562cc8a1c9edfb1771767 diff --git a/src/pipeline/datatype/StereoDepthConfigBindings.cpp b/src/pipeline/datatype/StereoDepthConfigBindings.cpp index 0fe336ef0..9ad36f384 100644 --- a/src/pipeline/datatype/StereoDepthConfigBindings.cpp +++ b/src/pipeline/datatype/StereoDepthConfigBindings.cpp @@ -241,6 +241,7 @@ void bind_stereodepthconfig(pybind11::module& m, void* pCallstack){ .def("setDepthUnit", &StereoDepthConfig::setDepthUnit, DOC(dai, StereoDepthConfig, setDepthUnit)) .def("getDepthUnit", &StereoDepthConfig::getDepthUnit, DOC(dai, StereoDepthConfig, getDepthUnit)) .def("setDisparityShift", &StereoDepthConfig::setDisparityShift, DOC(dai, StereoDepthConfig, setDisparityShift)) + .def("setNumInvalidateEdgePixels", &StereoDepthConfig::setNumInvalidateEdgePixels, DOC(dai, StereoDepthConfig, setNumInvalidateEdgePixels)) .def("set", &StereoDepthConfig::set, py::arg("config"), DOC(dai, StereoDepthConfig, set)) .def("get", &StereoDepthConfig::get, DOC(dai, StereoDepthConfig, get)) ; From 5e037c0887e86d8f0a635186857ffe3ed951d718 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 27 Mar 2023 21:40:58 +0300 Subject: [PATCH 247/385] Add missing bindings --- examples/StereoDepth/stereo_depth_from_host.py | 9 +++++++++ src/pipeline/datatype/StereoDepthConfigBindings.cpp | 1 + 2 files changed, 10 insertions(+) diff --git a/examples/StereoDepth/stereo_depth_from_host.py b/examples/StereoDepth/stereo_depth_from_host.py index cb1d782f0..d6082df2e 100755 --- a/examples/StereoDepth/stereo_depth_from_host.py +++ b/examples/StereoDepth/stereo_depth_from_host.py @@ -157,6 +157,7 @@ def destroyWindow(self): trDecimationFactor = list() trDisparityShift = list() trCenterAlignmentShift = list() + trInvalidateEdgePixels = list() def trackbarSigma(value): StereoConfigHandler.config.postProcessing.bilateralSigmaValue = value @@ -289,6 +290,13 @@ def trackbarCenterAlignmentShift(value): StereoConfigHandler.newConfig = True for tr in StereoConfigHandler.trCenterAlignmentShift: tr.set(value) + + def trackbarInvalidateEdgePixels(value): + StereoConfigHandler.config.algorithmControl.numInvalidateEdgePixels = value + print(f"numInvalidateEdgePixels: {StereoConfigHandler.config.algorithmControl.numInvalidateEdgePixels:.2f}") + StereoConfigHandler.newConfig = True + for tr in StereoConfigHandler.trInvalidateEdgePixels: + tr.set(value) def handleKeypress(key, stereoDepthConfigInQueue): if key == ord('m'): @@ -428,6 +436,7 @@ def registerWindow(stream): StereoConfigHandler.trFractionalBits.append(StereoConfigHandler.Trackbar('Subpixel fractional bits', stream, 3, 5, StereoConfigHandler.config.algorithmControl.subpixelFractionalBits, StereoConfigHandler.trackbarFractionalBits)) StereoConfigHandler.trDisparityShift.append(StereoConfigHandler.Trackbar('Disparity shift', stream, 0, 100, StereoConfigHandler.config.algorithmControl.disparityShift, StereoConfigHandler.trackbarDisparityShift)) StereoConfigHandler.trCenterAlignmentShift.append(StereoConfigHandler.Trackbar('Center alignment shift factor', stream, 0, 100, StereoConfigHandler.config.algorithmControl.centerAlignmentShiftFactor, StereoConfigHandler.trackbarCenterAlignmentShift)) + StereoConfigHandler.trInvalidateEdgePixels.append(StereoConfigHandler.Trackbar('Invalidate edge pixels', stream, 0, 100, StereoConfigHandler.config.algorithmControl.numInvalidateEdgePixels, StereoConfigHandler.trackbarInvalidateEdgePixels)) StereoConfigHandler.trLineqAlpha.append(StereoConfigHandler.Trackbar('Linear equation alpha', stream, 0, 15, StereoConfigHandler.config.costMatching.linearEquationParameters.alpha, StereoConfigHandler.trackbarLineqAlpha)) StereoConfigHandler.trLineqBeta.append(StereoConfigHandler.Trackbar('Linear equation beta', stream, 0, 15, StereoConfigHandler.config.costMatching.linearEquationParameters.beta, StereoConfigHandler.trackbarLineqBeta)) StereoConfigHandler.trLineqThreshold.append(StereoConfigHandler.Trackbar('Linear equation threshold', stream, 0, 255, StereoConfigHandler.config.costMatching.linearEquationParameters.threshold, StereoConfigHandler.trackbarLineqThreshold)) diff --git a/src/pipeline/datatype/StereoDepthConfigBindings.cpp b/src/pipeline/datatype/StereoDepthConfigBindings.cpp index 9ad36f384..3e202908a 100644 --- a/src/pipeline/datatype/StereoDepthConfigBindings.cpp +++ b/src/pipeline/datatype/StereoDepthConfigBindings.cpp @@ -89,6 +89,7 @@ void bind_stereodepthconfig(pybind11::module& m, void* pCallstack){ .def_readwrite("subpixelFractionalBits", &RawStereoDepthConfig::AlgorithmControl::subpixelFractionalBits, DOC(dai, RawStereoDepthConfig, AlgorithmControl, subpixelFractionalBits)) .def_readwrite("disparityShift", &RawStereoDepthConfig::AlgorithmControl::disparityShift, DOC(dai, RawStereoDepthConfig, AlgorithmControl, disparityShift)) .def_readwrite("centerAlignmentShiftFactor", &RawStereoDepthConfig::AlgorithmControl::centerAlignmentShiftFactor, DOC(dai, RawStereoDepthConfig, AlgorithmControl, centerAlignmentShiftFactor)) + .def_readwrite("numInvalidateEdgePixels", &RawStereoDepthConfig::AlgorithmControl::numInvalidateEdgePixels, DOC(dai, RawStereoDepthConfig, AlgorithmControl, numInvalidateEdgePixels)) ; spatialFilter From d393b77489c8da4bc3f27521fa864b80dbb0ccc0 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 27 Mar 2023 22:06:21 +0300 Subject: [PATCH 248/385] Update FW: handle disparity flipping --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 9ad71097f..26e65a338 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 9ad71097fb00494ae0a562cc8a1c9edfb1771767 +Subproject commit 26e65a338f5db1309010d617ea948f03c5ca235b From e0f7e37ef4f6fe45bfc961e658879cb7ab9559ff Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 28 Mar 2023 16:36:30 +0300 Subject: [PATCH 249/385] Update FW: support for stereo.setOutputSize when LEFT or RIGHT alignment is set --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 9a91f918d..2097673b5 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 9a91f918d1b931307820605216285e112bb46908 +Subproject commit 2097673b5489c6e15148e9b5db54d9733330a011 From e972b3be683b6710e7beae56baf49631b2fad0f9 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 28 Mar 2023 19:38:50 +0300 Subject: [PATCH 250/385] Update FW: support for stereo between RGB and LEFT/RIGHT --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 143f77810..4c2035bbd 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 143f77810529fcd4334f97291f2207a906b6a624 +Subproject commit 4c2035bbdf9dbe9000e55da111f68fda79504a52 From f29998d16158006643a39e5c9a242cdf2825a638 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 30 Mar 2023 22:34:06 +0200 Subject: [PATCH 251/385] [FW] ImageManip CSC improvements, New boards and power cycle fix --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 4c2035bbd..998740645 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 4c2035bbdf9dbe9000e55da111f68fda79504a52 +Subproject commit 998740645690867b2146dc44bbf87ef2743c7777 From 4d7b1eecc3ce1e0488a3a87f0406f669cfda4864 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Fri, 31 Mar 2023 19:12:16 +0300 Subject: [PATCH 252/385] FW: fix for UART0 / '/dev/ttyS0' init failure in Script node --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index f605e5888..e67e1c3ff 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit f605e5888754114e37545e2a517559c432d551ec +Subproject commit e67e1c3ff901ba58fa1a5c11ae63cafa6c54d9e8 From 6f91dbac74293f5a0b8a794f243b01a04ffc1e45 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 31 Mar 2023 21:32:15 +0300 Subject: [PATCH 253/385] Update FW with fix for USB devices after reboot --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index e67e1c3ff..8ed944b91 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit e67e1c3ff901ba58fa1a5c11ae63cafa6c54d9e8 +Subproject commit 8ed944b91a0d6b1d2773d6457b0ebaaeed18ae8b From 1f162f16ab184d361a776a45e40d1a026835a4f8 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Sat, 1 Apr 2023 15:02:46 +0300 Subject: [PATCH 254/385] Release v2.21.0.0 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 8ed944b91..f8c459eaa 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 8ed944b91a0d6b1d2773d6457b0ebaaeed18ae8b +Subproject commit f8c459eaa56a814974a62975926be871419c2d5b From 0bb766523fe05b2918b4d5d7f557ba653b69cf65 Mon Sep 17 00:00:00 2001 From: zrezke Date: Sat, 1 Apr 2023 16:55:57 +0200 Subject: [PATCH 255/385] add requirements --- utilities/requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utilities/requirements.txt b/utilities/requirements.txt index dfdbfd329..965875a35 100644 --- a/utilities/requirements.txt +++ b/utilities/requirements.txt @@ -1,2 +1,6 @@ PySimpleGUI==4.60.3 Pillow==9.3.0 +psutil==5.9.3 +pyqt5==5.15.9 +opencv-python==4.7.* +numpy==1.24.1 From d475353b882c7a93d22b400a2a3a3427dcb0dc20 Mon Sep 17 00:00:00 2001 From: zrezke Date: Sat, 1 Apr 2023 16:58:04 +0200 Subject: [PATCH 256/385] Clarify how to use cam_test in readme --- utilities/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/utilities/README.md b/utilities/README.md index 2209b1d0f..3b7259bd1 100644 --- a/utilities/README.md +++ b/utilities/README.md @@ -27,6 +27,13 @@ Optionally, append `--runtime-tmpdir [path or .]` to modify where the temporary ## Cam Test +Run: +```sh +python3 cam_test.py +``` +To start cam test with GUI. +Run cam_test.py with args to start cam test without GUI: + ### Bundled executable Requirements: ``` From e914e27603118d383b524efd31475e813cb12609 Mon Sep 17 00:00:00 2001 From: zrezke Date: Mon, 3 Apr 2023 14:26:29 +0200 Subject: [PATCH 257/385] update opencv, numpy, pyqt requirements to match luxonis/depthai --- utilities/requirements.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/utilities/requirements.txt b/utilities/requirements.txt index 965875a35..ade6e25c8 100644 --- a/utilities/requirements.txt +++ b/utilities/requirements.txt @@ -1,6 +1,7 @@ PySimpleGUI==4.60.3 Pillow==9.3.0 psutil==5.9.3 -pyqt5==5.15.9 -opencv-python==4.7.* -numpy==1.24.1 +numpy>=1.21.4 # For RPi Buster (last successful build) and macOS M1 (first build). But allow for higher versions, to support Python3.11 (not available in 1.21.4 yet) +opencv-contrib-python==4.5.5.62 # Last successful RPi build, also covers M1 with above pinned numpy (otherwise 4.6.0.62 would be required, but that has a bug with charuco boards). Python version not important, abi3 wheels +pyqt5>5,<5.15.6 ; platform_machine != "armv6l" and platform_machine != "armv7l" and platform_machine != "aarch64" and platform_machine != "arm64" +--extra-index-url https://artifacts.luxonis.com/artifactory/luxonis-python-snapshot-local/ From 2bd7899a7676c5db247884f773c86d9daaeff172 Mon Sep 17 00:00:00 2001 From: saching13 Date: Mon, 3 Apr 2023 13:16:08 -0700 Subject: [PATCH 258/385] added distortion model getter --- src/CalibrationHandlerBindings.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CalibrationHandlerBindings.cpp b/src/CalibrationHandlerBindings.cpp index ee7bb9e1b..734692c07 100644 --- a/src/CalibrationHandlerBindings.cpp +++ b/src/CalibrationHandlerBindings.cpp @@ -42,6 +42,7 @@ void CalibrationHandlerBindings::bind(pybind11::module& m, void* pCallstack){ .def("getFov", &CalibrationHandler::getFov, py::arg("cameraId"), py::arg("useSpec") = true, DOC(dai, CalibrationHandler, getFov)) .def("getLensPosition", &CalibrationHandler::getLensPosition, py::arg("cameraId"), DOC(dai, CalibrationHandler, getLensPosition)) + .def("getDistortionModel", &CalibrationHandler::getDistortionModel, py::arg("cameraId"), DOC(dai, CalibrationHandler, getDistortionModel)) .def("getCameraExtrinsics", &CalibrationHandler::getCameraExtrinsics, py::arg("srcCamera"), py::arg("dstCamera"), py::arg("useSpecTranslation") = false, DOC(dai, CalibrationHandler, getCameraExtrinsics)) From cad11b29849d86c64aba5fa4ff9261f1e634c98c Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 4 Apr 2023 15:41:27 +0300 Subject: [PATCH 259/385] Fix device destructor --- depthai-core | 2 +- src/device_bindings.cpp | 133 ---------------------------------------- src/device_bindings.hpp | 9 --- 3 files changed, 1 insertion(+), 143 deletions(-) delete mode 100644 src/device_bindings.cpp delete mode 100644 src/device_bindings.hpp diff --git a/depthai-core b/depthai-core index f8c459eaa..4b3c687dc 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit f8c459eaa56a814974a62975926be871419c2d5b +Subproject commit 4b3c687dc3058bdc3d55166276ee811d4076f4c3 diff --git a/src/device_bindings.cpp b/src/device_bindings.cpp deleted file mode 100644 index 50a855399..000000000 --- a/src/device_bindings.cpp +++ /dev/null @@ -1,133 +0,0 @@ -#include "device_bindings.hpp" - -//std -#include - -//depthai-core -#include "depthai/device.hpp" -//#include "depthai/host_capture_command.hpp" - -//depthai-shared -//#include "depthai-shared/metadata/capture_metadata.hpp" - -//project -#include "pybind11_common.hpp" - - - - -// Binding for HostDataPacket -namespace py = pybind11; - -void init_binding_device(pybind11::module& m){ - - using namespace dai; - - py::class_(m, "Device") - .def(py::init<>()) - .def(py::init()) - .def(py::init()) - .def( - "create_pipeline", - [](Device& device, py::dict config) - { - - - // str(dict) for string representation uses ['] , but JSON requires ["] - // fast & dirty solution: - std::string str = py::str(config); - boost::replace_all(str, "\'", "\""); - boost::replace_all(str, "None", "null"); - boost::replace_all(str, "True", "true"); - boost::replace_all(str, "False", "false"); - // TODO: make better json serialization - - return device.create_pipeline(str); - }, - "Function for pipeline creation", - py::arg("config") = py::dict() - ) - .def( - "get_available_streams", - &Device::get_available_streams, - "Returns available streams, that possible to retreive from the device." - ) - .def( - "request_jpeg", - &Device::request_jpeg, - "Function to request a still JPEG encoded image ('jpeg' stream must be enabled)" - ) - .def( - "request_af_trigger", - &Device::request_af_trigger, - "Function to request autofocus trigger" - ) - .def( - "request_af_mode", - &Device::request_af_mode, - "Function to request a certain autofocus mode (Check 'AutofocusMode.__members__')" - ) - .def( - "send_disparity_confidence_threshold", - &Device::send_disparity_confidence_threshold, - "Function to send disparity confidence threshold for SGBM" - ) - - .def( - "get_nn_to_depth_bbox_mapping", - &Device::get_nn_to_depth_bbox_mapping, - "Returns NN bounding-box to depth mapping as a dict of coords: off_x, off_y, max_w, max_h." - ) - - // calibration data bindings - .def( - "get_left_intrinsic", - &Device::get_left_intrinsic, - "Returns 3x3 matrix defining the intrinsic parameters of the left camera of the stereo setup." - ) - - .def( - "get_left_homography", - &Device::get_left_homography, - "Returns 3x3 matrix defining the homography to rectify the left camera of the stereo setup." - ) - - .def( - "get_right_intrinsic", - &Device::get_right_intrinsic, - "Returns 3x3 matrix defining the intrinsic parameters of the right camera of the stereo setup." - ) - - .def( - "get_right_homography", - &Device::get_right_homography, - "Returns 3x3 matrix defining the homography to rectify the right camera of the stereo setup." - ) - - .def( - "get_rotation", - &Device::get_rotation, - "Returns 3x3 matrix defining how much the right camera is rotated w.r.t left camera." - ) - - .def( - "get_translation", - &Device::get_translation, - "Returns a vector defining how much the right camera is translated w.r.t left camera." - ) - - - - ; - - - py::enum_(m, "AutofocusMode") - .value("AF_MODE_AUTO", CaptureMetadata::AutofocusMode::AF_MODE_AUTO) - .value("AF_MODE_MACRO", CaptureMetadata::AutofocusMode::AF_MODE_MACRO) - .value("AF_MODE_CONTINUOUS_VIDEO", CaptureMetadata::AutofocusMode::AF_MODE_CONTINUOUS_VIDEO) - .value("AF_MODE_CONTINUOUS_PICTURE", CaptureMetadata::AutofocusMode::AF_MODE_CONTINUOUS_PICTURE) - .value("AF_MODE_EDOF", CaptureMetadata::AutofocusMode::AF_MODE_EDOF) - ; - -} - diff --git a/src/device_bindings.hpp b/src/device_bindings.hpp deleted file mode 100644 index 78a7d43f0..000000000 --- a/src/device_bindings.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -//pybind11 -#include "pybind11_common.hpp" - -// depthai-api -#include "depthai/device.hpp" - -void init_binding_device(pybind11::module& m); \ No newline at end of file From a740e29958482a503968f35fee5b73c10c6d6b85 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 4 Apr 2023 16:24:50 +0300 Subject: [PATCH 260/385] Update core --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 4b3c687dc..3e4152a8e 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 4b3c687dc3058bdc3d55166276ee811d4076f4c3 +Subproject commit 3e4152a8e15989958031b37a7e4c314d14a3754b From e8c50bbdc8c962efdcd05dbb61d57bccbbffaa7c Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Tue, 4 Apr 2023 17:05:43 +0300 Subject: [PATCH 261/385] Revert to clang 14.0 pypi package --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d002b5c4c..030265f29 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -49,6 +49,7 @@ jobs: run: | python -m pip install --upgrade pip sudo apt install libusb-1.0-0-dev + python -m pip install clang==14.0 --force-reinstall python -m pip install -r docs/requirements_mkdoc.txt - name: Configure project run: cmake -S . -B build -DDEPTHAI_PYTHON_FORCE_DOCSTRINGS=ON -DDEPTHAI_PYTHON_DOCSTRINGS_OUTPUT="$PWD/docstrings/depthai_python_docstring.hpp" From 2a1d8a101401eacb0a578fcfde3bc8b664a31d97 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 5 Apr 2023 01:11:09 +0300 Subject: [PATCH 262/385] Update FW: fix spatial location calculator for 400p/480p resolution --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 3e4152a8e..b21dafae1 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 3e4152a8e15989958031b37a7e4c314d14a3754b +Subproject commit b21dafae1de6c48c9927f3d56b41c28d1ec10c1c From 96590c6a45dff736e579f4969e947e83b59ade8e Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 5 Apr 2023 01:23:13 +0300 Subject: [PATCH 263/385] Release v2.21.1.0 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index b21dafae1..0cd6c14ec 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit b21dafae1de6c48c9927f3d56b41c28d1ec10c1c +Subproject commit 0cd6c14ecdf205006b362edc8115b910531d5f08 From 7f25e77affe8ea145fb01424856e4fb59102bf4b Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 5 Apr 2023 14:37:05 +0200 Subject: [PATCH 264/385] Updated docs on pointcloud layering --- .../images/components/layered-pointcloud.png | Bin 88391 -> 0 bytes .../images/components/pointcloud_layering.jpg | Bin 0 -> 260123 bytes docs/source/components/nodes/stereo_depth.rst | 3 ++- .../tutorials/configuring-stereo-depth.rst | 2 +- docs/source/tutorials/low-latency.rst | 2 +- 5 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 docs/source/_static/images/components/layered-pointcloud.png create mode 100644 docs/source/_static/images/components/pointcloud_layering.jpg diff --git a/docs/source/_static/images/components/layered-pointcloud.png b/docs/source/_static/images/components/layered-pointcloud.png deleted file mode 100644 index 20ee291297b6dfae2fc7a11a54ebbd223862e8f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88391 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�c=Jz^|Fju?A55OVJ4b61G!y_r*(lKbLZ?| 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 diff --git a/docs/source/_static/images/components/pointcloud_layering.jpg b/docs/source/_static/images/components/pointcloud_layering.jpg new file mode 100644 index 0000000000000000000000000000000000000000..554816e8d484b4888c2c3525297f1ce623c539fc GIT binary patch literal 260123 zcmeFYWmsEXyDl0E6qn*oTPRSpxF<-lQi{75_u@`)T3iaWKyfWrG+1#h6fbVU9fAe| zVdwqcZ>_c8z0ba`bIz~*V};C2rj2LZV?1(?XN>tU_pk;aej~3W4?sgh1DK)y01t}* zSpfQ@M}K`$9}LtN6B`o~0|OHm3+pj9J}y2!9xfgp0U-%70pSxuJUn6w;wPkJ@CXeZ{SgNGU$sWv9f-OPz<7d5@{Cvd zF{!#a)^k@fzL2CmY$lnSE^>{DQ)d46ZlO516qHodG%r|K*2nq>{h>FR+mXlXd zRC=SSrLCi@r*B|kX=QC=YiIB7;pye= zReDba;kdebkEy`80y@swv}=&}wp?|}L_7d;3y$i*;0y8#%6PCfBmV9K0J}|Lg9rCc zDzi@&nqHTnk%AyUsy`@hN*Ttp@~{ND#??}h83il$^P0R|13x-gC27+(hj!rar8v+m zpOZoA!q9SX_2ZQQfZSx%kCMRSVueL(>+#pmKULh7 z6~z_A3ba;X>p^n3+|UWdNFJAj+&8+MAVU)xf8Rd<#&xR$gzvu!u97=wB#LCHaf=IF6uy1{NSkapA)dh@N)vb9p6l?U z(?0`I0TL3@b~F{Xy7`xaudly`eDNST%oUV~`Ij<~1^x?F3**Ne0tuzl5>sQ+J_GM7o$qFbVfHrB zI$H0gmbdCK9m{@Yy&8C`Gy3h6XkekS0D8ayBK$3}@^rFr=3+*F{cO87R?+gyP{(`B zCqUUmX~2_YzQ-ka|AYZ8M-Bn`{+J!|I zeb$<(e|TKf*c7SA{ob7}aj?jkijMs08Megx;AzvpJ6!#5ESE%NLBBdEBxsB6(r+Q5 z)kka7&bc)qOg_b5jNbC&Ysk^o^`2x?$Z7cLT0l+b(g%{^{am#DUYb7gGl9Yy?O|#O zLmi*C>Kg5`m%bFSS9t|%f8pB^f5%3kn$!MwcmLajI3~vqJ7BMUC>Hw}Pd^%iyFsq_ zy6Q_fd3?Skuk^NUy>DVjNqCvqOr%FeE5Okm7!ppW3Th2Cu*Q!b;)~V%zaajlR^X)2 z#@Eh$zD%?=O?0vGwgm6^|L1Pc%MUS5`C9}2t=C!tYLx$ryC5LLoEQFwS<0ZX4l6NuQ?he=)dil)GRx(oWAN&0FvpJ$Z4U>f{ zl>4|zl4?xbB&Uhy)HPCH{8Z|F+h=su<{MWVM9( z#y3)hn)|Hfk#I90CJ%77Nz51qPQ*n12N5`I! znV$()@eOcY!Wd`{Tvum?c%R}reTHV_Ht_dwkI%$_?Zgz_0`IpN^Il?FBLa z(R>^4B&2NkwXIuzH~2i1xs&cNHSyGEZC2bDA>woGzohRIWap7y+y2MwXWI6>=Al>e zmz`vJlF#z;-L(`;)WLvFO9EA)e}Yo0yH7_lD?ug2`@DhF%?+o0Ymm&N6-yOnuXDvG z+%y2B?Z-4Kx4d@>5>o+S1jxaJ;XcoEHGGAd6b{j=nYNrd%AmsFasT1WtyP2lC{&3n z7>#)i6)!CQVT>kC9koP#^Agg)*lfZKcE|22x*IfDNRxVKT!f?y)K zwdN_!NuA~6-s`mS&V1J|LZfHcIX+mn@}gY>vQqZGl>bn0|DDJO!21pc_%O60j{zS=AvL-7a6xwn!c7m#1`9gbk{k>F_8htecGK;d4R-eV|8JVZ>dkY z5sdx;5Cw)(Q)CJ3PFyJzFV0lM6l-Ap{{8ZM z#Rov$vvUT~&xwc3TI?NSIhj6BEi%wG`~Mwn|HJ(*f~-&G`)4VO_5ZbexPHjS* zQS)u$xk5F*ZnH$sgMU<%e#mEeFYhySWHcIcnxg_!I3g$r5ShG;laUnHoOSZhn!c3e zTv(Rd{*_dxWN#_1Po25RGrCeBfmXHy`bW4r;SU-J5DoNT3VrI0SEi@Zg{>hG(_m_C0+I;R}Ij>12rwn{GI7 zq$)mfINJf3j|OF4_0pmAgZ>}-VVu*)7zz}G^9p@qj1|x<0^4%NIXpL(bth{##7x9_ z=572-5x;qGaB#cH9$mxbAJ!5XjrrfM{~tZ~-{JAUBmX}fIk@qC_xV*};|d zk}1Sw4546f#}OrWI(ElV?ttxy=RbTvU>ykD-h#H-Zq(cEM00%F@(8c5%jX4w(gw0z z*S#J9XH)k*av|a|e|t4{nwOY!yGYmrz~ceHq>RVu&LoDTu16uGh|fRuyeN{)W+BIKHtDl8N87F_y>q)8pkn5y!1s}u?cS5%LGZ(y4qDO^0>n3dQyNU2v`LmOz}X$@ z$_%7Eop)z>1B(~Pe$jy$-np(a?Iau;j=B@R;=hi9Pl7F$FV9>b0FM|qROiR)C?))J zOyLe!Kj03h&F$2?K-xwaODlAhzz#LI66CnoeOE{No1y5(r(B3H^T$Igmu2O%+Eb+^ z*Ifo2_|%#z1DthTCv@TX2a=LV;wQywFH+Ej6r<9Q`%TzGlX&&-`N0S(ABo z=l7qAM%VPOGHmvPt&Dpt<*w_i&x_Emw@r6rA_R+Q8L0C@^5P$3kBUKlrYs!|C@@2 zp^7{7ynR2D{4y89o6RPKnta6DCXP=V%S~lejyC8ZiMyGqIFqehV# zt<8IHGeRQPcn&wl;C8X5vw)xY!dXQr_k2s1_%=ub*J;m!G-EY)?$}FJd>=@+DIh?p z46bU_Hr=KRRi%P+{~GHB&$~Qno~kO2=P07+iyy)#s`08pf6>)N4(t7f;t; zpOaEH-jxo34nPZx-;5ylUy;nqdP~cF&Cj-NsIxqF0^^Cp)p&>~9U^ z2qQ`5MM~Ki6z+6!nef};>!q^D1m&NYw?arkld-E0@USxdK5wf6gVlvMizMq~I&~R* zi=i8CZxvRas{cX|efe2@2b=_Q+S6QrVu7bZ8L}i@je$XDk=I zH%@lgmn<-DDWe_$b}EdWIdzavK)NBa1xOrcWKr&}$+M|WF`vn5n~;;oMit@Bp)%nE z-|8B@&NzNEURlD^X)#^!f4rAMKWbcl02~#4Xi<&uRB<;HlS{46EDc@joMfk~UZH-E zMJ2gghj#_6g{2=Uop!8WhkG(U_Q~L(u=OKLYr#LY7c(wugFHU&9;z&kR?IM~B#nGQ z>m_TJdSob;a8$HLLN#Z`kh0wMVZ6M?dH_(WgSv=l2CnOkz>Cw2cgBjxf&AZtW>yJ-EDmBX z9sqGAunIo7LZ1daL9wZ(zJ_nV=2hb&!DRns151?2w1=dIUha?jSpNEn zN6Dhifuk#$ucWC4sHw_VFz>$u$pUD{ZBADsEnsOsi|i>g8mqkn%MBI>T=B2P92-AK zeP=a;1}qJ|G<&xcA<3~hT>a=E&F8$r%LbW|hg8wOrXjztO}_daaz9Chlp4pHx;#V5SweKL{Ox*MCgX{RjF9hyxXmrluUQ>1E zxtWmA6)V{T;0Ns60|0&m)b}Evb*m(IIFuy%I=k@MZYv&r=kx(^Ffqj4HPV{OR;>wl zK=1{;hW~sK9z-$@ZdgERG^5P};4LHyoCNv(0MLKF;kx}>Dx+V*jMI&l8Cz|d)Vq30a0jbC-Vw$gTy-DEv zel^M$6ecR*HvHqbHMO)njmCBQIC)`3>Ky1*ZtfnEWtsHb5!~G|!<$nFL*9a6TFs_r zEut+PKc|AQeIPm$$DR&OqK5ApS9gq*JTV-oq#}l$6V2F?a3lf10rG}~aFZ9~o}w3s z#TU{0Bq2|)!6%2{J8gjLg{~Fqsr25C^R>TE$$Fm-nASjiOX|+n-%_{;@yBL)xoAq! zeIwsnVZ6^oKJ_VAo@}7!k#Q)O)bQmx+(f*^w)SAB157>wa2GJ6r2%Mu&jw+`TYC(a zSCUHi@(E-nWyvPEM!3nNoPKSouG!6hT-&W`CyFq6*8IsbeuVGUFUU9I!ArzFy9-@J7CsMIB9&$5TWvPPZ|E z3yr9_lQh)y$aNLEbS;_K2MRcdy7L|Y;cf?jqXSg?(U@7O8_&d@>h@(nomy}IF%^K& z_jXJf`{dpbolV`fkA25Jns-XnpJDRN6YN=dqm%Zd?Of^V3*odJ=fI=4SpYu`^f9&) zND|{p4&y1h%niZh8hDlE3x0l~RS0ip#9sDea4PFD(H-OgFhBs{iv}Nqb~=zEw!Kti zIS+vN3_;=~zQT_qp>h}`;fYj!uExT>Cp0C!s`UFS&*S@zMR^K!De6|0{r1AOx9ya? z_p()~2c(T=o;?7vSK-j2MVvihq?p}4&6C;5oOo_vW+og#Lx4!P==G}WUQl9@ac6@QgaM%@VAy34p)xRHJz=2$x{`TX-W$Ytx`jd+piWrYxTA175F5@v?_;mH9j|h_^wmVCQ>K)R ztxLSQIuTrZ+_966lmPW79JW^9pdLyff_>jie=k|+UvR8*U)xS(2NZ*WV(QC@XnvRZ zRQClj!h{N3JRR&d7r5e9a|C#w2pJ`gHDm&|q!&Zqu0BSJ2Z%Po%hz%Ue0VQ!7JFAo zxg-p~8~dbTKT`oc(x!lsC0%FzQhwvL3E-VU|gU5LOVxo-m-lpfn4*&+n4F%>_ ztoj6xz;wo=m!|_z%IJDS0vrkTN<+gtMN^uC6jMXPsIXu9?-7EHFP>{6`dQ?8kI{VS zLPiG=nm#x|=8)8+d>*dRjr=;5^4+D4#!p&*q z=qfygWZh2rda+}+%sK&3nT?NAVEZw`J#ZssTss?gYN-lTm6SkcM%lO0X{#-CWuJ)w zh93d-)(oo|lBd?}s?Gq{IzIsZqx~~G{@wls$;M#|3eA_ZF&z#rSVWI}3c7BMXPV3P zO?-3$X}T>>kJIxP`aA^>g-OU7>KC_qb(oS33Yd`v%M`W zA-Q`s2c)|c24&zM{Ra}n1FqbI-gTkrRGa`1+f#H{$(73L-5E+5qu>+wDc%nNi4GVP zDd493!(Z`QVFAc;{~bvb@V4y_82Z$d*7oWEMzS*?q*|hH(5gf$?LuQXNYFXYM3u9H z5x#NaEN(dta;e5&v?np%XP}=ZN5yBK6!mF~(8(E(&@s&rCe@c#?~{K;xD7wYa7|QN za#*iE{4d=9TNl(^@+`DMx6AVOL4?%}L{E%-Xf?-8-=^qHNeCD1@mRRcCM{z@Y8vL+ zm25kcOf*K6n5cI?!t>TaXm=mZT`r7u4aYBtqEIy3$s!^GL2wOpoHZ z(X#?4XkCMGZaNU`|L%J`(0?jKypk7@9mDRWOK`jBsduEuD#>0p=d6SSG;&&{zA*z5 z<}4~pDr%XH>fsmH;F&CQS080#b0wvx0L)EJo#3#mVnAtG9ExV6r|Dj80`QsfGV zum-)7<@l)lq={hE_r!$sJ{_fFe>eDl83OQc!3aj#`*I}3o!E?rZ<{DqjG&?T>PKRq z9RsnVqLyLXnu=UQ!w_B)pw)ZJ_g?{oJ9JJ1N91dj%2IG+ej_+U4u%!lrdn$MNaZ<) zl^y5t@$a!rvZ@gZpF!!G0!zolcO2UthnfFj@&yQ%lYeNxfAx_*%)OJ5Y_^BB+aMtM zz$12kbIQ=v*4 zS&z~r_krd&(c{Rz^w}GfozK7(FQnzDrz*p9{gkb0U&~kJD~sgM=}fXYCFMQEP4XjlX-!6Qi@>3UR5n3sdzR%B$K&dVes?n4evLyQ-$*J*$jZsqd&>3c_r7elf=pW#`4?@ zK*$B+upSb@x`o&SsL#=32{d|hCjBC3uZZAK-xvF`YldaO{?=`CZ-*b;Xulwtk#q?y z??AXI$_(go{x|pGKq2lWxcCX6^8sM5w}FLRL?n!8^dPYA95bvx1<;}6P;mYJKKDJ% z5iOE`EmRLbEH+C6kALTuW91lR4vwR!gM3o}!@-bFp>) zM;qtG1uq?gi{$K4v#suXDAWADtal$Tr<7yyYLM3bu07Rc)WPRR3;Trfwjp?I07a{8 zAV%mwK0cuAWJ#KF=&NJ&F+L+M7m$kur!?>5r0Ars^Ol&`d^ybDYqxcbps z!w?=ci;&Tq8ZqATy|AH?j@wRuVgmE8FF$w1lwd{pvJ0PLNvJlAk^LKmZ&`^8onxEZLA& zTEOQdykh5QTg5JnUi)#v)EM7jx{<(I-FqpnmiOt||nv293ngIY#{qP}FB8)q-{amSKq zjxBzi@?o7bofld|q;Q%J#+C#C3or3!zUOAf6R9SSLnvaUC`DO=CF-Q`{qn{#YHK5@kAZJ^&gz;ID&$| z{^i)+>iD8P;#}~5la9WQ_DVe)+OAmGJMtG!C;BoLdrqx>lg;jU^`k;7zu^G@rEWu> z?00M^+%XB~EZ^(i+5gN!kpDtZlj(H)$0^kCIo$dj(eWLXdSDn03`@P@V=KR2c%9(^ zbm}^r5*bB9%9rcQGUR=D3P~SHr(XP#u%r3w!~u8gJq?Ei@`?Z0aZou@0Nl;vdq4IE z++U8Wofj)F2=8-+kU^~ zzULp>5ZUDb{qN9oI9u_OQ2=f=ui{}*J^mn~eFKGV70uU86Ai!hF9}pErjBzv+$NQn z)pC>PpGVHneg7`M_33F+>}&|iVE)~JD1_DEe<2*^?mFC>8z^&n0Bn^YB2o4YCI9)a z7FEf7oueAJnX@T_yAw9H7Fgysy9%e0dn z-*cqappHwC4ehm$7l@?C;~SQvCipX|WmZmF`$?y#HI}gFYvm=~I}5FiJ6=*VlvKI{ zS^l&&IM^=kxU}ik&T4JvSD90ti$B-XZ&O~!b}CqKGWYaVrhAOm$-3*t@y7#UTX|&0 zCp0%rhGBu#gf4nL0Q%!6aj@#R5!+3znVXq>zz1 zMZU_!$A7RS_4^-ltgxP*gcigVA(xsg=cs(0se<3}0b(zaQRA}%X(j8iVxC25nOGk! zCy&(ntbgdYzOD&%{Sn?zWE5YLG1YGR31Xm=N{}71ULGhzRTzJ)6Efn>I$|wTz7-eF z;UvmThdvotCR6GXA=jYBqJVk)3QWo4Cwh4BdcMr6Ix4oEt6_Y!lF^MtEn z0$haGU6H9p+M3%*?YJ}3F4$li-)^8lmdzV4K&4H<7wZLZRYmX&*paz3!NNb?y>Tw0 zrXMi5l-RA(2?Vbb@XN!5Beo}2^k(1uIf`qu{+%jYkY_fLh`l9k<{HoOvF;Qzp=TA7 zh;TO}JZa!6hfh@SrFTpEhYz@H$fvwXOXsG-4nef==D>C8OJxos;=KV^Lv4gPpFvL= z2X4FcLhBc_UHb+|IFQ%KUenZMul?x^t5EWS0w-is!tnE<8RFB8sYAr>n`om!QT^j~ ziHSRh@9<2iKN8|q28(tx++lhR{7&&ke2RVO&oIbXv*NL-tJCZ8wCnDRE-~d^y4+Wm z4KO?=TT`hgGAX}_PTuJ1pyIShkg}O^Iyz2*8iNpu%o?RqG?LlYQ)gg8Q2;F19!wa( z3oo!(KOtJ)7qc#Mbu#y<#cXcTr(ov9$kgfplMJcx=ripqnAZn#a9_aseXFx{ zuc~00GEI{g#I6qj;1n3PP%V6O7yNqVi{fm1zdGf`(*- z=MQ0rtirqEExEfCP-Bv7gq}Cb2 z*%r{(;^(oa>sTR3>)&MUZciah=#@T(1x#HWDy&FbUl-^;o;%2nD!l`Bmn#)V#mlie zL+cscJMR^%X45C%+-7mS%8JcA!Q3~9%=31&d6C$VY-9VmbxSK*O2j1heVxteaZ$;; z_utG3MKLHh{o=)RT?&_lQao_ut~Q|?60tVbGe7;iOfjo~BJlE_73ztqN={FD107|< z;pG*L(odVu8bke_ZOr8BvA^Lq*nRE#$9I}hb2kCba`dyW0!Zou)j!Vv0mGjdX!_oI zlNKVsF(uj`c<>d~xTS;FI;L3bYEAJ|TItE)cv}!W<97B+<390D^OV7fAK`60;Yn3d zaf$cAW3C8)%$<|4WM=>%(|IrFp3^XP_;S5tfk72ZC~o~5rLr-Rs=t!1dgIrTTK-7R zA|mwoyV)_s8>16rqP#0l&TkDMn!+DVbA(KQ3yd=`Zh@=wB*@VeRx=mU&_Z9)(H86QZJlJ?w$^-PqK+xT&XUe7q3fgN6@S}TGW;T zn>z)x2EFvRk+Pur9F4ossQ*;J(cFMQ1todjC$j4*_kIoeY{q8UxJfcO(CO3FAr+@z z0~wnV9}E-^ozzmc2f;u9YO!yb9(P~|LVQn%*vAG+m?QT6x0Q>pJA0N}zt?Bsbxy;( z3;681R}HXXUD80W3PV@e{l>)lgL^Q2RFk$lTz8fxuxK^H^SAZ;ekuuZ=wM~R;`qCn ziCEZD0|E&)fajkvLiQ(&1`iDceBa#GCFUY!R7dO6yOY9AAc7vtKMV)x%sy^tzc!Ua z2Lx%?;6WVM%k~<3hSg&k8X%ZWCfFO~5%J-J8#W@6>;nrF3zQk&^B9VjNkhB2mdMZ& zL-jqahH?&v1HD7;7rb24Lkw83?l9hmD=ez-LH_5zCAr@Oi=`aA+ni6kN*lt;@Qd{r z_A|4&Lz3ef=XEENxrPni*=t*hM<)%GB2!-{li&QT3DrUxt!Iw<$NOMdT$G##2L^rs z8>ke-x*jc-6rs6BNLeg@+y#v~D9CDF^|PKhf=0lX2jW;>L?S2CL@1v>Q%O$>a!zCb zzWV~q4;Hq~k3LcOLZ&YF3Rk-@KbTSOX;JQ@Xtuw$?^6Fd&6XdG`(InBxpMRy?Ob#x zS{1QqT-y3cv#H%;^p`r{Pq;@dcRm3UN}ED=hS%Q?H5Z_EwBwSXFqRw`PQ1~Kb!nCNwA{c^t_3NBt(^?DRp$`~FrFBe z1SQI}$o-YG*D94X1F>Hp%XqtF()(^iU&J}|s}w4%#p$f7wPzSo)p?CQwMxR~C0}ah z7OH#2#3XK*alSgAsfHT8?5qhvavk`LS;C4_99~qs?*D`nB72z3$ybJXDr=L4kB@ue z$%D2VBsdh!~k2wMv{px~Yd0Nx!XT(byHD?KwVZFW9Hk@wxWPhdV-KB-+T zx{V^hEO628;i{H+}UyYHu&`^zS&2^;y<9%C&qm_M}fZIeby1Or#r#*tw+!29vkZm@;3Uoz1k0D{256l zBahSbBx6WkcW!3ya4h#IKtFem*>%|`Q$9^jP01k@Bav`h)Z%_dg#OhZ?_SOh;x>NY zVz|3$f|5SDlTF7&9p_N25V|uv-ecp=utcA(r`ZbYsNs@?6BIRJD_-wr$l8YPAG6ETVeXx!(c7zt(Xt!m7jIds>9o`E&A*Y*?p0QoNV zp)gxgRw!N&On&Mt^c_V67FWn{y8Gd8rk-mtm2ZW@G`+W@iym>?O%ScXPGV&z056l> z7+uN%DlFFVCE~wT#%G()N*Du;!N!2%8z(W4ARM@`%lbE~iZ;pW{S2p`Ju%Mo7qRIw zSZP#YBp7|L9~eCp3qp5G0eKZclcr>q8+9!WGe?)7s*Xq$J?^ih zDjk-VSL~Ci29Nsglc_lF^0~{boi<*a;QHQ-6%Sp5g%eF)SMqfjaleL zOofJdPSa>e^i-bO5p9;b`W5_>Mc`@VMpqCAyz;w>z2E`W)>H#Mu4?1Zb6I?~ln;j` zj>!Y*no*;MA`8;LVb^zLJbl+Rg~V`il0=iwQ%fwZT9OQpaXMcpVwhE} z8Di}?aqkt*RF~L&F_g)4opTcpl&+p&Bo7)yV#ELB?X5hWX!CX`@_}f?i^mKeSE+CG zpfjIKaAJhq4L$m?luJ4|>b|y00yYED2fTzuW;*4|tP-~gyO}vGoBYwi7M2{Ad2d$R z8bO6`i5|R}6k^z|^yY9Vn-(U#{`mnw#@)uAyhzKTf-Vo4d=@rlr+!!4R}6@K84w!` z3U{+lOL4jK*H9F7Z_WPsGFu`_4&~_%Oi@41ONO?*Cd1^>h1(y@b=c!kYcJM>Jwg6u zR+rd2Z|bU9VeHZc{#Spe6KBbZ-!wnpi3KCxiIMZnxcvrDK7pPN!ZnAhWVuvI zE^|c1?8z!R^Z<}7i&JbKte_Q?FaGB9tU1zBrdA#|U2}G;a|1#?qd&M%Xn(DKtk}hV zW`O~_od(jM1_&ESHo!A~doPWV1it~y?VJgAFr;%eWKBY|sVGMz-J(S$(2hZ3Mc_8V z>nz#M-7)Hu2*&-Dlvc8fq>ef!4zIUdI`fv2n=8yzcb z*PtZaqa)0Z=Z-lJnX>M`_{v1Ia3x?C*9(16;;w`t*FEWT-O(<@Nt0Y~%@91SfQ|MNp;6TG1-Rb=Cuz!UR zMPfl?VKBiF8tb3*_cvOGUvl2Y)@F5xx_r(;Kg0jve{NKj>Ag`5TZ8!Du$&(KoZ{i4 z=!=4T6E^oR!KM+jvM>d?bUhd(qWOyJ+Cv-h(ariY$-lxk)u$oj)0l;K&6^t)TFRj zWbimn@YgMI+1zJjS8DJ0R4=N^yS*9c$J@9hXg)VK`t^>UaZm+Z#Q-8iZ$cAsIz?Gm zMZ{L0NGL$}+QcObbxj>Q*uRqj6azMEjyhQxO*n>FAhubBHK~$4mRdh?6}_6`97>H0 zhd7Lf=%R#4@F~~)&KIW>RQ)FV56hTCVrw6#yVD(0A52Y?W=%O;d@ZzN_6j)Dk5`54 zMLTj9za6Y*winfQDq-jiTe?*XXPM$w5-q9E(+=Hf6kV@SE=fq``m+41;JT*o+>xnl z5Ybc36teG^0ZFSfsJ$us0b%<2h=lx{n;5uOcpk5Gt5wUpR)>GVUa+Tbn8PRYhJ=jb z#^21dr2MfpQ^ZB?TWv?HrZMRx6|H119I7Q%6EVWCfm(xP~awbBmjf`>vCK_w?r%zf1?3L8+)~s82@ZgrqN-hD;OturX z6II?eq5PhiZH&m?oi#8+rQdQU6`9;-?(%k zs;c2@U20*E^|eQ1G@U8+NgmVC<8rEpa>$fNSvt}oW?y>JwFs3C+W6vg3!9}bkGihT zUREnkp3th*M0-c%asoH3Ti$PI?9W)0zu98M(>KxIJGz?nN?rXxxELMlQF3rgbhYXF z>|4H;=)z-ZU_+&xhg+42ZjIOEfMhY8dsekM+C@mo04UQ47_0OkYgM6D!R3vvk_-y`w3?$p_Ge(Q+A3?NuA)&me(V%-o)F_tw-zVHeD>S zF8J{T?O9N7=xTrbU!K2H$I z?m>c{kG-9N_gZXIi(FRVF~V;XV$UZl_r;PIx<`7l2Z{EKJ&f~5{W9t!4-9JOx__1M zHQmUI`Oi?q6e(5gKT{gq*8lnuipA-du%q4K>&vh5a%o`UCwiwJYr6gQ+xQosq1c+& zxR53)*43u<>;~qDB9Xo3c{>D`)30!B{&3?i`Q;G+LmOf6J+CoQxXG;2lxGg+XYp;{M|kGV0Grt3iNMt7FLj*Sc#1iCKW_z& zUXuA~A}1J0tT*9}r+!C@QD<*LRTFZEH%$VJ%Pg};+Ja=-?%PiQ!L}kpm}WL$;g*~5}KOL?;VmYm%~=RK2KHqfJ5gdrasc$ z-?x)+r|L^qGq%q4PU|!M{;V6Z!jxd9=LQk^P^-(QOPO5P_G5Wa$P&otkLlN7Osse9 zpR?aoQ*Wp9)5iy1XvqMrcjm#}&*r^q$Dg%V26%_<8x9AwP^OPCWS96{5;y`>n*oh0QKk{}vy>Ntn* z^guhxxA-*D4Vk4(yWkvh4Lfe067A+MX%Zo`7vyCYEC}@$!PrfxoM^@Rc|a!+Y?R%g zOCF!T_BowKp?CSQgYPf#H>Y9sjF(9gHeuPnO>@DJ*Xiv%8PF9el|8>R`pJGqu%2F`{CNu z>)VJ@OWOQsCH_nHhNRO!6g#qwG1P3~DaTeRpj+%}g$WPuwm-#MZ$CH0y18~Gp@st) zhyYH~JghCoarlyzvq7D4^=&HWMTP!SGVm=M718plnMpYwtU_0c(B zPIv44VKRqD{U=C2;gzF>yT@HQA3_DpWz2jQ?p)P-F(>i?y!IktjbrMwrb5)ay6vB% zz`&OU{aZ{^lRqB-S9G%}O85B^cvc^}EK)SUY)PZnkW?Y0c+{4OE>}+S2=1nb)g?pf zGICT1YBSmz;v%kOuOA`DV`YxI$4$5aeYpV{iSUqIbjgK_oNY#6LyQ&$V9)Zr>NoX227 zV41NSVo+zFcD-OVK~oy-Ail@-ZaAE`{N-7UB{$GK)HQ{*Z;_GGN8u_BHq*3 z#GB@>@T79Q4*rtqZG>&_284&7ke zj+&O)Do=X6Za#&IzJkDV&E$1#24%^KDvJ-g#ZnUZ#6aAv_nXZLK#YuMEwAVD2rX=b@l_tRE6!fu@i|1{l zpI}yvZ>^DF+mF{t0yF^*&9RG8b}|7=8`Lyf;m$YeWLq5mBLfLYTuj*6{=$mp#%1_0 zP0bVAG^!!$6P)S_)O4T=5t)xL6qr!$6C*WI+z{5Y@V3q;L2rg;3}K_4qZE3ptoJnX zPxr5G$KW>#gEvvquqD$U;DJm6p*iK#TdC&}+C2^i?~k6kUspx=*+e3leB7aey|3(6 z$?H!dSBVcjDtUUcpEje2zlHiAo~MBzRY+aa)DXp)L9K$j3%_p-1*a;ZZkf9D{L`>( z$cy{D4(IBaM&PSS?l!LV?bG&@avy)CKT~fYQ-@VLSS_{D-jDPv=f5~7#;ZDq7QjVO zUBm!S$z2#?jcZdduhL&}P@ALI)SZl67Giu>=>0^mk9Ig&H14z08TCvjfm0VU>m%!9ce$*<@HnW!k76X3=5IX!(xe8$L}eyDmxlvF_?eC0z&} zBA=-~Q{ws#bkqte$M$OIa3znbD%qmxje3kPM6jXzxua_=g(&ka4W0R~iUvx7<_4Qa zHM&KC422{LAMb*)=D*hEZ(fFk`T+CejTS>9O8pv zRg-nA6EOnj{XVO9$Pt$KiwbNy(`rSUsyIJ^d;!ntIW20)uI8DarHX{=XrqriV}x8h zZ~e)7^)XpwJw|gPO<|D8)j?=Q(u>#^wJIC;jG*HsOP>AqXZ4m8VBt8B*_MGVTSNdc z;{d^k@qKRTc*H613dxm3XqV-Q?Znn|2?A}BwY3JW=YN2-$)P!zP7E9ULm$u<9q^M~ zkA=X8mlu#uLlHrj1?3*sr7UR|-sUR??N;TxnOcG7i;~_1MuX)5QFvwPQ;el$X*=}z z!*r4oIylLJ#TzF{i}I5S`LC*SnaUD?H25<3DP_2zKG zyMohFGgqBnr_b4KMCUiU3a3{>DSb1s1so(pOpMY$g26%t+uEVsCB<$7H{dtNLF6IZ z_U`06*RJK@RkO2~+)xDhXu$i~6gjvIl84`*Q@@W;dZI*`L49F<~7jO`Vk}Jk(M*J+I6`5Lffu_+mK-JXqFp4$)+ALc}BXInKjMC!z{ z;7Km++G@LChhnqD)@jjfRH)1}K8hRzP@6X(KboxxlT2L;O-?LU4c)fHiOh-Y`0suG z8W)-R+wur9bt$8N-Jk2O+?avJR||+8r^M14rekC!Tkh5fsI%yLZ8WVN-v!!D^pk({7UI*&QNOq>od`2hlucF` zX{H*pZ#Y-@Hh+$8;e}3gKGTK+sZUTc^uWe=rZgw)_Rck@>LqMR0my{kwZz7e75d?S zF!k0!ZTw-|FBEH$QYdbrghDCqQk>$&-HW@syGxMb?hZvla3{E1(c)SlDbk?7lV_gy zob&#%Gsz~C-DETSz2$RVw_oA)N@;dES1~~pPDr+K$L+}1J9X*nu?l*tXIm&!E#$lN zSb>aJHVNzQ(svKwcGqu=6KBh^@j5-y&UP)JIoqM|4T^`b=d?@W+l4rTPh9&f_%!)K z|4IjgxK>)p^R?Ak$x>JTzS0p5>*C<8p06v{aUR=)&R6TlC-OHQ1tKe$=y1fWW!G>4 z^-5_D`Q=sveYU)&2MWWggO|g*zAL}HM^dOG<6b%_k%v`r_hPHJqa>FpXv-S67ERLJ zuvee)#)PEvALEpk-AUZ#8e$WyK@Eus}dMtguH@6TUif~NE% zk@x8r?9&V`WVK*r&Tw;0nzwsLi3t1kEWMM~R!DdrY6vE?gEa4xZdZjcugq$5YK!cT z0J-N%wkH$VtPw1uvSHgMZHvr2gE(Qc6_NJ_$W;?gAZe66AV?Y3;9AIl*t$EP#d;F% z?1D=ovVQBzt)v#sroB>tXC1gl`J(C$e3YP+a~wTtG=(6L>*X7+b0M}q*mwZ9rD?_H zNKJrTXR7_!PRGZ1!-RJ&oat}9e|qA&w_HN95x`x%OlYE_?0f+6K2LY&+=bP3>sAne z^{Z<~s=VVXOuY=*lx#EM;}5xH=IKmsxl14}h(;uW8&>_o<+JJIMQybz-p)7eN<4Bk z9jV?Gmc%_^zFNe4!&kopKn-|}aNm2RHojCc7-{qC0hgto@N?ZqE8BkO+Kv62{8KND z-deygT+bVT(@Pd00=rDvSA519reWz>V$d^RxL{qA0Ufc_$42;|unk7KA!9yv3FYT< zVa7f7R{@d`_)D~7#W?`qLs8MdK9|zBB#=+uScVm2wIlpBg9KM{waVG8-JFZKgL&%=(0e2ZN*+FXjiy#7oY0WSDhrYY={QfT6Z)bPS z27ms3ee?=z-W{{LT1qBz{v5w~lr(wu$9*u(@TnP*|NN!UGXM+DmkFP}lbbggb}4-C z^fPCzC`Z>KW#TgKqwc5-dIr=R_mwMDlqBu03$9(16JT4ccaoEoP4%F3ST`t}_9n4r zY#^YgD~lji9xu6_vJ-E;aR7jUgspvEov9GmKQbt4d6G_9o@xqoQrZTb&Ua#&x2u)F)t7ZIVyeO6?z z)glKc9XnNzGA75GUMw@D0hBIigIjR22)W)^zy+71&k@M*+i6+901b+0S-Ii(v$cI>jCXic?|Ja+*KE*-n2A$o965E)(4MgH&{Rwn zDv+^fS>kR=X$m>Lx>kDBYhV>!5896N=y)&clUCPqw^$MWpT{cw341)#)U%^c5az<) zulMhViiGLH1c1c=gO~NryYM~ywb{V}CK|%1Sv9x^%w+ux&0G=t-al^aj7ztIlv>o# zJYm%?EnH1e=F{VN%Z}Zrl1>?*5VUSavL+$bK5@07ciCeK{^wo+wF|v!R`5y5>pe8h zU@SFoP_RMsv)%hv2qD`qO%&=5%Zv3ABzTh#7CcDQmx!=NOPl%JUB};qMiai!%1hYP znoM{9jk0N+7Hy@)o5YINBImlYA}Zxl2AUyEB50Uk-gvI=t4Pnzm=ijOpA0j|bCu4Z zkxV#=llTus*M&PgDF{4s z;Cit26$Jt`)cILo0g|E5(y(KfaYyIOYCvO$b}55q`cr-=FSA)}CZaLX{* zcH5Uj3UBf2g1BbFc!0?LT7zUc^vnO`Y3sKzPcgey8~7WbVaoP zrD!z)u`aj%#R^0yhx}4+Nt3pGYYPNcz?kNyRkH5HcR`-*X}&nEeBKr?HzEI33KK7# zXVYEHyaRvi^L#ld%`|)V*9Xt~>E)qmN1Uj{a16Elj^jS#^v3c@cZRNaU6$x$U6!PteA-}V`q}k_SD(j*&FZ4ohR;V3bdjAJ)wl8LWs{Lmmu(* zV3E;;PG(%sR~!B2mJy<_+1~z0A(1t#m0!BcxMl>0SFwjuEj(fgK zm?+IV^OEyPIQ`+`ms8!9I8iC8Hz8mv_4LPx+fcguV)S!RxCFHy%~jy|{Pf|b51&4= zt}Ib55eM_jz0-cm;(sWs>WG;@1xjZ&=zN2zs*M;moWT2<~bP` zE)M(7M1hHmO!Vx+vJ!%79QEfd2yeW`*muxUEreq-4WV5R%D)rnouAr}`Qx{oUSlh7rxAyg zzMT3w=o{4x00hp4-INSU;>zkvcs9QNAJ8TZc@m$+kq5^_?!dq>N0h!u6vD)wf2@rB zUynUSF|9Rrt%;?Hbn<+MGcJM2U#M$}Qe7A0CJOX;$%pt+JcvsnZtb@+fZ{5;2q{3H z-#SGtxuV317sJ`^2SdLC*Ewr|{_Pg-`U$K${V5HbyGrgZU^uyy_O0NI{Dq-xghQ~; z)T0E_RFm+{#7`95gd~ZZBS?URPsZ6C!y(I6hU#0Q9rvp_z_$b%`v#nJ!=sFb5z1#r zHr`6JVg+U*E5^&?5G*IbQN!cB6{F{W8y~-SvvDDdHRqlUE_Tw5Yye-gspF<2~!b zE0zhqr|A$*-HGyPO%`u2fk%3SUnGA=ms~y%a-_TUV~7mQ*x`w}>kp#f{QV?&y4a(e zV${rUEgUe!c(0SsI`f0yZQli>2iZ8Jz>O!&YuhxZ9^F?Mavg*hUbz-tPYaSdH(mBy zm6ZU#B*}=tmEJpPAAj+^Z?6vM88i8;tgI}SFMY7XNhX|!ua6R&O&*OZehg4vn%?N- z2Ue0j+bTrupyioUGm*$hf70@Tp!`E|7mxX6L6s1?+PG4b9d|)?JFS-zI)hgE$WWa9 z!!*V$c)H$BR;SAzK<<yM}iuUQpjyr9|78g>O6KXL+~1j# zQY>3NA^iss?oEl2Q0XN_x|EQKSIv*`)f0Q>VG6NyM{i7*MjVIaq?!qrUk?;#49z5G znt!Fu5Gy7?$@F;ZiKdh!K_KM@z>vVWR@9ELYwo~dym#SGgRW&on4rp1mDoxbAl#<~ zF$r7v)ApNrMBmGRI&n%IX#y*I-Qbz-=(rcFFy8d)Qbd5dW;XJ&U=&(FoKI;Rt=E+2 zcoAO(`b#{n&tFOZNYhIa+!Jp!b9D!eG61Y|k0uMOEB%L(z1jjz5|be+Z0-wf?Tag5 zZiZFHI9M%vP$GGWjoezY#*eMncPIK&F%@#zy7*kLXR#2z`@9!`HvYja?Blz*Ri`Uc z_z}dvdc$_L(Qy}mGZKhbGl5q|F#cr=%0K?G_5uS_zJF7q5g#tHIuwi-z&h91Ex6k? zOL@}E+S{JGw+dYZ7lYdu)d=|#9T@Jz8EQX$C>L|}A%+Q0XfBZ)pKdmAyZKY!)fb%x z;UlCTB@k|-yN8gWKpmC%p>Wm3;$_A@YpY&)pmnMqppebuu@b;9oPeLxOEI>B)J}9W ze-h+8yG!7_5TdvNMHjwn6_NaH1e5XsNWh%KZ!d(-9`$kuP3(-+(v;eA)nh;8{Po>4 zTF{Raj5##wq$1eVeVOfK4|RH>L6oY)bcXY}CiUPSdoxj2ZnHk5E(8loij1@`0_pgi zLOQQdc9QS}`mAzn)D16qrcB~<8q!OjoDi)LF`Vu%-mb1?h%h85df~Ky(ufoZTrO+j zIvyb*W1f%{Hd=Hlj?w5RKPzWYFj3Ru%X@0)|4@|Quht{weg24Rb<-kpB>$ zeR+cvf{h;Wqy|hKmRZwh&>zOmY8cSaB7z$c%@4nak>^Ujs{&A|vf&G2Smh1;W^A}= zct)xppdYy(nDYgZdF1=6M)vKS@9}AWS6sWP*V(jlng#np*Z&PsiyjYo^^rWOIPYHJ zOz+&6L$u?(^TDs5Bx-nD=kGu1-kWrr)p_w9;x=wnJog0z7C$M^y-*->hxmsy=fF&! zrx#AcDx_J3>lp6M(%Jxz+gI(435Dd{j{%9-YQ~%?gzKZslQBpoX;nHyCB~R#8C-qg2pIMn((jHnZw(zZ z$PL@pWstQ(YY?f{m%qkXG%O9a(uVsXRG|1fxj56ZnlIaP^-eUS3Yi-A28!{lUh8}A z=wG=VlDudwEZdT9uf#~Z+4l`h*81*h;fx6vm-*l=StLzmv3waIybidFlk$JXQ)uC3 zCHV$hkx&`+$`I>`$QK+R{2u9ivvS)JAn#mkFI1mjKQdAdZXwH6nsag)cc!v&{W#VB z#Xa0O5C;4fw|^(~Tv^fvk;^ZEH4WQf$}Q6aD<8pXpjO+4vTOe@6#>;gU3xvz(mg;3 zrb!;Rr8!gtVtrGqAD11QEhF*RkKY;i0#?5i1B*0&82c*=gfI#|*)L=y?CJlwd~qN; z5)&5!D3mQb9Y$yK*#9FH_%~3)veqD!o3AQc`-Hbac+L&D&5FgEk4L6?Mc5C3J0sYp zs4%=R?$T^IDYtgO2KEb*-h4k4gTy`J9^HbdEUXMaEydkBzk79(6oYh*lQ8tmG)oM&0zG z+NS=N^~cVaPa0v4Mc>W-={s=!Qv?~0`LM=2Eur2*UR02ARipHCRbw%dYaXf6)fey& z6vgo&#d`c%LV(nEVqa4#mV-1~6P{0Lv+xl(GQstV%h7AH29<4_f%aA;S1<0mUI0E( za0{g$$j>r$Q@#sl6K>4D4^`AFYLV*bKF@74-BjdcLVgyVPPKhzn*QlOcm0aTEK^_L zx4>g;1<>%0PPAR}I87~U8R$r)$w29)gabHx&{1cBX7N0~v55AdQyCzVNC&&YEyZIN zP8@xdmOUxGZv12$t3FEn=iy_su7QMIWsE9=-!U5ciziU1$|T_}_!B)Xu*nCeG))(nXt;|7Tqxw4Z<&i^6EY#r{wK1K+Dm^8BNA zt}Q6SV`!qlH#aFK~}B%|2G(nGpY!SD9OZ3{l8J{YLvtB zia1XWmp67zY{6YM< zrCy8u=(nRgeh;KS7u>vLv(!u!gO0cKC`2DO?(w43hbTVTiNe46=a}XgFwxty(#xm>3*NnXp@8(*21wH@`m>DF;JSZ% zW3ufygc18A#Qhrg7{D43(B`f0Upn$Z+r3@7K*%NVKa_{%lOO?b2H;ODeZD1^ElE8Y z!Oq3dD=zu1X#80!Q`f(%<;tbi@){ZAdFmC#TGO6iSAq$<4FXGzPNIi9TTZ=GgFa4Vp$B)t}SEfslt@~ZVzva+*RiSoH`p)c-EaNzawpYip_8ds{tIQFTO|FuWigx zRuH_rmrsR~2KjGCcDAfNC>WMXygyIkydw_L6r_dss1TpX zlnZ&e892Gj zKbs;az0h~prK5HbAZUtT6deYM=UrY>UzO~I{Xs5A%MDYh<1S|<%%RP~j@QKDEH;#p z*xhmWXODMbaQ?x`3&o+|_@1`iV&%3pO6B1OG7DyyN+*9>m_Km=HyT)m_YauqOoydPaIWSWN?8-fcM#ztCr@EJMM>xu@&C_#cOZ98#tJP9;8; z;Ks;(4%(rNqUh{fKOfn~HZo9#jm9kyRIk~tlEP%PO4`~}(wsBNbdFTAekQc7P-9FJ=-8ogySAHqh1W9Ekq!%6*h$1B2QF%$TUO3 zi%Udx9scX}4^89pYWH^m4iNZ7Tv?su-Ldn5C03rSUO zLMg&S{qB!t%THo^Ot%+6%_Y|I-c+;B$nHc(`l8;W8R$n}+VssK5u~G72>7{5^n=gh zXaPm*BSYb?508-IR5T(ZN*t2R%1)46l-({bGEOi`qSWOrL*y+j#}p zaFW7meYvjr=TiXgu77h%JcR-@Rg{G(9O+n1mo}U_dCw9yjvQ3Z^8CE`DQ!9 z_HRq8%LwQR9L$+90xMaDU#;!FA;UUq+z|`=4}~CrAL;kx2Tm+N+FHFn_mMeVpk~du zzF3_v=3DfY7Qe<}JA@w5hIqek>)sePq3YJyF^Lwk$~GsB^pq}e1`apYOnBOF)Qjtn z;#LbyaD+-?d{C_BEVVwOZH)iQ94CAP8sfiFD^#YFN8~xe2aZy6aOHm2y{A*3sIOAy02hG>rC% zki)G!1ba9wYJGV}|9AZDmy4iD)g=n%a-9RMzX%_nqLqP6@`MqWt|y&DGAe+Nv_TD_ufuV) z#hr!-gpsNv+E&*qvg=RVoGZr`wnw~sc?sIBWMr|mn$^W6fKI5#E;ZY<2*_R`Nr1?F z0BNv4)e8;mX2!8ZE}6qTnO}h1r8tXSgzUCTSPKR*2;Y4H?eoUTJ@LC%$y*Xnw9`)% zaiXL33-60YgNzmEOq1m1(&Z`r*?bsRy3)%Tt~{*YT4EF@dlCx`uSk<`r4d{TXgOFE zPPBd)c8yFGpDAUXwzkVLP^#M4DjytH4UrHKAU_GFn1t;O@in}MwIOOa!m}PkM0Rl)w@z%+ zuXKQUuk3T+eQMZsoFv#_m%1`SXW$#olE^>JjQsadH%;qwrnrMeufVKOplKKxSJ>NN zu~lB`uCFb%-^ieXAH>Izy5bH?Ru)^bb2m)k<@QoNmLEi;S7farZYXMzGhfm)r#(qD zblE`e?{(YY6kp`~Fba!+NuvL*pIqaPoHaDqR^(Za-j=r{G+nwI$)r?np6i$AN7z_( zv}b<^bx-{Br=~PzGv$8eZLa-Ytlq%~b1hmze)k0Y>kFgfkX=alaE3QwS!R>C@R5P{ z=Lwz5WH$~8^7Hj_<5l_x#sRUnEFN2YDsNOi&faxyJ3l%$q2fk3SZZ0Kto@OE|t=rjyF0vL=cY9jvO7+$=?`+0Y^;|K zJe9tv7ElXj?3zL3ztt0|6aIBi^6B5-OuM(H9N8vXxMf(prL1-4wg_djDrE2cIQbps zLsM`I+Mo2lLdafTIq+srtchFL9CWbGVqv2W?jH&KYvh9Uf*a0z0dSHWNb^=ESVz~w zJ5^iMEbM(FhTH3Z)GZ2k5M~~Jz;K4&4?oBnkCKoW_Ijs4`{RdA`tPscVK{&+4>Ia! z_W(7x;iixG;~Sn*d%R(ZiuQ;YV^srMP-gmP6hW%P=vDhcZ!V;&vwpX0F2lBW`SBOq zhc9LEfWTgjNC3audR2e|BD8Yb_Q5!RH}{lqdrLk;Zr?^X4~10N0zdb<3N3r#iPIq& zs1UhsX>uEZ&$3T%O_b3~&R(m;Tq5fT`KQp+73L<|mv01FDSnv?5#PA50J%IWOK8uc zywK!nus1X`v_m>VS@#2!$0Qhw`}fba4+AmHly<7sw~mseK3_2KXdXtdG%+-F|CA-% zXjRxGwb(zQgkKoF_W`qN(diy~YV~7^`~1NtF08h140M>MbZ^}k6ZM^?t`V;+-&{BN z^*grMmn|DBb;nXCkLtzz>9*3Ia{9xs4zUZ0Jb7FesBpz@Vk*inKuk@-$Ui6NAQ2nV z6bN;BPaP|Lsec5}M(`N$>^G~os}?0siWMR!wov1A=_`1=U69?Cdz@w~vLQfZi1T?= zB?o82erl81iBjjsXhU$iekam%)SVnz%E8zq@6Kaz@tK+9;?`>^0)1`(R07MTx@tI% ztM-YUL@UcrdOwA0@ZGr?@lOB!O8hHK;b7@%_gG z)UDXFVePq^Glj|T>Rod^?yh)wtUP~N)Ebc~kE*WSoqjIY`X)pfZ+NvRV?8!vNjPem4zQQeb=x;xKzm1^47oxIXrKN@Mgo=?F5eT8;GRdaf3Mvs znNky}`YHk6E|6$ReGQMk?Y&KTNgTO^G?6+AqYeE{gWLWn$<2Q_XU?v?kiQ&C~SSk92|=<(4P-Fh)O7<=ctGuw&9F(dl3{j9b2 zLqT8Z6hz3ewLFANrnrYYDcEo&uBMIrXVI&=OM zMbcUrCsp;Rs{9kP;45+lO-{p*FUMcprA##&4uy|hE;J;24II}8+@~yk?4%Q3=|-N1 z6+K`2Kf0>xF7&Y*<2wo7o~!ZC2%!`?l9Rt^aYOaON3FgX(&!5QCCwcFZeHX^naHn~~ahqyRtPuz08>qy4RmCzsh!c|zk7-_<)Ct*4V!&(XX{H^keE&FV07%ta8EvG8nq0aHn$e;0W1CyKZ5`y2Ed%uZWR z!7~qG=G}{fW?LN!t*&kj6>nPKee!oBU~kSF7E~={Ip(r}pck^hdU11rgFToPjjAda zQV;^@#^iSq6ucUpr9of9OI&{7TNbsM`Hd{EoxfQ1m?$pqclcXdQ5L)H94}J`-`PDT zG(K+=o+BAKM8IBdx~LpPm$Qj&|Tg zu8?V_vD23SP#Tr{R_np9jcG65kCk(wth;fJP5V2^-OJ6pddqb}-h52#4!Mv`G?MaM z7pHyED&19kZ)f(PWlmhmQk=Epq@ymz))(m}N}bYhqr|DNyM@thk#0sUAu+15kiU0D zrf1Vj-q#H2AefqyPp!YUw!JzZaaT}Gy=pqqs92UJ?wOr8*?2F#y1MVdz5XN!=T8jy zQg^~fqODr4|4fa?DRSF+k@6y_NaSiAHL#z440-F z@+-*~TKyn#@&+CLo45Lr2_ZY&ONUT|M=KzLYe8=irK*KPZ7Vquq;6|;Ukr^IXX&vG z*{@Ipu!MDvC)hX=rEW>tYz3&Shg2erKP?7$+H8M@9X0LrU|K05|F-aF z{an5q95L(8qYzWi1D6pTx7I>QMZK*+!s(r>YOv|PgdnkiFL0e`j|&y)?sO9-7xhqHjo5FeLqs{nRDn4kubwqL4dCB~T2KEQB|H2*#J{YkMyA%xBy6=h zBtP=pJ**~rWLZRM-s{nDy3EwQrma?$K!1nlpZr|@BnI=sGZ?u0x0>S|Lp#SB0H{09 zA&n?$U%EJIZHj_gwk~a`Lqyfb(d5bhj^TPIDKlrn4?$54VEiPtQx2P)=;5we0>p%i zCuM9|A`isAOQo5f6AP$jTlroR*T=#zTjz2$B*3mWYIUCxv~Xr~QI7mw!Vul=0y5|` zqse|Ww(Q$qkj+0lBbt_dXVek+-J|qM071``suQkc`FlWXLl)hmsq{bNY-(K%oo5KXw+B;AQNKTk+M> zLq2J~i!q9q5z77+@p}(Rjzt2MU|XYc-BJ||jn81)oqQoyNfD8wp6d^K8p=9xui*Iu z$_p~e+&MpP5B=XX)Vc~|_7Xpr)jP93saV6AwK^BT{5h+SV!Gg!+eBsi$v5;rCV8j4 z&fA!S@vEe@tiH5`_@K4Hv>FGu#O^o{k9+vhtb4n*45hozLI^~&zu+6r--9Rf@jgqR z{zFmtz7g+1%{iO&B!;lxcYcjv91dQmp>qx`_>iWx=**KZJHl;Zv&xTy(NK;z*2Xb- zO(Icca7ih?!7)CB`!ICYOOg27x!M;tl-p5Y+SJJW? z(d~HkP6v^747;hRy%q|e%ek(2pl&i!`3CEYX{`;$&gWy!rdu-CVWn!yQy2klnx+iy zUzt~uH};5G^X64U0N5^bEax!Li=j}W1!8q|Tt*&aTm z}8B*z048H^Fyyl##!s@^9pSeX|~6QzSjMO)`t;UWZ)Wrx>bz9L(l1DOE>;V zzBt2eoHCGtQEXb;rE?T&TMYj=#oy`K^W2zsd0pG;{Z02$&`nPsSMJacS4XJ=sUK@= zoup3R8i|;nMPu7g)EY>aWi823586;{CK<{fT@w|_JZ~Ng>k^i6f*3XBxjY3K48GrGwe%|+n z90b+sia-tLFJy|5!xJD|-jmjIZo%UAt1K1!f+N1p;XBu0DSlXg#0w+nFz1G-H*TFY z)i#w()3Pi%Qj4Y{c?P&2)k|;mMIp{TrPDuV3I0pAFt|6Ez*us$>9#(Y_`H5kDd<{_ z^Ip=5@_lC&s*WfEP4qTt?R>lbwM@Lr-H2xDZ!92EvIuEU&7!U!;oqd1y8PW&A`K(eR*}mmvi()@aPZf5BrK)X zgbj2< z1R%XR|98V~J+l=3|JpDA0|WifW`m@JDxhI;pUPC#q>06ec9qKYQS|?}_wxVuU33-! zElH4%$0440f{j3xW<$P>fW)Z;G!!>wt_?U;Xm#p#{bjb72`*ANOh4~f>gzFiWbRHe z&>>mUq=RMu9f_$*iaM8DtDQJ1y8r642M{anJ@bD}^&d<7^Hr8Q&o%umcEb2I)p2OD zh>Lr|>a=J1o-?V>Z{1ZkkJmsc~nXHw3XUWc_Gj-hkmiH^01fmmct&OC(fe z7lZ5;Wxb{TRBX4EpV0Dh^K}|KkK5}(Q(1w)%_;@N{qxb_ifyapd)aZ6Yo5PHZLR&s zO)>iTW{%g6m3jVqWsnxm`e8vrXtyLs) z%!W5+d0H_U{>)Xs6w-pzx&9{Qx^l1Z8A!z3x%)0DZwnVK`~SM71E4Zlna`qZv; zS~rN+4G9N)JU{|BSYpRLXo_)r(hM3lNt8!cUvwYL;k-arA1990Aj{2|`gi+Nvl2_k z@Mrsz;5De`im)`g`Xt6k-FkhQ*WIf2i-n|)GrQLjI_?`P8bMsYweoeY8YddmAhx(}tawRp>BmjP08ZR60puOP3AP zn|QkvJFsz>MgUeBC6m9eR?{87cjD;CO4}8>zI}$;#}zmQRh`UWtQrF*fM$?RsRdll z)KBWm0pDJzK>ec3)~U>=JVu5SWLX)ELyTi&jaih|M#sVjh18Pjeg)@w=1q%FPB5Jz z8Sp}YFKl1EYl($d?kO9$mhO$E`ow=I%xotI7{1!H#N^~N2ID-B5>--Ob0l3Ik-@H& z8&HeXGo|q!6s#!S$??tn5OWqh3nYu)=4V_L>&cSglC>F2pQ6c)VHWXIUECVG11j#U zW@70>x+5Fe&A7Yuenf0t|A#mCDVFNeO%IhK3-VHS0e$h_v!t}oxV+U(5_LeEpZ-&7 z-2q~6^_FF?3E+aWLPB1s|3k5~Z$f-#uJtMNEz7VeHmJ_Fh_WS;ab+7= zyoxDAyD5D2B$B3q$Zxcz;aCF4H?sINz`eINF0?G=^dwRUwB8bGUT@)>Birt>cwcIg*{98(p_k;b^k0+ zf1@sWVCjzJHzmHiy}|n8&YWYC{_7EY_0AE&U5OkrnHhV_PG$GceoslAnrl#ri@r>12D~D(afoHr&!{ulO5XR7TjDOAy+d2Q)ZL1Si%Vgj%7ZZf&}w4M29#=ny+IHC z7R{z;(KO3gXt*l93&2p??@H8Oz>~pax+u~MIFH0F6o!iz9sa=2F`ZG78aUN{A$($j zla9UtP;iIyrRr?=`eePtuMLI9^nykD;x2F-Hfsg&}?`IdbpsojDu&wc;kw`&{EEe7XuLDYzIj@Zx zFJA<)1h96mTsk#6uz8m9{$LmCES(igqmQQ~1me-&Ct-RK3h8Lc+x^f>AYNYoV)8CP z0o?wa;n4ktf1RaUq8K@iuKLemY`;woYloTm`oLPIHRB-`ny^o&vFx3SsEnq>(jC=a z7dyG_wbI^IED6^J_;944+~b-@A}=|0VuACj72S&d1|@B~yLsj8?rdfcBZ^Nc;!{7> zf&X9M*+YG`G#$zl`6?d2wt8GeHgii8KPr=;5u@gK(Nrl zT&vj6qMRM4_G7dvm6txm3NN&(GdO__IgmChm6L+#m>jKfw-cOp*b+GSDSU*Kz{u{c zc?T|#l8@LlOen;4+O&Sam@j<9ZyN+FP$9*M@A?90Wq1<1YQy_Aiw1nkxvuw{REat* z0?UT(el&0=Bzq+EcQ?Fc%k`En&CiaPlNY4MA}cd?6G|0$I<-)(jM`ET_3L$QYu`9? z5U$v(PAWrd>ztev{1d=TI;Hx>%F;B7wgAH&a5jOWNn(1F~*?r^CATzI#3LHNP1Y!af@G>#2cLc#F04LjdDk0`uugH5 zj=qZ_)A+tF6f86aiLzAP`4QVr&+BFU9(FiWjdbVQhW@4JQ-}toe5B7G$imM>mB3cU z+&`aX2GYB}6aD9)ZWV8zyd{q2q|sH0mSe~Lt-+{kpPH#2I3B5SO_L>1%cXy>3jKxO z?jHX8$qxWcWmsJ*=E=?d<-$~(cT8T(I8HK^NOr9^K&^0zdE6%#)kWpZ}+k- zD!9GMO=O%`{kM91*o3{M>7B2#Uf|hrEO?;7E=}o`JVxOJ9VAt0LtK@64mU0BqTsgB zVL?)nUQ%huW{b>pwatIgk@4~mP1xtkAH!o>P>u*|Z${y}Q6yOfA`CX~o|sp3vskaG zdBXKVgV*`eX|@0Dp|t((_Ib_bk7>cSU|wt1w&Z@LomyIaC&k=_r;`7-PH>90P0*5R z%=*`v#oN*QCutWQ^!g!u9#QYJz6>w{@yjR#|>Ns_OzfV}#AY7Dw0CJGB> zn*~+beE+B(y{Q83scqF|CozvTgk?s$BO9~-;#MCr;a5`c~r5{zq{nD z9z0H!ZG6>oO`O;xY`4fm@O9e|w;!*rOMDgySpVkT{{7A4@eO^F$W&>h))hi0^R$iB zJC!Blr#z<)`3E;l!3N~2d0N9e4DwrG5$M-@E6=;EpyH^s^MDl%X5NyN@15)GR2y>G zn08z|X{f4Y=Cf;+W>xP$^Wxfgqffn$@xvOV0EH~{?Gi2?x63?!twfu{_dBg7mtsP< z@S{*>I$v@$(TG@J1E|As-DW#B^Ion>{zC~tVr^GUDMM6WeukK zDLKC%qb`+kosSDcrn-gSYI)k#q(7CrZYnS>sgwPW&Cn}sq{|TJ4~@1szBnD6#FG40 zwKz6To02#-RB0kL7i=h8=LyM9>;ykD)#vm|yyZzI@@B-tdtsujx*Nos68(`BDMsd8S>)lpqItmIAUX73kI(%#ftApBwVF3nNX$ZQ!5vxMheXq~8 z$ll;*)eIGX$#tdvt71M%Q{=zAfni$?y=ds;%JW1@9E2BA7`0q+-9|;K9kSC)H)Z@1 z{Ja`JEYaEF9+0RBYO*#jz`vL zB^@Z~Gk(_fg4~C=UGfZXH210H_8F1Aih4aFiNkS5aa>ccjPv$ew7)eqUFmVxt^1ez zG+UoxiAL#V8wop9g>G1YL_+k<300t<^X%keJxUz=2M0F3#|DX~XQp~e-xx)Uiygw@ z7b>lBpjCp~qxin}U6k=N_x#H1Gp1&POHV^5 zjnS!LwC%>P&D=F1++80e4)VM63X7g<$cx((8V<)c(q}~aXuAFPLb#3~OhcIL4?(=R zDWyzUv=-(~qY@+b@fyM@Ddb%IPAD%?cKqO(Xcbk&S;z5R)L5uk&eZ^4{irJwUADy)CahC zglBoanD_1$=WZ)%5`}%PuXTnt^_Ua|(;o_xE30mPTW$5%C~t=t7kWeNKY56SX9Rs- zJc!_3j$gBDv%WIAh<9M^D`ef*E2DM19~gk5QWJS|)HYpf=aj79{gtPYo1Y+JdKb7E zvUa~phF)-gS1lXsIcENf9})w&bMq1W*N`az6Mnd@%aj)ybtD+-;_(G&vAy02en(z+ zFHGwDaonfTVSO>23i5UEY5?8AXtZ(1=eUYm6V7`&Ao+G|Q1jD_vW5;v1)Ma7LHLZu z#Ca>VZ%nJ!y*l?blAhFYwP=}u4fa}0Uw=|Jx+}~O%cYFkPYyEXHqciITRB(aU!0_G zdY!SGF~i5e164kcP~Brr<0=O^oZ-outl$+8vrS0@C(|O>cY+j2aVT!Z-QBIYdvPf4MS{D#yF-xT2^w_Y z?0xPT6ywh^ZO>J~%iVupYq~$41vY&P{J?H^JAjZ?1kl?L-H}~GKqyj%6 zENrhT0SiVY2^Rq>Dioa^E*w$U4m1Nl(^=<}jeI1d^w9)_)4`N?4fyw7hgODT?0r^x zp9V>{;%f2-o>#9-qgle+v!;bJ%_BdQq&7wPaAIjuF<(@bXRI<{z7~yj+Cm5R!}GzI z7R!LBJnrV`?I){2&8*j@1`N4@%fVBYe-qCJc!R1iN?5qp>Q>I! z2?`%J<+(OgM%tY4X4`F1)5-ka$a!GVX=?~`gd7hVimb5n!ZAZxkB z3-MlkZ?lqi88htzO>^0wsKS3;^>%OoR*n&~>nC~dmqidgp+w@4t@vzvbNFJ=uk2i+ zXG5t`=*1B*ntr(NN&${5H4(S76q)yH07_#TI5+{CH3r3Kpi~3?O5o{5`VI(Xj9}a1 z_>}8sPBC5C-pzy9bOf_xCzfECMky?2Fi{UC)jwrP@em2}jT1HPY4u`D%bKR4r`Hj8 zTOZPCK>+Tq6v8&Z*aJIJx-%hHcRdjgKIX+Lb>ZiBKcOLO*2^Bv3(UheioB^eeDC{M zpCiWGhP_3phS^*tVCwLcZ7>-`Ua;*aV8S*A)(Km7w}b{1AWJ*Zybtw_3p~LkQuvZ1;wta|dVW|# zb>6zEx9q=MCxiHpzl{Juh+^HJ;5w+WaCB!6iHJHhyN}0w|ML!ssFt((`UTjIm#5Gmlly0SDVt)c&=^WT63Wx4Vw=TNbJM1W~+}hew z$V)?RNqMyRhL5JDo$)niIn~Te&`X^*ovaxr3NBo4j`n4Djy_X|=G3Wb94|2@c{E-= zhz1J9)!_>Q++Ux6xNLI!A8ljTI9>>I{SZz&FxgDWGGl2sLt5dfS&8;9z!VehDX#@K z{$W(#Gh%c9;s@qfd-*rmt-SL^+i_#`*ui-!Ak8+5DyZ}G?tc)-Y^J=cq+A_Mnv*vq zLm$wh;I7sbl3vpfB&ri`8?9rBu`kpE++VsxtRR zrtFLj)UEGuG&)uz^Dc=^jn*VVa*~t*BDmi12G^sc>>GIY$AaY-ubOI(Iqvl$Vgb*6 zO2p3FU3lMLi$LWU7d4$*|6jzfk`4(z_$9|e3uB`@v^WxK_8t_qSrt8Z^fGI zCB?kerA$AqN~Z;hdyg4+3d=lLC^IX)&VW#hTsj}GMZfz_e%zYV(m{Q$;qvhj0r9q7 z7vhIXBJGgvb;>=FWzp-+gcmn9S4xr=zI!$5!z?w|vtFd9<(k_W%*Ap&HZt$P5C|Ym zIP%<&41L7WnP1r4spnzzKM3cQj#M^D8*q;#IqriU>fDcT)5kEXI2R%8VTrv8`h}|| zIo=rJDD7y4<(toAjK>I}H6%_)QNiv&IrTx|=GS@Tt1(E}`>rOR$+AK+w z^*Zs<$_GFh{!oK7dHBpC)dGP{f}vnR za%y^OuO{v59$a4WT+_b%oYolc8v3*}7q8|RVOlO z;djwL`QWflG_due+QNSsN0{(l~S5W%7S z!Ses5?*BJu+*(}5hQP4Sw^07)U{kTlzT{5@KEwJQ$6B*x=ziRvVr9&LLk>HN@yv4O6^)x!1@3Z)6Q!TyRR>m_Q-n_XCz^i4jl)5jc z&6vP-^n#|XzusIQZ+8cIxs@yR5 zKwNeApXS0W+hug89&Yl=hRa7^ETOw8@84y@Yv&%bhb@`9=zBJEK(Zz^=9HG;jfA?{ zr=`fcgOIH~uJC9JEIEmX#`)5=bON2u)#Ym$wCDqS^AWp1KnNPA34}{ z;FIlEtO;QM&~wA9v&)Xgv%?!GCFmyp5q8=y1o$3N>Ol2yY9B*!E(6emfw8!V9r z-k!*#!)UQzXAHgXC>Jy8$J+U$%&u{2k@$AKriH-G&hn`pKaziZncNXu$YB&Qoi8q8 zzESB|INv^G(9_dv&O4`x!*Mw7!^b%er}}|obfqx zCnFP{P_+SH5V}ybIthg)t&}-{jtu#hYVL>&KyxOEeeXUTu75;zK%mCryFI?8c=aJ3 zlXhAAQk%cxI#m_=D|c%%-*99ddPXEPa*ntA$Z;n?3Uw!2jGnl7&wh0a<%!`McGC8? z>l-5xuipzM(R>^-mb*~n49a%$+)YS52xNZBfS-cPmIkZ_Hd=<;XjuLyXPT!6%efvAr>#B0VCY^EumWS=`87spWPR~ep(Be;0g{t;+sb*|Iqv8 z)Q4m37)&rSjBenl0Hs(I3JWH5E~zZT1UAV27`6&7V>W2!1z6{GVbm_L8R zKUwKPbD)PBh2Po!2cemS-9V{)w$?Z(#(ZS7VZy1pkKZAkGl(S|!qE%BJAtp6ouB!m zSZiKH3$~4h8QNCrWbzNG%`t<942sDXD`n7e)q{vhCt<>Sj`*G00I{4NP55s3s#qQr{*-kiasWXjBIu_oIqdZX0z{A4%0g0>{_3+;jiA zYA=BJJWsCjzT*H@qgugW5Fu5lt=tVZvP5W91QsjST5Kh3cO^JyS z#$>Hx`8FqWI3mM}~N2VoLL)Us*)Fpwl!J{(XY z`1Xm_Y+NG<8GSPe58;LpMyG~MICviAAaZh7BHh)MSk`oRpyPUa~2 z&aZd(F_wy~*P&(jNhSq+wPrhFD4N83g)ic>=vh_M38{P1_Wr}qfYk9y<(l&iACJ5v zf%dSBAF>+b&Xa#%4x`xXp;EHzB&ytAGvrxW@9r{n8ej3`1~TR(ttp0mGf%9yU!$%h z`0Q%LctX1J%k4i1>tbXH=wszph8Ph>(GSY*&e5l1@-mf4^xGHaPe)*@uJ~jRH={7rE$rFra<=cnqE)lCrN{x1}Ul zU@gmbZ?J8dA7cKZP7_6Ret_4~{dM3>ahMZ%N1YMukyie~4_z6i@Hp#lkDDaocUj+< zE*P?==sM?DeJ#v1CWFjS&VL|_Cn;uR+kIk77}$~@;PhV6ovQ6_sL0yNC7=-hIZW~{ z>EHME5P|in@R>?K4Om3=zc+FyeRv1>Rrg_^=Yde|V^haU$l?YoGp^_uA>C*FdKFbA zjTr6<8K@(U&ALYgd;-NFcC)C5VkUMZXf9#)c`Ob#lF%e2#*=7lp}+G)=uVX}l&bfX z3O})IPuCl2Z}E7Y)nW@yd327x@M9^0kv>mSGJhT5NuiGHtoh`?xp76gxsvb2M|+^J z;2i$h_m~#m7R0g$hHI3kc-ExpZMBblYBF^3+tl@`foTo${(GSd2^}Nq2E24#$vtXX zgSLJu(2VdVD(or8q8WnX2b{_Q-XNCxU}}errN1bFXparHC4`N|L81d(i9>CD$SHY` z;F~&e=tF2h{vdY@g*edGLBX0X8lG@by2KKGOFPyg{79Vpqp6PMaQGdV+LFpB;BH2s zm4jBgLLy?6i?gG!>eI-_&!$!`{}DLSUyp0DqJLqyMtd_@5g*O!(V7fJPl<~Sk>UYMf- z^b(9DU={DNWF&#SbppX4I1 zG9K(kW5wN;%y!}e=r?wL`>3Hngo(9P%A;~}IM%1Bf5_J;D7=9!KIc%&n~mNDP-4pc6D^LC){rU zQhTj`=VUBp-t-dfJqBAZ`47k36CDU6B%dxuRP;~2n(pmTol=r645eO6lv`Kf zJrXKKtH{Avr>-x-dDw=Vt~%|PAjkcfq0TR}TOzU-Xi1f&dx;%Rn<0G1!3hB{)5*ki z#C2Zq2^_qH+#d#8q|I?WBMQ3kHHk%bcBV0~G9=9&&R7*rwMl@Iy*WWhmBU=F<7 zt7wTQo8=!HmD2SOnJ|i_{D%%%b}78jzLVe!oeZL>iUC{mPwt=M!qQNvnw2d>+$x34y+*pEoc01d26lY&9|XqS`p&#f0-%|K z3tu;9-xNS)k#5PZanpmBPGIsaD43|qH_dCLp!V9k=?N9c3tZjkP$?2zYWBMG0gp~)`!(DsuBjV!YzW=rz>Djz>{k0Lpbf zQ^6R~w8Q|voZZ+URHa34-&7O&B%*w74;0z@W4LuvlP)e@3d9ACbw{RDo>Z=*0O&fH zr?qhjVh3$_n{W;b&XB+HgmX`%3wvJQ@5S?7A~y#{Q1lEZu=65z8+T&PUm)Y{#O?;> zrM>{k3z+s6>k)olY|1LN^`0!3Jd;1*$x&eLJEuLDgu9?pC=1-}CypB`T!dMWX(OQB zbo7Y3t}YPH@|Xu*XCG1%p!V!tZLBxh?^zSvGmI`dc{+`yy`7A-Ua7EwhE~#E??w0( zeB2(W>Pk2>Cv1=-XoI}I6z3byz6IQ24pkO;XRi}bhi`H}-sJZ#4`4TL{7#r4q2kZ_ zAhtG*t?RSanSN@HtEX?Ysl?)b0LKpyA5|LYef=45 z9yf-X3!0lMtIPO&BVnF`>O*+<0vJ8}!BC$_BNL{Rf6QE!8w&7(pRC^fV?AIexUgUz zzz`5b2;kj@DeHD*%kuT=4iE{PzRXdVAJF^a#9BGQFD_2ryzs-c{`nQq74My*!~Q-!s(FEj zMymtL*0>PHrJx9y;+p**KcSC9J_fE=*7B2pr?=vvKJ6fIbbT9sCp$BF2IlKqU%G#} zxI8EMQK9hzND_YD!JvymSNjox56cD1wnh@j=iL4`MfK|XWkyKosDv)hF5|omqY8wN z<&Qu-c9O3ItIoC5>s~qzGBP0Po^WWMdsq2xxOfi%DIR#d-CpC`$1g;ZTM&2o`4>|z zodo&cV*z^C8|?dtac`3SHu)|w06TErC_7z{7@W7&TI<_gru%*Uqoe=9V8LI_Tc(tk z%Gd5Hj>&#Cta-5`K#A*)wPJmu4~CQIlrHGETv-e1qh@_#bwFak+H$>(Aq9-At`kU? zk%#;@V$vP?h_f|e+Gx2Gub=<#e-KPyN5hxMt@W+5#>x9z@r2s@%eR?2*!3E;Ct1%N z`x*TuuuKT*y|=GDhar@*X->I7*D8y$(EO*Rd5BJCo^SaU4vqHMMqaM7AslZ1p*vz1 z5Y~gsn+-s$3+|@%(nS{#>C!WdF>2Lz01>pLu~!#^gcn9UK@f z7YlR;7zbKWh4f&-i??r-7Q`rxV1`e_XR=V^Yo6?K-M2ZVFkh|#p`!ZCZ+|3{|0+fu z4ux;DzIax$PuSL6mWwn$A7G6vcPBzJE?l7i&E-zcy)M%sboV{^3&Y_FmHEB-YWu?0 z=B!hJqo{OZJm!Dr#hw_pHLuwLtX>Ug146HNWrOxqz@^(dw9}`9ETV8f`m(h#TeeCR zx#NsyL6s`XD*Uke&{kKa%e-WiG)2`VKbrYBM#g?orTK-uCD<(Q?>{RAP#*P@pIZa& zg*tj&1w?f?;SA^=EctZo>G1 z%K={vPwrQQ7(;j4XFK;xk97X5++EKOeR9Wh(1;~oy#O$r<_9x*QaolyMeOxNxV~V` zUHJjP|3QedhPV!rcIQ^E8(1e+-@+@`$O%X=IM8j0gk zCicqi7rKwbJ@LUREYRONxnH1>IMnOzscr)PrKtMF{IxiEHAz3Bzb@hdfFnY-kKUn5 zp5-6kRiwY!VcV=L+|?SP3REB>N~SaK^BS3Gt#B78Z9XDOI!!t?!h#b=a`|?~g))XV z6n)hjm+UQvW`6A`wEhL@T$D;;%quhuRab>R*K}(?uYr9Q_QT`Ab*okP@e6L)DC;m` z`M-e}N`Hqcjcjnoja1@sDD^7z_i(p)HN*?Bwpb=#fV))0FAj6;FA~=7g=e~5;Wc7K z`+E5>;O$^Idd!5Lz+jEA2qAn`OCLT=7l^S#Z-VFUsIdH~e2d2=5{SuIE*KkWcbZV)6Y5)UVVlibgdr$>8oy>JCu%|`- zt`5BV4oKuxZ9425JRND(U}&^kvQoJb^{P$iZ5Pp9Zkn6!*pFvO%Am9SdorWOZ1f&R zYD8iK)z+1x)OsI{MTSuR&*$~ZaJc&xhW(6w(oF~wg9FeeDw;Mfh=>yoQ}@=fp&ev^U@hE%unB>W^rfqZ>g3%wvajMpnyhfe~--LuN+uiFX&m)=$-EsXdtOt7NI1S&R z(O!KYVR7ECA)Pt29ZHNJqXzsrDxU(|bIF96VvL((Y>J7iXjA^MJVKvu!~7UuLopvh zoAp80ezMFIaSUS%2VCltj7mT?dC?V)Ds6Nb|MCod)DoR)Q>r{0iEblYP6oa~G^n14 zZmYBN35`x&K49jL|1rQ_BKM)PE6yyr!ytvwxA^M|XC; zUvhV(RZhj1OOyL-DBf-C9InA!L}H`W+RSynn<4KOZIvZ>F>^(eXq=XMm>NHbBdw8V zGC6&b@(@*}F#O@SX<`Q=oc4dG?&h@()@{`InQe}x4SB~gIRa5p5=S*J7%_yy;QR6hzXM5zY)DKw@>~6msg>UkAIjE>(s22u}2el zbE_0fE#SJD&y5WpF}(YR@%B04Sh8*^csc*@ILAp@*HT#Sw)CCbaTjxr50#A&;>n53 z5;I`hicYaCTI_-Y38~vvZuOi~9PuPa^$0PZ8#rT_njJMG$nYB*bA-ZHUN&*eIc02P z1xk?2xv3c+F?B+BaQ^n^x0?_%9C|%N4p&(IZ+hg9x-$G?o@sMaHO*jNBUs^aC%;W^` zw&3hgWS*ac!)ux51{X%_3np6L1)tusTAS4tVquBdnbih%Z?7cf% zDC1RHFt7aci-}VKFx7WlM^opYrwtW!Kr-&}9%UqPEb+Q4yP)@_tRcQ0o^(scv~@}n z<}NpF1v?r>Qz}y@s3ky%0?gzqkh+(E- z-fT^f-d$+$qUClakA@s0s<&;K?I-4GJR9dGx5g`%SL%zxgKfDUx zwH?YjI>PPpG1b0VGQISzcjWgMs*b@$!pPZg_frMzANment+5W#1@}6;2??+5+tVoa zH;7je1n9!ALWjJk+u+qd?90BZf5)#5yC*}N?&vI$@q%G1j=ohoQq?qi0gq}1k%@cX zLj*-)Evcbzih`XijtmVe?Z4nZ1a>m6j2?NSjPNyRPD)=NB|scFhYOl%b>C%jU6Y_Kr=2 z?VO+U!eHFXoPtZtH4#wa3s*;GP|ql-UQ2mns>p^Hu|Rt?ahQYQj`ag9VP1t~Z_+zt z1jNipUlDF1Q%Tb@yM&^h)S}=U>WGX%0LqI*p^l-SI4EyfxUbUIL&&b%zqocq`Dlnd zNyhc}eq2Z*J_$)`&>9yk9Xwz}D8^!VrebiK{v`TbGBs7+aKkZ+S$Rg-|4)XiZ!7f+ zD(xX644ddZ2uj$3L8FE4%T=ZLF=`c6V2jLu;B8Ehf~EX5;l+rpY$OiQm+^OuH$T!` zUicSC+A{g8La+}xQWFYlNbK$%N_*D9_u-E>qRK_^Bja%RJcZ|H0vXx~z7$d2?_+gV zcK5WUQ)7=IzaBxvlUd&Ry2kyE>7YB=K08h)70E66t;v-Eqj=*l$h zdbDp7IL@_vy*RJBuqA@JTNbekdxiQc&-v+CV-=b}h~aT{nDj5_KbZe@cm2K$n4g%g)5PrxL)Z|2H_S(0Q=B&i9>oqy1_Wp_gdTb_|wzl}TWrTXW z6RsPcU+ZQ1ZGngC-D}nJ7u_}OM+FXTk=SAT&>1Om>(CDLmf#!Cx-1cnQh2*<(n&fu z-3nI{X|>I@_dHb_X(bbV=HCL-ii3F21uncAqUVCw(SL6Qik)pt$euTDd0i74_Muu; zMoEqYfzlfxk7c{Z#Ar=wFuf(6k`t<}NHw6PxuV4{MhYjG3eIb)+ld&7c^1lM$u6^#h>i6rS~kBKCYD-_Z2dM{r7ytYglyX1J==_Y{&hP`O) zC0tzLVfub+Yi@5{K%2^MWDzmnHy+-v2eoc{JLLDX^!l+uMusSPx|B}Hmv znN;s^)u(vV$C&YxMPpN^!256qcA|Ar(p<}7QKj{*(a>ApiadFPa-_y@NPCG%9K>Ye z>+(2?zE_c*s>Lq^Fp|0#N@#NmLnxFR*a>g$I-YCg_w8@v+LC!(*U&MLnGev%5g*1r zkEs>Qg%%AcS4^1h>vf#jUSXVs!-d$743>$TO_LoNnXAS>IDyg76qV=Hc&U3)$W9pV z%(|5Hyu)FK7*#tzRHtUCn22jK4(~i(dyA4wSLAXxHeQ%?A7GsipXvc%ju<-*p22w% zkZ{z+g^7oow!wsq6rVhbrh3kUs1SCY81>tDy;p<$7TI1vN0BbVVKGYeg?dU-%s{6>aL4jX8`&4*L6hnQ& zgq;Qs&B&CvC~6E6@!9>jt6_}&%@6J2xeRMYHg?o6$BAXgyWqSuc#5-Kq(!&1q(Xna z$!@%UfMT#3?VYC>zZPea$RkX0~1%e#xd*`nVDY_Y}69 zyi-y-5Y#AnjcPxaXGYX_Ehytfg93E?eopSx{qgvN!- z8Da?t4^Xr;`dst2#2j5u4DB5uWrIQkJVtBDv~@I!qo_xEsL4V;{??0~i@N7@i=F~J zJ&T{`6eP5@M-TmmrJ7o{S@M3}ENuUIw!beZZ`j)7f7daJkOKz1wcpz&tPRnhVsDCqVsEf-l4 z$OsF5QfgvHgd{&8;J}|{l|uw|FPj2YeNp3*;~Rq&IA~AjH(rxWiI_z%g$uSIYKb>P zC}cnrnwDZ@%{*m$^gGERZ7D$nM>5S}3(MuhNNq!Q3lmG5-hdBfSJ`PI(6nru{yzwl z_bZ%Lh;3S4UwYjim8l#Dlp*n+bm0NRUD)!7rC?ZY-fyrPWWa#y52k0jS*GEzRz zp*#xR-(y6W)G+|r&zUlF4=(*>U4{jguAOALtw_FVyv%TW*>RmCAazB~h<9Bpy7u6T zF&Wyd^DK9hE!DBH9Y;(oQb+g&+UPfHLHHmHOD^{duTa zGs@BHLob5(9_cMug=6Ula^zQlFRZPoEWIMX-LJwQ(gX0iLGxF)Q$F%ugfee^9eVk! zj)!I%WT7}^%~(En{n1^U%^csnKV}{@wh<9wU8FIQxb!v$+{=<6(dJ+M;FN(AFU!kYw@A?(*_VGE$@>+1J5m-{BTEj4k+0Q! z(j*eIfHEMycaG~W$_t#~-3u#a`+KvUESJUim>Mmv*Su6)V?XE&Y+@1+AV*pZ5IwC< z7O5V5JrWckK3K@aAFjm~IGM4r^YskPV+xuiio*B%TEg0%;F(c=X;jZA0tiViUm$%cl$nR$Xi^m6+gNNlBh_F zZKGrc(=rFBZe*cW=mdJ+5KJo*z|RPkI{sQoUsNv-|G+KF3G@8?PsS%YMWwT)v$?TZ zp7?xwoAy&OcLMK&rSAb=j+v>zNl<6#w$7Q4W3<~{2UM;;>sTrBThvD;*80$4kG~kM zd3oJ;^pp-Kta|7Xv6hGQ&I=2-r46AiXxz8(NXQ^*M%ZOxTl9>4T@GC0c6=iRs` z=L7Lumv_uVOZ@y;QA<1%DIcSigUmkSL7odHgDBy!`ryR$5BFWl{9Jka-)70%R#Cv_ z3N??UQR;&b+5aFI7G#-k7UwX5^8%Vq7<5P^o^vc6b@7_Z{i7_Sg?)dK87GUtmFbRD zY?m^Tz?>E9a@j)d^*-9QA&%c|%5?518ZwH7`f!dSo$qmiV}Pd!NAcE) zHUEa?4aMks$HY&cn=4(J?Chpr8J(slS#gW&i>?OpyiHV_iD}9Tt|RJqwbNvgm$caU zG0c>)5j1~ar1m^k7+ZsZU&-kX)pa)I-|qD+AQZ(gS_FS z_Sspu`r*?5zrCMQXEZZ9A^a&a92oY$eIeZKhUWhMGv8cudUSA9b6iuC$~*fadp7*{ z!xudEJf~kMhIi+b{-pKVn|f8fNd*3{3S`UFtMN_3^z}aouW6MGugeN=gied9(oQ+Z z%l6HCSmGmsqC!6!nMbP$bkaE!FVa4RNFaX^K)&|c4b1~#*1h;~IP{DcZ&Cd|^aK*W z?mY&mP9u?%Bc-hF#=7x>L}L%ccyJX06id2LW;gLt;&UZ>@4)0g{st3oiaCh)k=G&J zo+%n6AykB5iT4`HcpLx267?dvqvjJt&-p7TaFy);)@3s}U8UH0?Z8%sHCT$2uYCi* z0}KOn&n}iMWKwY7TfA4=L=oX9hO1jQQ$lBB@qoG+c@OyG^KEwu zJNvS89%F?6iWny}1X5c!fyZUe`E`Ld$)|9jLbP;>%?0yRYf278ZrA6w8vf@##kC>j z%lG}NLjk7dK*cDoU~A=fp8;&YfDT%2O4E6hUWW9E-~E-PK1T5D1^hl!8Km>5AP-w| z`X@;d%a7N4l2GeBg8vW>Xe>?^CUQZqo@xFZ@=>+rGQZALDS-u}+vRhn3tF|!hQLAI zQ2R~J9sDjeg*iEa%gtKoRGrWi#67$$3ZUFkkQ;v>43RU_io>Y6@h;$rmU$PZ8t+AW zme=&p?pO1*chaMT?8gea4Tp870KSd-h#p{y_DT{W}tX$D9lh9?{{2@e=LP@9L$H9AA)co%5 zvp37xKebXt1On4rTdL~nFri$qrE@mQ6_pd8nPc3IV&02R0~ zK35W=xBl&crR7K)r^77%G{+rv2X(#uki?~gpm0FR`6#t?>B=$baNe3rck~;j-Y>9C zh1hBzL4CEYx>erS)~uyfcGX0bW5;yP&_%yux7rxp`Qz|YGftZCJ;Gdxa;Wt$au@4u zm?lHDZ|*xs$R8Ob==tI*fg_5A-gl^rKV&l*?WY(g80sp0C{v1IjS5hLZjCJAUx}05 zhKuR%#wCOi(6qXh$6dowudr$05M^hI$s7F<>S*iVL+%IXaowiZi2|DU7q^wO2Kv?+ zbDPqv=_{OxxN_Nue_imOV((83X2RXi?7hwY_TUuX2pVIo;)nr>ReS&<`J8S*;J^0y@vePd%Acf?C+;FB7%SPMpzqxSo3QdI_SHU(LMYE zBLUvEqc4mLyu3*@^vUZgIsLtkns*xsnE^Ra{rHmI=PN9OVy(Y>l+xc?6Bzi93+&u? zA>I{TaRF=zOB}@3MT1oJR|6C@DVYaXcf>06&aeL@~p%eeI9K&PRqM@au_ZQ$6Msm@iV$=Ye(OP0Yj6Iy=7CeY1HE z6~wD_f+nKY)e;VGTD^h5hSCymD0;t-MLv{cZ|L=6xeTK@s^l~yqlSB(I`vx3x`H0%DPf_iO(2Yx-lrOB zII&Ao2(#SpiJ+vI6{CVRPYjS+Mgc(X08&5M+4n_jBRqsA%8dq!7*v*ub3pQFB6eD- zw$NZBYR106$S41>rWQB>;YF)-xSlcFx$@u52}H&t_A+bFG|u^Q30TqNr6^G6_C!0F zhYfKkbS|@Tgt%M{i7`OCei_oHs4n}-`<7aBownSEzrZ2S?=Ww;JLpt8vsV+*5pT4J zpeg$g@oy-rFr$7-)FqB?N-x9bf%brA9t!r8wdzauT|E7QwnWeXw&>yWJh^48mTh^! zyw(l|F9(YCljR^w7XuuPpi7xw?d7XDXQayHYh#?+KFQ9u=blWwAC}4BZdH!?7P4 zJpuQ`|Mxp)o$mzf)nR!tZw%#8-8g_UQ3t#Dw6CZa6=JNNN&Bq5>LZ(V^b#AqQT}-mT zR!#^ag~24HNcO$-YXzt$@+qJT-maLjP15NtB3%M3Q)K5Qo(+OKEodTFoDf{#k)}hi zBe@@-vtxmcy^~}2k@9FBNVaFtXw9e9e50oTt)5I8pnKM39La8FVr#l~4TQI;Ji!IN zJ?xQOQi#FyQ(Wz@YtbsICO}4|%o-+W+V$LPMyUzt`p&Ox6nAxWjrprDTIq64A~Av8 zu4TOK6$j?q%p1w9*+>5HqCEZBT-8{w18@|bitNj~rcUWS>v?vwW z0lU`3jAajJ0JG<`iwx7fDaO74+NEZo{oa{#@fbj`vMmpHylQyy9!w*YhWZ!_ z5F!$v-3TV(?rahfZufE-_3z73%AGcghGoQZ&#Zt8Ynfwh4hf2LN6UWF zknOhtHCrMmLwpt91ls$2jlYkkQT>%67dBiJA;E_Y^#G<3IClp>?Zx|9#;9vSFPGu; zZGG)9d(7fmsqYttr`e@QU9Bx>31D-fth~bN&hZiEG4ido-hXz=3FDe_G9f?Pw!+*D zJA^niYfp>|ru@A#=_PPa8PxLYPOznuLBiE{?<`2eFT20!6AQ3)qh8Q!F%b7}c~0A$ z+d_<3HZ&pFk68>yTl;ifF*mmw{$9*m%;micm`AuO5b%i&4sLrN8wBd&K|K+k%k4=m zbBqx~=_9Nu{~FjP?EiyMnp9{J`yYhPug-lmT~hm=@hM>5TNk|@eW{`ihS3_f%g%m+ zG#NTN&$XOSV{|#t1hEE4Zf&wjhoczDN?KA0XW9eu&*G9LhqBbPoEiaiFb$ z@b~W>21?GOfaB=%7jrXSWXN(ySyUZQ^8#!2QLEepeb-VaI(q+s6WN1Auc*f2a%tT@ z(IP`r$dyie`&T@nkL(A(zo4mL0s9_dPo#y8;K(MyT;%#0dlrYh>3iPf?SNL+*ny+l zpHf(ZnnDDw?|ftXiD>B?;h!a6x70NGgg%-H`TH4YOmd9`mE1)Tsl-vS!b?^&Uf|-m zZSD?8Kj6L7Q;(*sGLBKsy&Club(@#sD5lE#g3J0o(e`h_=0g@s%|7!zM^k#jztgfh zeB9`EBY4WbDf?*oc0+6Un`@?t1$;RYLZ*sQXBT0i%*Jrlmu5_3_~y#}cB$aLK5^YF z8dd9~Xsec;nql%lX*_Zg?H=A$WqobTs6)-sEe}+YM=o~TAv4!oO&YP8Dw4Fl5Lz?h zdr%F29kyS6`jj=H)jOV3W9OeBzmkU^?OJ4mD@+J1VHlwh*A)D3*_>W5V`cPI*LAUkn#cH>r}z8d=k zuCXee!(x~)jip2tQRhbtJz0#u%V}(~O?>g;SDtQHbnJ)klEe0Zyfx@>qk>b3MO%w` z>?J~Q5Ah5wtsrXXludDFZfyFaYiPc{+U0=Rys2l;e7O2TK_&O!UvZn5P0J!TXG%Y1 z4fB@BD343tkq6k3A9$4F$xY>b8Qq9q9A=^VK0B-YQq|xH>aV!b22CASh)qQ`?C{){IXc`iC#R|$rU@i4cmXk{4?BO0rcF!J^Fl@J zvhmK~XR2d4JR*@6itb2q23agGTNu;y>tp{m0}GUCXREUKSIe2MJ0IRn+5WRdE>sOw zPIRVpk{Gc$tWhm7i*Ns`xYa;GshEMm{%5@Kz!ZC>VDuPdvpH~hrA}6LflXuAtDiLc zfLfy6vVbqMlAJHNtwkog{ZEc~NNf}Hr0ITMuo zE2YdSma1Jc2aQ+blKr+&T!RD%=xGZf9MLtm zbrTxA=rLc4%^RY1MpNrKD0@|&JZ~viD?Nn#AUrPomqjKfWGNot*@RWp++~J%)6T1EVUW-`~26J!^UaXX&KquGjVc_noeE6 z>H1smG5d@0^l4W(0-~lya{5@;YCp|B&@D)o)jUn#xWV--DM2dBWKGIrg??G?d$UQB zYK!nYCqXb>ppYN8p%jfQ=E8<7$LRS#GYWssvy(G}uQ{8ZtKbTytmO8kM6ashmYVFY z<`#f4NDka`?MzlqADNP$T_?81nPOz-wFWX;N7aGUMRp?k+nZOw;HP53^0s4RX0PRZ!F5#eq}aZYn04A{^aY^?VE?N74%+`yHOD^a)5i)ZhoZhad-g1iLQV4 zM5)ai(Gp$Ov@)AK?PKio-vP|G`u6w&XN{?)j%*ob<}n?(=e=j>jlUC0(b=MMqT*$J z)Vv6i4w@o&=|42^l!=(~J4hEvJIK*0go!pO2k&5l?A6BS?E{B!wl@Ix%Z(1pv#(h{ zIF;>fo?GOm&pO{#HS?zK?q)myh+zx+>}e3!-heNVgixL-Uk&IlS0k?om1iU&vS9L0 zgB!F?Vn@9jOBqblh(G;0!2IEU68j17PJE6BEwlT@ut1Awx|?kE9*guza#m_Q$W@{E z*=9~M=u~^c@fH^@ZECB#Zy!#y0&%-JPSsBGKDR>nDk}603|_mJPYvdGt3L3fVy%rb z=0)W=j|hU%r_}czhQnGQfw2rdOPy&M&-Rx?N$f^4%~rRba^9T4b*{=&-xebrNzF9`0G#JB2gc?%7{&2o%7AsC7~O=BMLuq6 zN6g_|UjhD(JWXqQP1(t3k96}dl|+}g{Y|OW=aI1vwKGZ!cUNd|zR3jg=j8it>w^&kFl1MzjJPS8AlxdTMORDzR)RK z_39UN-}$8I|MQPMGdv6@!NIz4UiXoO=j4laiGOd<1hu)24C;>Ni61AVZjcl^%&cR{ zcs?H<*i-)ecU}?|@eqi+7E}4l-D}RdEzMJqoKLy$jV(sg2xohs=H!7?riOm8u<0yg zb8cTorPBE(I0y*~o4CxJ--8Nm_Q~^J29{akwGW5{V7z^?v9E%eY6qRRQ)aVGNS@wb zS9gEs!aP4T`Bm-v|FHB{QEjzt*LEmgtkB|4kQR5hLLpF!Q(Q`c;_mM5p|};-;10zJ zF2x~ufE0J9+<%_;`*$+;qM;T`$|{0fXHXqn_5awfQf=+>pb2hf z;JS%a?A=v|Jkkg}$=!$Gxh!o@RN$VK8{Iq1sV}&*HMZB*l*lo)f)$bdBfDrs-&nyb zf&H~;#(p~Yfr01G&*B&O?e5NjG___BYoaKUl?e_aj)75}LRcO9UIR8y5TB8J0sMpj zE*0u<%)g24<=E$$M-$I!51eVbNw_s-dk8R7rKm!SWxGq{V943kPKX%U{lMI|GSrDF^y4rQX^3?GG^6(fbpuwP(@7#q`-iCf@Rp=8$s_X~pmI^nHd)AzhK_f`R85haj~J3a=v8Tpq#(q5H(W4qj}uv=k8rMFi^Po1X7zqK_#bF4fKh%5^C=} zca&Rv{&>NnK!GNh?$euc@&>kvzQWnpA)Ah_6^8$mfCWnxyG0yJ_&n)#T=eV8Sy)mp z#M!e=vrCVR9k$!==G5$X4cEA4J@jk|+(Ndv2(2h;ib`z+CtcZFqF)j;tX(`zi8F;v zGv%>SL&S9IUS>=vy0G{SaB5NSqXq0821{zHTrD{!9KB*hJPS8H$I!X|}&jtm9~24A)uU!Qe$dzZOWrzOx}JDl8(Q~*^0 zsNWGmF3wW~$TfYp+AJgZNzTe)-?!>VW({3*M)2D_e9W>|6Gr;gTVx8cQF`X{t(JX1nQRg<}WOZmUO|zkp8-PloSAk+ZbVjR~49#of>- z-k?v%u(Pwnh*@*utCe;UAQ~9e=XlCp&|Gldc_f`7jQKS^#4$Oh~=)=XO7atSq zHa^2(@dacTb|-yH+JfmY-w1|7aMDIyPIhsDW<{%u? zDdA+)&WpKauyOuLN*UBkGJfeKp3puj_sXTTWWAB{sjWPZa4LeSw1IjgbI(}xdz8$j zG z!Klh%N7Lb}f|Ynd?6S4#r^$A0Ai@L1o6bB1%IlN$x2NHnLvlxf)1ADTF&>p?j)Bti zn}gBWJStS0F2QxRj>9zB+?mKb6y4kKk-~43pQA;0LvZ-@cxX(TM%~jW+jhGTdAD2SD+C@4{o6AdL@dHSXTn!d;usff9U0#0by;3SIoZZE9 zxcb74y}pTtn4hn(6_Xb<7}-u78z)EFDXyOncjpdf^@7QB4B5Td@0Ta9k6r@Bk6gwO z`9}l z2iFW+JqjZ(K60u=h0*3ClI_NEW40J$@Ie{BDxO*Q7E6c+t(3c=a)798o8)|H;iJdJ zu2mbRnn=*L63kW`yngC8T5K1G+Ir61QF$5O)*fSJo-m~ximn$dA(p^aG22aVePYWl zRJbEDy_Ro9=ljj`Kfvp@C3S79RgRJ$?ms@^HPzuAsQh204vCdJ&d7$p=)Y2)+{q@w z``nF<1&RC!&ROX3`eA^bUBM@h269i!l>DnB#qS-}J$6l)P$J*^>m3?tCzk!INpsHjJLAwd-Hj!xNG|rP2te0Tx z*o>P%f|YCu2DGcL=lG|qB-iOZ!(y-$@e7=2ina~|fAkw+xK%C)b;9SMwV zH_oGKVfmn9h}rmWSqW^?v8<1Jz%f$`!}LhIVqUwGA>xlp^(NuksBN*KxF!fK+o_hm z_gzkPjkVQgCrn29chkDu+Oa^%YpJiOHZ6{0xi74ImJ`k@}D0I<7-qPwV9r@uCj zH%~JzDMHJi?f6Zq_Pg~DqHSS5R`)(}LJe7#lYunhA}U#^Uo@aP7yIn zz9gu)8$F+U3J-}6J!HJU2_7p_s$SK6mZBb_+3K(}w~Xs?43L_#l_8)~7~s`I18gPo z<^1FMN)hX=P(I!%I>yGLa`8^+sqtO1Svxr~P07wbsV~`M$X|<`o|G;?phw)^H_MfC ze}QWB|2HnJt$T@+ z$qEe|w`KnwK!*HR9O+mTtg2d;fv5-4(|Cwv z=fu4_f^pPT6M1oSM5|fyH2F<&6*ph8Psz2=qO zmsYj4HzKGK4NekL8Yk7&U%SsGGY;~25=4>y7<@|F@g}l#Zz|N_Z~%ay?dbZ3N@Jwr zZa$=>elCH)aqfy%e`aDP+L)W{xqN^=J1%h;V!N_VNFzQ$Ly5VMnPYZyd!`%P1jmQyzqLm6P&);d!IiNN9BXtm=J0-2SG<*Fcw5)b4su^ZVfDt0 z`$pp&bE&MP*#75~m>Cc{Hj>G@(I)V>f=72Abk`C-Trore&35#Bw`k951hoGk`e8&O zz^K<}>qSaGb4b9Yh&dr~JNNodlEx}{8VvjD+Vri?LB;!_6&Ycz)?F{q@*+)%ZtV1C z+;&YU6Tiv}sq3aaz_BeGx5rN20bApWrJ|V2IH@R72Azyh?Q|%K(R=S@_zktIr`p94 zxrT6aixCWO3XC#ZNs1|!#IVE=8ENhTPBfdGc>2UsrfkR|CMW9Ng)GT18}{Hjk-s9S zSHh#jEXlWFuM|JC@rg`W?oUZtiX2VXZf``J`sxnzBpsK6elVqaK}Cd(MUa)za^wE> zt5JY&y_jrZ@98h7b7ih`W#r{q?_9KnAshDNyll{kcT|3z7^eiPssXIF9~2hVL@9cc{UCJ%_F>4`6~5qA*vUW$-Ym7{u~{KaGrYFDw6o2T=(MW7__ z<(?XZ_K}?oONeUc=)3Zya>P0G$?`i?6T-x)UVZE(e-3ndcHGv6UA-XzB%?~+Y%TI96Eicum^$N=t8Oy|W+S@Drty42;>j$d^BafBdprbtR} zv>QZL;7Zoc(uhVU^@#d-)vGp4oVK64Pv3TO#Xs0mu0?vXqB?{iG0J*6OU$o+%AM^*a5J->Up;0p_AjdEna zDR9oMB2nV=8>S}1ho|JN=}5EhbR<|a0)~M#lkKbHBh$icH{!mJpRz<8xf#*9cq^qsCEbYFJ)wXi~Y5&9VK) zj-F4F?n7(XbvxFfx;u(jbg9v)6lVJT+Fse3Yx;4yz`fk$@J%&%eOP=Z^>XzuWLhynvC}`bvSzwE%`@^_!jo$YK((Z2X+|;pHk;6yDrR6Wxitcf2e%D? zn#b#dnnT3K`!sN2NH&YsPPZ45hEAI|Oqponu(2D{HP<;`VQT$Zyu#qm@|6_Py*qP9 z;&BMpFD&Da{Pu6Jsmw`G$qqd(RaC#Y(3)j3GG)3_n(CQ2)49;5m91DqR9uLr4 zs`w49VW}tQBoTb~7xi5tg;=Q{@c9=CpeRrdW0|DPymDFj4y*~W-nSi|jlzvF6c z>D)1%^QSC+C370qidmvYyvn_b7ZH#6uPIe2xN$;9(h|F0LFs^7_cxfcSGaxY!P7_| zTB9ds;&+e<-5+-xU&;~qywJ1-gLep-p|>?Xbd=16R}7v*!(#$^nMO8RAm3ipHa;y; zt-N~6y)-*wRq0V6+yp5Ps*%GA8ky^H2iIwa(n$xSx#cI!A(AW=$P>}}eaP9sYGBk2 z?Q5^_e3h03nk@siXq z8B@Qvwmz)M@xfhbq$L-Mxy&3rD=r9P`6X0`#=Tvurv)LNjJz0a-5yz*m&te8ETvMc zPdNJbzFWCw@VgUKp#+ZWX+oQsBG^F|bGtKI3cnCH|Au>fQ+=nhM?4kw8;v0LK?id3 z&_UYQU9{VilH-Hg#^@KVvEqCj9UE&VHu}}tx3_go5X!1pt2tgk*)9>4g@i564ro|4 z%Jm*GxCkH8xn;&v*A2Q2CUt^X>dMC5O^xKPYRbfrj6NivV_2J`3id329zUyZE0pEBZgMDEGN9_} z`95oP@q_+oX;(&g`o5M}0=J?7Lf;rt@ch3%>-e|oZEY8twyS9=?zZmRE2nOW4Wl-# z8bhq(p4R&cHOzLz(N3BXXU2cF;xVCl-1kB|MqIt3ytb>S`8yEF`_T*`>#@gxGEx|k zVx~4ZLm&rOO%`ZxK^JT&6)R;a6%`)N@AjRa8H#JYN0IxR6= zqB5rR(rE!zQx!Go$)SI8V)MxFk=X_u)IM4oekJ!Mhr$JZ9Dk#;XT_@00`HC`tr$5i z9@qetV)v~F>l@^_@;ZFiw(hrT9#Xz~b}k<=Mhtb->0xgWJ*p8rqrH)OJ%Q{Oli=L9 zc5o=|N{UNgZ;zHNBfojJoe8YB*M8H#S8Q$=(tuwEz<}qM-OsnuP)AI{TjgoI=*QF1 zeUjtckG&q9b(JRmc#|1p-}4DY*KUa#?dMvQ5AgE8IV6=41WWgpqonU6scKUwkh7=_ z$_vYZC-+(RP27zG9rez_wKe`k>*i30ClM*mMX?Nk>iVg?HKbsgGCjd7=IjACi5m^< zq=<`6K6Z#h1CAAo<9P*#{NR;AHlJ%_*;xv zDmN_Xx)>T*HC2WYXPWa&X>*_E?2JKNjDqPJQKZ7+*u)v{{|fP(86K}PHwyHB`m-Xh z+xP(tIz~BaavmAtwCtdcKe7jYTH-jK>!Zj^-S$4_D7Hqvc1hN7tQo-|+-#zF4lDdre|O0NdLw4o2}-)~T1|Ez(kn@F>^tAOiZ?k~nCXyA z4#T{AGj|wNd4Nv z?{cVFCw$0lxWwVNM9RzGIKJOWJuyc#;X0{gRmB^4|22;j_S=uPwh|utFV#u-@wjs4 z-e8D^y5s4KXh36$5wkPDlM-6=@^kO254Tw*|H7i|IZrvJoLk60!w=$IXOWKB{=SRv zJi1*<5YWGw)+=0sUp_h?%V83ZTHsYBrAdq>!!+3xfq$U_P^1b8i7O66YShr;=JPOh z(Ok2E4rp8>6;39zfv?5gWIsyclXVuv`GRtYQb;J~W>!?tU1a1a@oY(|e8bSDRnqYohfP0BWxj?3;sBqgk3MfDjAKhw&zsjpvh%F8G^rO#RUe6G zy_0s&6->76a2+%#XXeSo@I4U3Z-e9f*PJ&Y3X^~ii)+?*(`Cj@8TKn+80H$@(e=Q^ zMq$T{Bgd+CyySu-?M6qVqu?8fWjA=^x5t>Glqu}LzWGyS&0THvDgMoaEmz#IeVIK< z>ysqcL>p;0DLi*t+`oAoE!`JJCD4M>k7)*8W{SkS$L89=_k20{ZPfA~pCX10k@7(C zggxhR^Sb00_Oa?l&@{b~ij#kaP9LAmpEX%rA#IY&pHE9~xKhLjJZL{i9*5=JKpgQ8 z?kzCQ13O8MOQGPQL2;@D_C z%U}SaI6>cyrRDVDT>q6ch@iSx=(cz;5+b+-2^7IORP`3RKKlFCKEzfUa$Ae{wZ;Jo zq~TU%s8lGrxhV8?0L;+YN?!9O!>3Og{)@CSR`8|sBlYD;JV-?z{s+5!I||3ka`QH?UfzPf7p7gc#@f5e`DF3p z%x5;8f#*%6tIO;$W)QqD@H8w?vh5q{Tp@`$Qh)P5e(BNNzO1dhJJOTuk@rSjj#3%$ zP6KJ@{e@QcTDhrh2!I5qhy@{4Z&9l*U7bK+|FX|Ht4ALh8@|BdsXB zig{m>-#GY3zTB^#A*`(+c(ltuWo5(hB8$nLTlbI~$wuiFpK;WwPo7gq#}bxFOTh zN+*gwWlT2H4doFd{dG%Bjdt^T2|S^9(9!^9PIy=j`E~o~PF?=z7@j@uAIlawwRQ=( zopcxNk9tmI?*mp<#m7;SQ_G9GNgPE$EPXj63qB{LTTtSVbHKwlUB<~d7-OC~5`<|G z#4azk@utb>v;A?z#s}GsKR;roKqaIoe68>FAsvP)<_ar)VlD>4z5hg>0|k_MPSW-m z^(b92FysaZM@Er5ho!gkCkh79g%NzV-Ab?d!->kBQ{rvId z;7omao$;2J(UU>iFfyu)?C5Ufn&o1M*VX2aU!etS5D8l2LsHlW_gng=up9`n6O}Naj!>}lpRn*Lt5a8$U9}{w@@^@ zVuk_@F}TDeML?zf%{`a?ds9#<#-(1s!@>%j5M{8JpG5DKF=FCw>S!Zo~iFgXRxW2}ly)1r>= zSUI}+W*%3!j#YV98mgi#a<7oT8W$7xdy8}8RH;{tD+tip{~O5~O|P8UTPKiOcRo2f0^lvI6qUH*}Y6G)X!dn%leza`IHn zXqmBx$%a3E`VMQ`ot>G>zOB6fjbx7aPeBmUN4f4ko_4UL=|Vnod))WEn}G!T#Cb8uZ}CFm7$7zuGQo@5O}hUH zISK?f3~f158y$6Mhr7EvLe>3>a;YHjBF;V=s}$nf`oZg8Bk|bO?pmicUxIcan8!HXVXt zhuYH+tkYdk>RX=CcVb_&(G^p5i5X8nKcw^{@0{*Zq(^FhR|AsWt8Ij^_aV8zJT1SH zpWNx7ibVsyfu(C(B{BWJH{GiisBQ&Pc(p2o8l2ZXNhBHI?r5lH2ghcnb`o3kI>CJmzj z#Kn}FiPji-24@xSv8le*|6lp8*g*UMa%3qnG#-Mi%w63oqhLy8ukbvw z!b6lCKi=Lgn>*}WM(!fk^!Bp>jmZ=1y$1p+MaDQ|p95>DvS!LwuH%UVvhTVegPT)Y zghw-%1eGsyk2DoeBKLPHGj6pchAc4t_mT^?#BI@V8qIzb5r;3{aVX*xLDKFPFMRh4Tn(Zh-gSQr+(^^^-Mdc_5H%v8# z9VgqKksL(p9`C;#tz2G>gR2247nHJ#0_3~Do6j90nf7v93f#W8&DV+yevH{_IcX3g z7ulr>3ixv25&RG0?TCA;Z;^UboA3ND9tT#~7(f0N+RMuC+tolQJ$9+Pm$;GNl-}p- zNbIM3?ikAdqj`oO_Deh)7U5mM$4AH*j?MliC=~7e=phKW=NKi0hpbiJBx>fuI|Li3 zmK&g_p4$KTH&{7_1#}`inu!a+B0n^2#>%sF+fvkhVu+?$?kpuFrcx0zzE843f@*!L`vl`+f48%H=pk;=4?`UB)^hJbpg z#53?{X!E2b@apP_**fn~1`_de!=QFJR1+6LC&a=(P9?T-LNi@4nZ|b^=!SRUCl(j^0kr$gSAwzKS>I5=|~^WVB=P%vX!n2-|sHdwP{w4AFBu{@v7I`-wXfYfbTp;^M_iRznpmr6)u=(J6h(5V{h z^^tl#DDG|Bc%C91CpQmN^p8hS$_W9mx?${c4Cxilxjs~O)6eVX+IZ_+CYuQy5c@EK zIdl7xI-!Ik+e9Pu-gFwG$Um#x=$h8MDh6Lou;vJI-x<0M-K8hY%R$_fUwW$t@k}Z@ z;_!zPStO#0W#j~eWR01d+y;;C2T|_D7a)t&DvB)BQ>9fakTPJX!#*I+bzT#!T&*a@ z^+k=2H#O;$W&x-Y_sf1Opc&U(&-wL-gL7wkXliX$FR(Hji*-Az5H132Y_~2f1 zNlhs>|GI5ejwMs;dEfxH1W^ypP!J4F=|%1K)!-2u-STn%ycNH$0v#jUv9;aYP$-ne zjdJ;wAVN+)#7kxeY$Om*QF2*%oxDYN&6u1uwOU^${y4{M-(cSt=k&VU3l8BlZ?5-E zXP7JM27g%tq2i+9mf?C|V-i#6rMS9nbLdR^S=7(GvLWkL17$_#cLL|Afoj?wA*6{JX3gK#UiBg>}r#W2e+mUoD8KFT0FIOY$}u`T_wB(l-pY z#!g;e`8~?G(6;KxWS_k2&asa*GP(vb&OCTNke~kM!_y#N{!Sh&?;2E1@IU)Ar0H(P zO(=~?&vE~?G$W0Fhb3~-)XO4>Vj?BE)mZ)nrw`c1Et*Fh8R7SAC)XIt0u9L)1z6SI zBP1hRcWp~?K?te*5)nUF((2;BLBcC+kmTK*Lxo^N*q`8f0aR~!xQm|#w&fqu0zJOqFpj>))cy0 z;nCd4?=hzDyeHDZ{)XM?VCj}>woH{)IYyHC1WMO-a`Q+4tIzET>Fh8Yv?{Y z`Ay$#^*U+q?V{M~N!m@ub3w}Mw%bX!dzzm*24M!Ba(@Bn0F)q6gZMb)1g-{W%i`m(;pxYJZPh(H3g={R9Sy1zjf2r^s5W@q=K{1KeL|Pc) zBKEBBH`ad^Hq0qY|?4V zj%uVn!9M}h#IO&dz^eR&A-F=C;tvt#v*Gv!sJg4e!b{&foI%!|D>h` zHve|GX;^dPgh%Y2Ks|8H=6*pw46IraCi02zi+N=RDD>AIPWb%8U@JXOi^RLI&Ae+( zb1J%5Juv#TiWfd$abh;blfb{R6>w>P;Gd zKFZivn~)^lrzRqCqPNAh=a9&!{{T;HTB_UTs@s-GXSWVvuP9%0^ijCut_@0r zp+)9T{~f?Hz50F;y&^J`QQ+B92*WQJYSV#5#} zXAwK+F0djE?BgxX{AKwUa5;kIJqVbD;V=4Pfi+iCSa?YRr(Q?Xs0mhtJ~$!OQA1hcZXZL0r%f(dy#l@J)3+Jvuo=k=&k3Yn zx|?(Nxo|N9u($GUkR~8b0m^cFyJX9b6jwUt4*u(q&-hxnZNh!Qtgv5P6ou zo#X-lKt_a*;iM{=ac1DWv!k6m)?W?Zji3)_OacHorc_+4=BgK$l-|hV@YCp*{X7PV zKi2qP@~l~AHCwVVx^}hY30Nqk&Yur?68-j;@PU_YkKFNp>WA{}b=_8R8hpEiA-=sr zR`Lu_1d8J>=x&jEN*eD*$dWUq3JGNZ9zL?WC{9z49_chKo3|Am!SHPwR%QU9%1=r#s0 zMQz~~QRO5AH2-Sp`Fl}}-?+-ida2(*RBTVCHe9;VCIYAOs3O(yuaw2uMcM$e19m(r z?7-n|nqcYvN6REuM)2~=8hZuB5Jlp~ulVFBwY54C@m*X*t;7di3rZ0@NHni_(uhiL z%l7#IM|^LQIZ6tS_~jYJY;T#WF+6X+2?ezzRV!lhwh#qAaae z1;P4c#??V0-T*ax>Avkm)D~;I=MnP+Td%IaCBvyM9zR)e2s+#Braq!w1)pj2-!k+| z$g&@f?06S5QL-hc9?Pq$WU`PSeIb%siHi@gGo8+;R3VO7cyJ9Kg@-r~ciTuu>o(0w z#{g4C2~(q}-iTegtibQy{&S*H3&xYXiKor zAYa2YZ1h`RR&y%8lxUD1GHz}#6|08o75$z46c<+<-_;J)`$)GN!G_C5%_(e(j>bPeO||U0uLwLyjN|Ds4EDI>SC$(^WkoD z#H?Bxir?fc8kxp_03oJmyq-P7i-h5nJZ%m^xsDdaRC7HXa6-;6ZHG)I?>->MGQM-j zTsA~cTf!MJ#2LX@N==ohf?79F zpN7Qj2Z}?te1c75L#btm_>=QVEwt!5CT1$E(Xx85Rhe)iK1^ZyC$d4pW zuT#DuR~Ii;jkda|!#f=Ieimf*lO8*H?7>Ehdn~6BHwR_NKeqT~aGnny)p@HA|9LnXOroR=pr9W4Uh=mJDf3V}9!Cd_i#< zcyCkl&mb`K;m*ByytF-;uAh!C!IKzO94_US|0Vgkla|aYWJ%+ES7Sx@qp7JkQ`c_&JrA)VqX&W;g4*n_F*9 z{{6CVt^7!Wq-V3SZ)yB5T*%+6Hkod&PxO zqBbpYU?AxgF_J6N2Xsp*o4oL+$DwL~SuH2Kf>-9by_tU(>0amrBPY1nJggWTJQo5I zYwdrkee_9xz{@2{zp7-L{?7p+grlo7{*T5kRN{48k4D+4Ar8>mQ~i{whS{yyBS zlrLA^QqV{4ft< zEMgTDdNRZuA|~2)-Buzw`q^mS=a&wtxahn;1SrjL7*j*f)48;D#6bjB3sbuk?)(56YAnb z(fiS&0}~9)FmVymNOpk}nN203qjImp1qV)!lDyOHlGss2ymlN0Ds#n`D|}nz$(phg z7#oOPjf9NdTY@gQ;+oFhv?SVXu2ASxa&YTc?{Un!>%NKB%iz(rYd;jORld)E0^Seo zM8@*7{gxf{u}rZqlC$-#Aj?qVSXy;pN(G~%cr zg9Gtc^p|~-5#dL)%~CL5e)&WavDNshc$Z|=>%j%3{3rZ-Vaa68aNlOD!2WNf75I_O zE50}lA$uWlo9>7=c&kISI{YucOu@{fDM45^T6|UuwW;1{5XIQ?>76ZJi5JWyZpFw3 z@Ug^L(bmF(X$^uadYd+z8X;imU!P{jni8aH^N;BsxjNfI&yPI5rX}(6V3{kLXI#)m zn$}Y$I=mKW^d{Jq(mqLlDGmWldUrf~-7BBV3(mbzK8r zTckq>f9hMuID2-0CLR&TWfz>PD=oBm)-P|Dn3BJrKk8ZQB&`-FDN`J}Y;G@lc&IjQ zj69X-_7YXr$n3Y(M&vg*4mpO2Vh&LULXAn4apWB_Y)HDzvr>!CfN2q5QB1*K2-b2= zhix7QhVolXqPRHJ-Qo#U|KucQDen4S&CvcVkN-%0NnkQP)MbH;xI;ID&o>Hw9R@J@ zP70~%eaqr+3c*6eh2Z+m3Vh?~dq8%WC0rxW8|meZ5;0-4qwio0Kc&XPRVG4j0K1mI z)h<`p9U172wBXj#>hH`_MgcTfsCmC07f(*pfkxg*L+n^6U_n;F4ZjQF!{dEN%cxljr%E~8=S3@TJ~*9Aq~bJF(xliVPWw=M5GU1M!}-EVknr$FjQ z%Nhb3`-Lv;ZBun#VECs8MKWQ^wi`sjG)w5D!h~(cH}Ik=J^B2}6>$Wbo(Lc%dF_V$ z25QUuIB66zVrCG5u4GT!1)O7*JfoI#yYx?^n*q7*+xu01t`Rj))&BdNV95iMq267% zx9yXca`RXbAi2|wUQzsR3S`tadbF4{6|fDo$Txd%p)UDa!mfpo%t^kHyh(N^Sjk)( zZrF>5@B#p8OQzN+;G_=XkbOgNVJTY117NLWvy?6eSM=} z;SI2keX#?vSR+``tpCpg~m>~^O;JW2IrRPNNg!p}#nry_!8`@K^#WfNYf&c|N2 zHE9Z8WaPnetx=(1bLcT5&I2yFkj+%B^wdI`nY~L1n(U)HCoqya9^Q%=GPWV5Ki$>@qaQF&Nm|EjJ5a!VFjMG5wLyuy zf;DS`iM1v5)(pT!&DM&vV`Brj81l0UNBIN)yMngP$=dXld~==gl-iaiRBZI%YYJ@g znMZfcK{rKug=!j`vAdUeK}7N8!l(tOst?zd5VTmZ8hnYosxDqzqy0evvs+ir0Evm)*h*P(A zaaB{n+NWGjmMiA38cN~lA%0!YgXq(C`?(Q9Lq3YUDYw>< z+<^`HTaI4W>YgilpixykWF`W8N(fAo4K^{e{jX(Cxh20D7U!!C^vo0L6e0P5@(bpn z8ZneDDOOFtqY?PWxz$O2=pi4>*v8)~auxds-4$ z12Z~Kv#u^`ZT6~DG>6gKr8JTo5~d8x?5xiy`c{u2$w>2(ZvQ_cx9N&2@0v8Fky`5i^i+Q21DYsE#T6YX znj(p~$gae>HoU5=A9?Oa(9@L)cs)rgSvD({wgv_^+Dnf1 z+p2e2s+y&_9|W`YNjdDfn%3m_#ol_qpQT@2rhA*OB4-b~5BmFfo~QoKWt24S>pR}7 zXXDVDGR2BDn^=dru~z3=wl@X7lKPoCSTH1yKg z*jY3f5n#&Eue1+0td66casO)r{(H5dOuU%S2jcrMl97%piZUWmznGftyj~Mgy!y#$ zK=79%T)LBo=+hXftEcdA;GkAZ3(!Xy_qLUF5S(29&1nUQS?W4uzB1`5P@OglQ}TBP zddrZRqoSEpT}&hM3d|jNk{d@d^Jsq#;;l5+MyWh?0FG#cm&qw{FA|CRv7}CKRT@AJ z41_?1x%U>(;}GGaEU++z(&(2z+^VU}A4m{%@B3Eeeay;^3}f!A)Em2XI$OMcz6p?9 z1-X2b>z({E85MOIOACSV4QFSqWOiAVp`M|!T%#K= z6!x4XECzMLlqy?<=&u?ZE8mthXZ6v`N)C?mo(Fu_5k_1B9+4+&due)oUWY?ArP!n+ zl~S5oT~W;*=7KNn-wzJ_L$gh{D-ydQTV&^xbO6F2C$H1Mv#iBgHxYrVy@Yj(6qh=G zSPN9t#W~0PZW?Z6o=?E#c;W$a?>sxqlpgzCLX2HnK|Eu~7mIiNU0xxAw@UpvzxRg~3 z!4Kc1W2EJtSKvY@SOB7;EP7=msoaPJaI6{eCpWn;@cQ)zRg8!{1XGI~Z2ilcybD8| zw-gl_TCLh=rYkr5exkK)3mbd2uO`|q&3));mV?NjI+5KGihgE+I}#Wl_+%(8bD5># zMEnGY>mtjdfHnBC<~|8MMqYDpmIVGDlKWbfmJ8))QNPu=h6iVrJ{0_k7)V@@*tY~H zTd&evmh4o+8=UE2A$`Z=qa72Rg8;lNxK3Ahob@@Z2~BT^sv>^wOH^Nv2WmJYA4%W9 zkFGMgd%W#wY$WyG&`+_m-d}LXttDntxzqbo7%?w<{OjBrw3tB$VBBuZZ|^5FuiAkC z!r3N0{t?xOkvmS}t;PcO8)!+-vJw>k{S) zdrvl|2@^p^u%w3k<%$_ogrdg!xfoaYe*lbUo(ojn^R%k!`@Bt^j!wnyg7!o0`I(ZY=!u!(HAI)*6U*DxvaG^5!D+w$MmP=^`-VS)icyR$yq* z!%Um2%b;9?O8#9=RmQHnSSQIf@!lprGCs|JpXpi7oV)YjaN0D=;Ycad*_k_4K=CEg zD$3Ll7as@TBSiiq59O&=yTxgaRz6Aqofi=$!8>Cso)s9If~jw={Gy<1-A&whX91b; zQ9@~(vaf#Iy~PEC_YE60rtw14xcOWj^nmDb5tX4n4O8YdA2UJ+Ym=>>a(bfdh#-Cw z+t2o*Y6#w+huz(YY?UTivOhAeG{Es)iJZouz9V4`Wyg>_3$Wc+jg)GzjD30Um58J$ zd-LB{yG3KJa^B#?*PpRqFkTLjy2vJAtQEcL-FhqEUd$%k3CucAsMtA+%Huvi3vrW> zRXIQDiMp{6jI||NHP9cYjF#_@G3JhM4mySD=I_+D^;qQxdrH0QMw>1(I_$7mxc%9R zF}tY>`*x=W(pc;M`jp)z?KJ>5o~dCI10p2!V>H{Nt+E9^QoF%R5fL zGA5EOxxmX%;n&nTEx;cEK3<92QV}<`VftL5-4p}jSAc(F8m`)Sthw*|bM?xa-4{z_Z20O$p?- z=R!yO5Btjy({bY=MfgO>U~mfMX{t=UQG4H%>?>i)E>G=-3#nWCmlKO-6C(a8i-j5o zWBHD+&dsf#afDaPDC?XCN_k9VpWXg1B)%csq*uc@TVlfdHThLAkOW{^L9ZLX0?apD zidpxs{z7bDYy~vwX{GOYr8p&gkY5II{jB|mqWJs0a+fS;imOl*LXRoFrGMyM?ynDj z+o%<_#b_r;MvWE_Iv3WN{+}FK#m2AXR9* z!Jsw;6%=}TtKa~qiz~e*bHU!CJ)PrUH(p8QM4H161?~2~Qp$fk6irb5;-m5Aio3o0 zNjOdVQb9P5FanMxr?O-L!bG<6XY(2rF1@R({}h4+L&J8TeD;(6jQsVu+n{azovl5uzbz9= zgRtxmavk@iyRL@V?l3Kue6J$SM6G%gwjFE?FEa)q9!~FfjcxS5jGEf)kfhuth5qgj zA^7#e?p8c|?Mr(PIw@*X6&w4mAtFO6&%*dN;XlC4S63?%o@dz|JUKTpZcj?KSl9iy zH=0hvhHe7=hE6(2 z3z^Y&p}EBXjcn?u6yk9)FU;qs-*AL%8iML#cs8reV$U#NeMY;;Q2#%GCAwSYnxMiJ z2H)IM7r{mK+N&Xe1=3eT>Ik7D{A9wZWJYx{6*$Ezce!keYT@r)4yQB-v%F;}MM+f! zP~8r3lNriCwZtWdDvUL79-jbv)HMd2@MpLD$|C4A)2w#mGfd$ANQ}uB4`}cUUkp0p}`Ahu1zsKai%&hS#(q&qz_nFeI z$vfw~%?XUxiyTN_P0j2G2XCP^z1rmIqry$}nVIdvtor`|3_*{YTEaY7<44d1h zSmRJS<&Fk@hd!CDtzY2wwWMEZwilN$v){;4%tHQRl6fHHjlRB~^)_99GSS9dY4TXI zCB?HNW0fue`ukHOw|7|>S39 z1ZUn zkjHOs%-g5i^6*|hGx!r*`ZkxSYKIoL{!A!NU(O_Cdw-2&EyHVirU@~b6e22(-3L$S z`PBaa+8T$4#Fp_RK$4vKjTsEs?lbkFStnwR`(GB_{{U|#jv|%~{#cM_b9Tu-zlC+- zKw?1T^)-{>`xw8o?XF%pWVvM|%(18^Cp|zFu(jw@i%Rmy>uH*N_ST(F6!f1f~h3n0g`J4 zymz9kg?`jd4mSwckL6nWg~HoeTser(u?aA!$!ZCgHF*Vs8*Ew_2pPGTg^1%;cjD*xQ@|)386KciMM}JUMZv+6~i3bsDT@J)2Jh z8To!*ah!YC38+|-(XvSliMI>ozaS4#c@>_!t>nlhU$uGiyJjr(?MqMBb#jwv_d9L# zjI#msJu8*e^xa<0ZMXNZv%f|z4nRJ*%`(Tu+LoPeRx6FpM$M%#0{V<&oaghRZDP|{ zM0?n#OSA<M{w>S~;ZtCKqOOa@<>?WtK!%;FTvC$4{?Xh(^(_8z`(Y3l6_Z z*oQ^7)FfD}Z=U|~zU{i(GLw^xbKjBdFllvf0c+kLwEJG6rCeIud7A{1C<8q}?}~?O z*bx_pH25{mN;^x5*3#c@T4u-dJXg?P2YwIu4*E-(^=(ATiU^M2-KsAgMgYTO9X`1| z1$ZW{tZ8%U5!=CQD%~jsrC_5c)R1|rehX{p)@f~Y>1TwTJ;KIVE&$^oW4Qds;w^gE zy_xJDIq>#}p=yTPM!8$7ZsURn;yTwYHH0%l@f1)TVBtkaHl&(;lFxoV%b($3bNO|r zY1*x}s}YjgPccEk;DSE`UYspA4-Zjla2E2-^(hDk_Q;hGBeZL12pM>)J^{YT;KlyK{cCc zcvVq6%CmP3%Ws`{ZZ72&tK5XAm5-$sQlRLX4>% zgJ>Lc(yINTi@ON}O&}!7*un4L+#DVc%qqk)M{6Ga{(Oyrg+Fu;{;|hGJ?l%Ek9nR* zXOv{_Siw~XoN?>yI-0X1qRD{32{E$(5;510b5UvfUFG$<$72NdgE(eX{n)_=CAtoy zIl$(-jSJyx`O!(M&kPFtcG|}%=Psazd|(_C#&euha}LKXr`g?W^9U^=+<9Vm0k9u_ z-8yyj73$s}@a>+0u-jY973JiL(mm3rn6eSLDB3~WfOE%Qr=Y}^QOjv+V6b^Dptnu3 z1h?KI7#sq*4cDt}&OLaoc$O&RiaUrWyZLgkEbL-Z8z5~TH$3z{y|G!jT%?HY<7sz@ z;|m-rmv`jt%x@ zj?#RhW*h;uWOV39&D4*?-J0$TA2oJC1WNdj$m-#V1O-)cz~gx10F&x!3M>BrXR6;8 zSvgfv{-{bD3zArd3c1|CmKrO2x3d zyVRrGW1!t!+y+=9lIkBK7Hkom0L_EDj4GZuAlFf#d@J#$xi!LTS8Z=#s>rrFJ-aAO z;|OJR-Ve+N%17PnMaN?^`%d3Og`~f@TX-2fh=aIEUoVo|GD+?}QP_+Ml1r^aR)r(d z^m~h?opwbv)yI(%@J8aSto?b%L--o*{4e`HYSuSRX=SIagU_~h^Gv0WILUMK7e6rM zft5XoC(^X<*~e6h@(HePZR56m@<--A$oClfpekATd>oVaPa~~MoKZ?A%$N3lCOUw) zeO}JmTbUTlsbS_}!4H7OPI+ZNE;{2Kk42@&s#xAwL4KCkuRECD;?>&DNN|OavOZOY z6pjuC-ayYES81QK&XuF;nqZzGZRSE=X>}HsA&d}&DKE%8bp(!v`9Fc-Cb-asEK5Nl_1nalp0wA(pT>{Jd#~Ar?gaQd5jBrWMPW@J!@C3rlx`~cR znih7Fc@

jje!O5)_W%PjQkf-&qZ;G0Mz;oYc8V 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 z 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%0ArHKl>!^IKo{dQT5`P|*H@$Z`6hdu#@A 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 E0bPk?YybcN literal 0 HcmV?d00001 diff --git a/docs/source/components/nodes/stereo_depth.rst b/docs/source/components/nodes/stereo_depth.rst index 49a66c975..80ed6d362 100644 --- a/docs/source/components/nodes/stereo_depth.rst +++ b/docs/source/components/nodes/stereo_depth.rst @@ -1,7 +1,8 @@ StereoDepth ########### -StereoDepth node calculates the disparity/depth from the stereo camera pair (2x :ref:`MonoCamera `/:ref:`ColorCamera`). +StereoDepth node calculates the disparity and/or depth from the stereo camera pair (2x :ref:`MonoCamera `/:ref:`ColorCamera`). +We suggest following :ref:`Configuring Stereo Depth` tutorial to achieve the best depth results. How to place it =============== diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst index 1562fd5e4..91686e9f9 100644 --- a/docs/source/tutorials/configuring-stereo-depth.rst +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -287,7 +287,7 @@ Stereo subpixel effect on layering Default stereo depth output has 0..95 disparity pixels, which would produce 96 unique depth values. This can especially be seen when using pointcloud representation and seeing how there are discrete "layers" of points, instead of a smooth transition: -.. image:: /_static/images/components/layered-pointcloud.png +.. image:: /_static/images/components/pointcloud_layering.jpg This layering can especially be seen at longer distances, where these layers are exponentially further apart. diff --git a/docs/source/tutorials/low-latency.rst b/docs/source/tutorials/low-latency.rst index 978e467cb..5e88a2b4a 100644 --- a/docs/source/tutorials/low-latency.rst +++ b/docs/source/tutorials/low-latency.rst @@ -133,7 +133,7 @@ Bandwidth With large, unencoded frames, one can quickly saturate the bandwidth even at 30FPS, especially on PoE devices (1gbps link): -.. code-block::bash +.. code-block:: 4K NV12/YUV420 frames: 3840 * 2160 * 1.5 * 30fps * 8bits = 3 gbps 1080P NV12/YUV420 frames: 1920 * 1080 * 1.5 * 30fps * 8bits = 747 mbps From 516860e7e897a89a1b168ada1c1ae8a5bad23b1e Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 5 Apr 2023 18:58:17 +0300 Subject: [PATCH 265/385] FW: Fix camera intrinsics when RGB alignment is used --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 0cd6c14ec..ffd0546d4 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 0cd6c14ecdf205006b362edc8115b910531d5f08 +Subproject commit ffd0546d426dd2870898b6b8c8dc1ad1615815f8 From bfe65785b69c99f5ad96762ee480a7f30fa46075 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 5 Apr 2023 21:25:19 +0300 Subject: [PATCH 266/385] Release v2.21.2.0 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index ffd0546d4..125feb8c2 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit ffd0546d426dd2870898b6b8c8dc1ad1615815f8 +Subproject commit 125feb8c2e16ee4bf71b7873a7b990f1c5f17b18 From 6eff02ea137a3e71569748f759341e964529b9b4 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Thu, 6 Apr 2023 21:14:25 +0200 Subject: [PATCH 267/385] Fix disparity shift figure - typo --- .../images/components/disparity_shift.png | Bin 0 -> 36229 bytes .../tutorials/configuring-stereo-depth.rst | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/source/_static/images/components/disparity_shift.png diff --git a/docs/source/_static/images/components/disparity_shift.png b/docs/source/_static/images/components/disparity_shift.png new file mode 100644 index 0000000000000000000000000000000000000000..83a3b5ee5bd5b454e88f5ac0a55d8b696ecfc757 GIT binary patch literal 36229 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 literal 0 HcmV?d00001 diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst index 1562fd5e4..1321e99b3 100644 --- a/docs/source/tutorials/configuring-stereo-depth.rst +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -375,7 +375,7 @@ as the other 2 methods above do. Disparity shift will shift the starting point of the disparity search, which will significantly decrease MaxZ, but it will also decrease the MinZ. Disparity shift can be combined with extended/subpixel/LR-check modes. -.. image:: https://user-images.githubusercontent.com/18037362/189375017-2fa137d2-ad6b-46de-8899-6304bbc6c9d7.png +.. image:: /_static/images/components/disparity_shift.png The **Left graph** shows min and max disparity and depth for OAK-D (7.5cm baseline, 800P resolution, ~70° HFOV) by default (disparity shift=0). See :ref:`Depth from disparity`. Since hardware (stereo block) has a fixed 95 pixel disparity search, DepthAI will search from 0 pixels (depth=INF) to 95 pixels (depth=71cm). From cd07f49a246962f18fc2375c20313f1c3c61194b Mon Sep 17 00:00:00 2001 From: Erol444 Date: Thu, 13 Apr 2023 18:18:59 +0200 Subject: [PATCH 268/385] Started updating depth colorization logic --- .../SpatialDetection/spatial_calculator_multi_roi.py | 9 ++++++--- examples/mixed/rotated_spatial_detections.py | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/examples/SpatialDetection/spatial_calculator_multi_roi.py b/examples/SpatialDetection/spatial_calculator_multi_roi.py index dbf7c6fce..4752f5bfd 100755 --- a/examples/SpatialDetection/spatial_calculator_multi_roi.py +++ b/examples/SpatialDetection/spatial_calculator_multi_roi.py @@ -3,6 +3,7 @@ import cv2 import depthai as dai import math +import numpy as np # Create pipeline pipeline = dai.Pipeline() @@ -29,7 +30,7 @@ stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) stereo.setLeftRightCheck(True) -stereo.setExtendedDisparity(True) +stereo.setSubpixel(True) spatialLocationCalculator.inputConfig.setWaitForMessage(False) # Create 10 ROIs @@ -65,8 +66,10 @@ depthFrame = inDepth.getFrame() # depthFrame values are in millimeters - depthFrameColor = cv2.normalize(depthFrame, None, 255, 0, cv2.NORM_INF, cv2.CV_8UC1) - depthFrameColor = cv2.equalizeHist(depthFrameColor) + depth_downscaled = depthFrame[::4] + min_depth = np.percentile(depth_downscaled[depth_downscaled != 0], 1) + max_depth = np.percentile(depth_downscaled, 99) + depthFrameColor = np.interp(depthFrame, (min_depth, max_depth), (0, 255)).astype(np.uint8) depthFrameColor = cv2.applyColorMap(depthFrameColor, cv2.COLORMAP_HOT) spatialData = spatialCalcQueue.get().getSpatialLocations() diff --git a/examples/mixed/rotated_spatial_detections.py b/examples/mixed/rotated_spatial_detections.py index 3b386a48b..b3ad05d80 100755 --- a/examples/mixed/rotated_spatial_detections.py +++ b/examples/mixed/rotated_spatial_detections.py @@ -4,7 +4,7 @@ import sys import cv2 import depthai as dai - +import numpy as np ''' Spatial object detections demo for 180° rotated OAK camera. ''' @@ -58,6 +58,7 @@ stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) # Align depth map to the perspective of RGB camera, on which inference is done stereo.setDepthAlign(dai.CameraBoardSocket.RGB) +stereo.setSubpixel(True) stereo.setOutputSize(monoLeft.getResolutionWidth(), monoLeft.getResolutionHeight()) rotate_stereo_manip = pipeline.createImageManip() @@ -104,8 +105,10 @@ frame = inPreview.getCvFrame() depthFrame = depth.getFrame() # depthFrame values are in millimeters - depthFrameColor = cv2.normalize(depthFrame, None, 255, 0, cv2.NORM_INF, cv2.CV_8UC1) - depthFrameColor = cv2.equalizeHist(depthFrameColor) + depth_downscaled = depthFrame[::4] + min_depth = np.percentile(depth_downscaled[depth_downscaled != 0], 1) + max_depth = np.percentile(depth_downscaled, 99) + depthFrameColor = np.interp(depthFrame, (min_depth, max_depth), (0, 255)).astype(np.uint8) depthFrameColor = cv2.applyColorMap(depthFrameColor, cv2.COLORMAP_HOT) detections = inDet.detections From b0fa0fe3b3723311b0fa40250be918b9d479269a Mon Sep 17 00:00:00 2001 From: Erol444 Date: Thu, 13 Apr 2023 18:29:47 +0200 Subject: [PATCH 269/385] Updated other examples as well that used old colorization logic --- .../spatial_calculator_multi_roi.rst | 2 +- docs/source/tutorials/code_samples.rst | 1 + .../spatial_location_calculator.py | 23 +++++++++---------- .../SpatialDetection/spatial_mobilenet.py | 9 +++++--- .../spatial_mobilenet_mono.py | 7 ++++-- .../SpatialDetection/spatial_tiny_yolo.py | 9 +++++--- examples/StereoDepth/depth_crop_control.py | 7 ++++-- 7 files changed, 35 insertions(+), 23 deletions(-) diff --git a/docs/source/samples/SpatialDetection/spatial_calculator_multi_roi.rst b/docs/source/samples/SpatialDetection/spatial_calculator_multi_roi.rst index 10392c5e6..585a5630f 100644 --- a/docs/source/samples/SpatialDetection/spatial_calculator_multi_roi.rst +++ b/docs/source/samples/SpatialDetection/spatial_calculator_multi_roi.rst @@ -11,7 +11,7 @@ scanning camera for mobile robots. Demo #### -.. image:: https://user-images.githubusercontent.com/18037362/190861621-b57fd1e3-5a3d-4d79-b1a7-d17a0b78c63e.gif +.. image:: https://user-images.githubusercontent.com/18037362/231822498-6e3699a0-039e-424b-acb2-b246575e91ee.png Setup ##### diff --git a/docs/source/tutorials/code_samples.rst b/docs/source/tutorials/code_samples.rst index 46127c230..a7b72aefa 100644 --- a/docs/source/tutorials/code_samples.rst +++ b/docs/source/tutorials/code_samples.rst @@ -125,6 +125,7 @@ are presented with code. .. rubric:: SpatialDetection +- :ref:`Spatial Calculator Multi-ROI` - Selects multiple ROIs and calculates spatial coordinates for each of them - :ref:`Spatial location calculator` - Demonstrates how to use the spatial location calculator - :ref:`RGB & MobilenetSSD with spatial data` - Displays RGB frames with MobileNet detections and spatial coordinates on them - :ref:`Mono & MobilenetSSD with spatial data` - Displays mono frames with MobileNet detections and spatial coordinates on them diff --git a/examples/SpatialDetection/spatial_location_calculator.py b/examples/SpatialDetection/spatial_location_calculator.py index 8e30f0a3c..4f2779dfd 100755 --- a/examples/SpatialDetection/spatial_location_calculator.py +++ b/examples/SpatialDetection/spatial_location_calculator.py @@ -2,7 +2,7 @@ import cv2 import depthai as dai - +import numpy as np stepSize = 0.05 newConfig = False @@ -30,12 +30,9 @@ monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) -lrcheck = False -subpixel = False - stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) -stereo.setLeftRightCheck(lrcheck) -stereo.setSubpixel(subpixel) +stereo.setLeftRightCheck(True) +stereo.setSubpixel(True) # Config topLeft = dai.Point2f(0.4, 0.4) @@ -77,8 +74,10 @@ depthFrame = inDepth.getFrame() # depthFrame values are in millimeters - depthFrameColor = cv2.normalize(depthFrame, None, 255, 0, cv2.NORM_INF, cv2.CV_8UC1) - depthFrameColor = cv2.equalizeHist(depthFrameColor) + depth_downscaled = depthFrame[::4] + min_depth = np.percentile(depth_downscaled[depth_downscaled != 0], 1) + max_depth = np.percentile(depth_downscaled, 99) + depthFrameColor = np.interp(depthFrame, (min_depth, max_depth), (0, 255)).astype(np.uint8) depthFrameColor = cv2.applyColorMap(depthFrameColor, cv2.COLORMAP_HOT) spatialData = spatialCalcQueue.get().getSpatialLocations() @@ -94,10 +93,10 @@ depthMax = depthData.depthMax fontType = cv2.FONT_HERSHEY_TRIPLEX - cv2.rectangle(depthFrameColor, (xmin, ymin), (xmax, ymax), color, cv2.FONT_HERSHEY_SCRIPT_SIMPLEX) - cv2.putText(depthFrameColor, f"X: {int(depthData.spatialCoordinates.x)} mm", (xmin + 10, ymin + 20), fontType, 0.5, 255) - cv2.putText(depthFrameColor, f"Y: {int(depthData.spatialCoordinates.y)} mm", (xmin + 10, ymin + 35), fontType, 0.5, 255) - cv2.putText(depthFrameColor, f"Z: {int(depthData.spatialCoordinates.z)} mm", (xmin + 10, ymin + 50), fontType, 0.5, 255) + cv2.rectangle(depthFrameColor, (xmin, ymin), (xmax, ymax), color, 1) + cv2.putText(depthFrameColor, f"X: {int(depthData.spatialCoordinates.x)} mm", (xmin + 10, ymin + 20), fontType, 0.5, color) + cv2.putText(depthFrameColor, f"Y: {int(depthData.spatialCoordinates.y)} mm", (xmin + 10, ymin + 35), fontType, 0.5, color) + cv2.putText(depthFrameColor, f"Z: {int(depthData.spatialCoordinates.z)} mm", (xmin + 10, ymin + 50), fontType, 0.5, color) # Show the frame cv2.imshow("depth", depthFrameColor) diff --git a/examples/SpatialDetection/spatial_mobilenet.py b/examples/SpatialDetection/spatial_mobilenet.py index ec2eff715..b67757b44 100755 --- a/examples/SpatialDetection/spatial_mobilenet.py +++ b/examples/SpatialDetection/spatial_mobilenet.py @@ -60,6 +60,7 @@ stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) # Align depth map to the perspective of RGB camera, on which inference is done stereo.setDepthAlign(dai.CameraBoardSocket.RGB) +stereo.setSubpixel(True) stereo.setOutputSize(monoLeft.getResolutionWidth(), monoLeft.getResolutionHeight()) spatialDetectionNetwork.setBlobPath(nnBlobPath) @@ -113,8 +114,10 @@ depthFrame = depth.getFrame() # depthFrame values are in millimeters - depthFrameColor = cv2.normalize(depthFrame, None, 255, 0, cv2.NORM_INF, cv2.CV_8UC1) - depthFrameColor = cv2.equalizeHist(depthFrameColor) + depth_downscaled = depthFrame[::4] + min_depth = np.percentile(depth_downscaled[depth_downscaled != 0], 1) + max_depth = np.percentile(depth_downscaled, 99) + depthFrameColor = np.interp(depthFrame, (min_depth, max_depth), (0, 255)).astype(np.uint8) depthFrameColor = cv2.applyColorMap(depthFrameColor, cv2.COLORMAP_HOT) detections = inDet.detections @@ -132,7 +135,7 @@ ymin = int(topLeft.y) xmax = int(bottomRight.x) ymax = int(bottomRight.y) - cv2.rectangle(depthFrameColor, (xmin, ymin), (xmax, ymax), color, cv2.FONT_HERSHEY_SCRIPT_SIMPLEX) + cv2.rectangle(depthFrameColor, (xmin, ymin), (xmax, ymax), color, 1) # Denormalize bounding box x1 = int(detection.xmin * width) diff --git a/examples/SpatialDetection/spatial_mobilenet_mono.py b/examples/SpatialDetection/spatial_mobilenet_mono.py index 30a237d2f..f6c265071 100755 --- a/examples/SpatialDetection/spatial_mobilenet_mono.py +++ b/examples/SpatialDetection/spatial_mobilenet_mono.py @@ -59,6 +59,7 @@ # StereoDepth stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) +stereo.setSubpixel(True) # Define a neural network that will make predictions based on the source frames spatialDetectionNetwork.setConfidenceThreshold(0.5) @@ -116,8 +117,10 @@ depthFrame = inDepth.getFrame() # depthFrame values are in millimeters - depthFrameColor = cv2.normalize(depthFrame, None, 255, 0, cv2.NORM_INF, cv2.CV_8UC1) - depthFrameColor = cv2.equalizeHist(depthFrameColor) + depth_downscaled = depthFrame[::4] + min_depth = np.percentile(depth_downscaled[depth_downscaled != 0], 1) + max_depth = np.percentile(depth_downscaled, 99) + depthFrameColor = np.interp(depthFrame, (min_depth, max_depth), (0, 255)).astype(np.uint8) depthFrameColor = cv2.applyColorMap(depthFrameColor, cv2.COLORMAP_HOT) detections = inDet.detections diff --git a/examples/SpatialDetection/spatial_tiny_yolo.py b/examples/SpatialDetection/spatial_tiny_yolo.py index 5575bccd6..0158f2b9e 100755 --- a/examples/SpatialDetection/spatial_tiny_yolo.py +++ b/examples/SpatialDetection/spatial_tiny_yolo.py @@ -84,6 +84,7 @@ # Align depth map to the perspective of RGB camera, on which inference is done stereo.setDepthAlign(dai.CameraBoardSocket.RGB) stereo.setOutputSize(monoLeft.getResolutionWidth(), monoLeft.getResolutionHeight()) +stereo.setSubpixel(True) spatialDetectionNetwork.setBlobPath(nnBlobPath) spatialDetectionNetwork.setConfidenceThreshold(0.5) @@ -146,8 +147,10 @@ frame = inPreview.getCvFrame() depthFrame = depth.getFrame() # depthFrame values are in millimeters - depthFrameColor = cv2.normalize(depthFrame, None, 255, 0, cv2.NORM_INF, cv2.CV_8UC1) - depthFrameColor = cv2.equalizeHist(depthFrameColor) + depth_downscaled = depthFrame[::4] + min_depth = np.percentile(depth_downscaled[depth_downscaled != 0], 1) + max_depth = np.percentile(depth_downscaled, 99) + depthFrameColor = np.interp(depthFrame, (min_depth, max_depth), (0, 255)).astype(np.uint8) depthFrameColor = cv2.applyColorMap(depthFrameColor, cv2.COLORMAP_HOT) counter+=1 @@ -172,7 +175,7 @@ ymin = int(topLeft.y) xmax = int(bottomRight.x) ymax = int(bottomRight.y) - cv2.rectangle(depthFrameColor, (xmin, ymin), (xmax, ymax), color, cv2.FONT_HERSHEY_SCRIPT_SIMPLEX) + cv2.rectangle(depthFrameColor, (xmin, ymin), (xmax, ymax), color, 1) # Denormalize bounding box x1 = int(detection.xmin * width) diff --git a/examples/StereoDepth/depth_crop_control.py b/examples/StereoDepth/depth_crop_control.py index eae2677a4..a9a2c7a69 100755 --- a/examples/StereoDepth/depth_crop_control.py +++ b/examples/StereoDepth/depth_crop_control.py @@ -40,6 +40,7 @@ manip.initialConfig.setCropRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y) manip.setMaxOutputFrameSize(monoRight.getResolutionHeight()*monoRight.getResolutionWidth()*3) stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) +stereo.setSubpixel(True) # Linking configIn.out.link(manip.inputConfig) @@ -62,8 +63,10 @@ depthFrame = inDepth.getFrame() # depthFrame values are in millimeters # Frame is transformed, the color map will be applied to highlight the depth info - depthFrameColor = cv2.normalize(depthFrame, None, 255, 0, cv2.NORM_INF, cv2.CV_8UC1) - depthFrameColor = cv2.equalizeHist(depthFrameColor) + depth_downscaled = depthFrame[::4] + min_depth = np.percentile(depth_downscaled[depth_downscaled != 0], 1) + max_depth = np.percentile(depth_downscaled, 99) + depthFrameColor = np.interp(depthFrame, (min_depth, max_depth), (0, 255)).astype(np.uint8) depthFrameColor = cv2.applyColorMap(depthFrameColor, cv2.COLORMAP_HOT) # Frame is ready to be shown From 2f674695307bcca8042c46945f2b0a9b00f10e94 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Thu, 13 Apr 2023 20:15:34 +0300 Subject: [PATCH 270/385] FW: fix running 4 cameras with Ethernet, 4th cam enabled didn't stream due to device memory allocation --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 125feb8c2..c4af66caa 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 125feb8c2e16ee4bf71b7873a7b990f1c5f17b18 +Subproject commit c4af66caaf544ba1b409bee9ead30cd8ba3deda4 From 0c9fd43356609f7be4dc580f34f1a7cb53bd775a Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 17 Apr 2023 12:28:12 +0200 Subject: [PATCH 271/385] Added UART example from Script node --- docs/source/samples/Script/script_uart.rst | 40 ++++++++++++++++++++++ docs/source/tutorials/code_samples.rst | 1 + examples/Script/script_uart.py | 36 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 docs/source/samples/Script/script_uart.rst create mode 100644 examples/Script/script_uart.py diff --git a/docs/source/samples/Script/script_uart.rst b/docs/source/samples/Script/script_uart.rst new file mode 100644 index 000000000..7c1eec07c --- /dev/null +++ b/docs/source/samples/Script/script_uart.rst @@ -0,0 +1,40 @@ +Script UART communication +========================= + +This example uses :ref:`Script` node for `UART communication `__. Note that OAK +cameras don't have UART pins easily disposed, and we soldered wires on `OAK-FFC-4P `__ +to expose UART pins. + +Demo +#### + +.. raw:: html + +

+ +
+ +Setup +##### + +.. include:: /includes/install_from_pypi.rst + +Source code +########### + +.. tabs:: + + .. tab:: Python + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../examples/Script/script_uart.py + :language: python + :linenos: + + .. tab:: C++ + + Not yet available + + +.. include:: /includes/footer-short.rst diff --git a/docs/source/tutorials/code_samples.rst b/docs/source/tutorials/code_samples.rst index 46127c230..d8ada96f5 100644 --- a/docs/source/tutorials/code_samples.rst +++ b/docs/source/tutorials/code_samples.rst @@ -122,6 +122,7 @@ are presented with code. - :ref:`Script HTTP server` - Serve still image over HTTP response (only OAK-POE devices) - :ref:`Script MJPEG server` - Serve MJPEG video stream over HTTP response (only OAK-POE devices) - :ref:`Script NNData example` - Constructs :ref:`NNData` in Script node and sends it to the host +- :ref:`Script UART communication` - UART communication with Script node .. rubric:: SpatialDetection diff --git a/examples/Script/script_uart.py b/examples/Script/script_uart.py new file mode 100644 index 000000000..d20bc8b47 --- /dev/null +++ b/examples/Script/script_uart.py @@ -0,0 +1,36 @@ +import depthai as dai +import time + +# Start defining a pipeline +pipeline = dai.Pipeline() + +script = pipeline.create(dai.node.Script) +script.setScript(""" + import serial + import time + + ser = serial.Serial("/dev/ttyS0", baudrate=115200) + i = 0 + while True: + i += 1 + time.sleep(0.1) + serString = f'TEST_{i}' + ser.write(serString.encode()) +""") +# Define script for output +script.setProcessor(dai.ProcessorType.LEON_CSS) + + +config = dai.Device.Config() +# Get argument first +GPIO = dai.BoardConfig.GPIO +config.board.gpio[15] = GPIO(GPIO.OUTPUT, GPIO.ALT_MODE_2) +config.board.gpio[16] = GPIO(GPIO.INPUT, GPIO.ALT_MODE_2) +config.board.uart[0] = dai.BoardConfig.UART() + + +with dai.Device(config) as device: + device.startPipeline(pipeline) + print("Pipeline started") + while True: + time.sleep(1) From e155231443c7bd0b144996228c0569b06631a61f Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 17 Apr 2023 12:29:46 +0200 Subject: [PATCH 272/385] Added docs image --- docs/source/samples/Script/script_uart.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/samples/Script/script_uart.rst b/docs/source/samples/Script/script_uart.rst index 7c1eec07c..e56ce7f08 100644 --- a/docs/source/samples/Script/script_uart.rst +++ b/docs/source/samples/Script/script_uart.rst @@ -14,6 +14,11 @@ Demo + +.. figure:: https://user-images.githubusercontent.com/18037362/232458809-a36dc418-6bb5-411f-9172-5130a926191d.jpg + + Osciloscope connected to the OAK-FFC-4P UART pins + Setup ##### From 7da506aebff3b394d7ee0d40305b92a3a56c265e Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 17 Apr 2023 13:09:38 +0200 Subject: [PATCH 273/385] Fix typo --- docs/source/samples/Script/script_uart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/samples/Script/script_uart.rst b/docs/source/samples/Script/script_uart.rst index e56ce7f08..e046b6031 100644 --- a/docs/source/samples/Script/script_uart.rst +++ b/docs/source/samples/Script/script_uart.rst @@ -17,7 +17,7 @@ Demo .. figure:: https://user-images.githubusercontent.com/18037362/232458809-a36dc418-6bb5-411f-9172-5130a926191d.jpg - Osciloscope connected to the OAK-FFC-4P UART pins + Oscilloscope connected to the OAK-FFC-4P UART pins Setup ##### From a652f428834fd2c287f08cfad06af0caa4a612ef Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 18 Apr 2023 03:23:22 +0200 Subject: [PATCH 274/385] [FW] OAK-D-LR R1 preparation, OAK-D-SR camera enumeration fix --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index c4af66caa..53198b310 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit c4af66caaf544ba1b409bee9ead30cd8ba3deda4 +Subproject commit 53198b31067c97448051c64a39dd49cf5bc35647 From f03c4e5c09f2b441155ccfe3fdf1ad3ca8b39cee Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Wed, 19 Apr 2023 00:25:29 +0200 Subject: [PATCH 275/385] [FW / BL] Updated both FW & BL for OAK-D-LR R1. ETH fixes and moved to BNO086 IMU. --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 53198b310..c03bfeab6 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 53198b31067c97448051c64a39dd49cf5bc35647 +Subproject commit c03bfeab670ab4e325fec5ab8c23fb5804759a90 From 92f4425f0ecdcb48703a147510f1489b773cb150 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 20 Apr 2023 01:59:32 +0200 Subject: [PATCH 276/385] [BL] Updated to 0.0.25 release --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index c03bfeab6..8a1316f34 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit c03bfeab670ab4e325fec5ab8c23fb5804759a90 +Subproject commit 8a1316f343fd841fe5ad8e1618d17b2a31ad33b3 From 4297c410496fc62de63098caf4b879c279f88313 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Sun, 23 Apr 2023 15:26:05 +0200 Subject: [PATCH 277/385] Deprecated misleading board socket aliases --- depthai-core | 2 +- docs/source/components/nodes/color_camera.rst | 4 +- docs/source/components/nodes/mono_camera.rst | 4 +- docs/source/components/nodes/stereo_depth.rst | 215 ++++++++++++++++++ .../samples/host_side/device_information.rst | 2 +- examples/AprilTag/apriltag.py | 2 +- examples/AprilTag/apriltag_rgb.py | 2 +- examples/ColorCamera/rgb_isp_scale.py | 2 +- examples/ColorCamera/rgb_undistort.py | 2 +- examples/ColorCamera/rgb_video.py | 2 +- examples/EdgeDetector/edge_detector.py | 6 +- examples/FeatureTracker/feature_detector.py | 4 +- examples/FeatureTracker/feature_tracker.py | 4 +- examples/ImageManip/image_manip_rotate.py | 2 +- examples/MobileNet/mono_mobilenet.py | 2 +- examples/MonoCamera/mono_camera_control.py | 4 +- .../MonoCamera/mono_full_resolution_saver.py | 2 +- examples/MonoCamera/mono_preview.py | 4 +- .../MonoCamera/mono_preview_alternate_pro.py | 4 +- examples/NeuralNetwork/concat_multi_input.py | 4 +- .../ObjectTracker/spatial_object_tracker.py | 6 +- .../Script/script_change_pipeline_flow.py | 2 +- .../spatial_calculator_multi_roi.py | 4 +- .../spatial_location_calculator.py | 4 +- .../SpatialDetection/spatial_mobilenet.py | 6 +- .../spatial_mobilenet_mono.py | 4 +- .../SpatialDetection/spatial_tiny_yolo.py | 6 +- examples/StereoDepth/depth_colormap.py | 4 +- examples/StereoDepth/depth_crop_control.py | 4 +- examples/StereoDepth/depth_post_processing.py | 4 +- examples/StereoDepth/depth_preview.py | 4 +- examples/StereoDepth/depth_preview_lr.py | 4 +- examples/StereoDepth/rgb_depth_aligned.py | 10 +- .../rgb_depth_confidence_aligned.py | 10 +- .../StereoDepth/stereo_depth_from_host.py | 4 +- examples/StereoDepth/stereo_depth_video.py | 16 +- .../disparity_colormap_encoding.py | 4 +- examples/VideoEncoder/disparity_encoding.py | 4 +- examples/VideoEncoder/encoding_max_limit.py | 6 +- examples/VideoEncoder/rgb_encoding.py | 2 +- .../VideoEncoder/rgb_full_resolution_saver.py | 4 +- examples/VideoEncoder/rgb_mono_encoding.py | 6 +- examples/calibration/calibration_load.py | 4 +- examples/calibration/calibration_reader.py | 30 +-- examples/host_side/opencv_support.py | 2 +- examples/host_side/queue_add_callback.py | 4 +- examples/mixed/frame_sync.py | 4 +- examples/mixed/mono_depth_mobilenetssd.py | 4 +- examples/mixed/multiple_devices.py | 6 +- examples/mixed/report_camera_settings.py | 2 +- examples/mixed/rgb_encoding_mobilenet.py | 2 +- examples/mixed/rgb_encoding_mono_mobilenet.py | 4 +- .../rgb_encoding_mono_mobilenet_depth.py | 6 +- examples/mixed/rotated_spatial_detections.py | 6 +- src/CalibrationHandlerBindings.cpp | 2 +- utilities/cam_test.py | 8 +- 56 files changed, 345 insertions(+), 130 deletions(-) diff --git a/depthai-core b/depthai-core index 8a1316f34..4c67a5197 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 8a1316f343fd841fe5ad8e1618d17b2a31ad33b3 +Subproject commit 4c67a5197f605aeeccfbdc97bc730ea836028bb4 diff --git a/docs/source/components/nodes/color_camera.rst b/docs/source/components/nodes/color_camera.rst index 6323af090..d9718f8f7 100644 --- a/docs/source/components/nodes/color_camera.rst +++ b/docs/source/components/nodes/color_camera.rst @@ -87,7 +87,7 @@ Usage pipeline = dai.Pipeline() cam = pipeline.create(dai.node.ColorCamera) cam.setPreviewSize(300, 300) - cam.setBoardSocket(dai.CameraBoardSocket.RGB) + cam.setBoardSocket(dai.CameraBoardSocket.CAM_A) cam.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) cam.setInterleaved(False) cam.setColorOrder(dai.ColorCameraProperties.ColorOrder.RGB) @@ -97,7 +97,7 @@ Usage dai::Pipeline pipeline; auto cam = pipeline.create(); cam->setPreviewSize(300, 300); - cam->setBoardSocket(dai::CameraBoardSocket::RGB); + cam->setBoardSocket(dai::CameraBoardSocket::CAM_A); cam->setResolution(dai::ColorCameraProperties::SensorResolution::THE_1080_P); cam->setInterleaved(false); cam->setColorOrder(dai::ColorCameraProperties::ColorOrder::RGB); diff --git a/docs/source/components/nodes/mono_camera.rst b/docs/source/components/nodes/mono_camera.rst index 3995de685..e5b278337 100644 --- a/docs/source/components/nodes/mono_camera.rst +++ b/docs/source/components/nodes/mono_camera.rst @@ -48,14 +48,14 @@ Usage pipeline = dai.Pipeline() mono = pipeline.create(dai.node.MonoCamera) - mono.setBoardSocket(dai.CameraBoardSocket.RIGHT) + mono.setCamera("right") mono.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) .. code-tab:: c++ dai::Pipeline pipeline; auto mono = pipeline.create(); - mono->setBoardSocket(dai::CameraBoardSocket::RIGHT); + mono->setCamera("right"); mono->setResolution(dai::MonoCameraProperties::SensorResolution::THE_720_P); Examples of functionality diff --git a/docs/source/components/nodes/stereo_depth.rst b/docs/source/components/nodes/stereo_depth.rst index 49a66c975..cd3d340c5 100644 --- a/docs/source/components/nodes/stereo_depth.rst +++ b/docs/source/components/nodes/stereo_depth.rst @@ -286,6 +286,221 @@ as: For the final disparity map, a filtering is applied based on the confidence threshold value: the pixels that have their confidence score larger than the threshold get invalidated, i.e. their disparity value is set to zero. You can set the confidence threshold with :code:`stereo.initialConfig.setConfidenceThreshold()`. +Calculate depth using disparity map +=================================== + +Disparity and depth are inversely related. As disparity decreases, depth increases exponentially depending on baseline and focal length. Meaning, if the disparity value is close to zero, then a small change in disparity generates a large change in depth. Similarly, if the disparity value is big, then large changes in disparity do not lead to a large change in depth. + +By considering this fact, depth can be calculated using this formula: + +.. code-block:: python + + depth = focal_length_in_pixels * baseline / disparity_in_pixels + +Where baseline is the distance between two mono cameras. Note the unit used for baseline and depth is the same. + +To get focal length in pixels, you can :ref:`read camera calibration `, as focal length in pixels is +written in camera intrinsics (``intrinsics[0][0]``): + +.. code-block:: python + + import depthai as dai + + with dai.Device() as device: + calibData = device.readCalibration() + intrinsics = calibData.getCameraIntrinsics(dai.CameraBoardSocket.CAM_C) + print('Right mono camera focal length in pixels:', intrinsics[0][0]) + +Here's theoretical calculation of the focal length in pixels: + +.. code-block:: python + + focal_length_in_pixels = image_width_in_pixels * 0.5 / tan(HFOV * 0.5 * PI/180) + + # With 400P mono camera resolution where HFOV=71.9 degrees + focal_length_in_pixels = 640 * 0.5 / tan(71.9 * 0.5 * PI / 180) = 441.25 + + # With 800P mono camera resolution where HFOV=71.9 degrees + focal_length_in_pixels = 1280 * 0.5 / tan(71.9 * 0.5 * PI / 180) = 882.5 + +Examples for calculating the depth value, using the OAK-D (7.5cm baseline): + +.. code-block:: python + + # For OAK-D @ 400P mono cameras and disparity of eg. 50 pixels + depth = 441.25 * 7.5 / 50 = 66.19 # cm + + # For OAK-D @ 800P mono cameras and disparity of eg. 10 pixels + depth = 882.5 * 7.5 / 10 = 661.88 # cm + +Note the value of disparity depth data is stored in :code:`uint16`, where 0 is a special value, meaning that distance is unknown. + +Min stereo depth distance +========================= + +If the depth results for close-in objects look weird, this is likely because they are below the minimum depth-perception distance of the device. + +To calculate this minimum distance, use the :ref:`depth formula ` and choose the maximum value for disparity_in_pixels parameter (keep in mind it is inveresly related, so maximum value will yield the smallest result). + +For example OAK-D has a baseline of **7.5cm**, focal_length_in_pixels of **882.5 pixels** and the default maximum value for disparity_in_pixels is **95**. By using the :ref:`depth formula ` we get: + +.. code-block:: python + + min_distance = focal_length_in_pixels * baseline / disparity_in_pixels = 882.5 * 7.5cm / 95 = 69.67cm + +or roughly 70cm. + +However this distance can be cut in 1/2 (to around 35cm for the OAK-D) with the following options: + +1. Changing the resolution to 640x400, instead of the standard 1280x800. + +2. Enabling Extended Disparity. + +Extended Disparity mode increases the levels of disparity to 191 from the standard 96 pixels, thereby 1/2-ing the minimum depth. It does so by computing the 96-pixel disparities on the original 1280x720 and on the downscaled 640x360 image, which are then merged to a 191-level disparity. For more information see the Extended Disparity tab in :ref:`this table `. + +Using the previous OAK-D example, disparity_in_pixels now becomes **190** and the minimum distance is: + +.. code-block:: python + + min_distance = focal_length_in_pixels * baseline / disparity_in_pixels = 882.5 * 7.5cm / 190 = 34.84cm + +or roughly 35cm. + +.. note:: + + Applying both of those options is possible, which would set the minimum depth to 1/4 of the standard settings, but at such short distances the minimum depth is limited by focal length, which is 19.6cm, since OAK-D mono cameras have fixed focus distance: 19.6cm - infinity. + +See `these examples `__ for how to enable Extended Disparity. + +Disparity shift to lower min depth perception +--------------------------------------------- + +Another option to perceive closer depth range is to use disparity shift. Disparity shift will shift the starting point +of the disparity search, which will significantly decrease max depth (MazZ) perception, but it will also decrease min depth (MinZ) perception. +Disparity shift can be combined with extended/subpixel/LR-check modes. + +.. image:: https://user-images.githubusercontent.com/18037362/189375017-2fa137d2-ad6b-46de-8899-6304bbc6c9d7.png + +**Left graph** shows min and max disparity and depth for OAK-D (7.5cm baseline, 800P resolution, ~70° HFOV) by default (disparity shift=0). See :ref:`Calculate depth using disparity map`. +Since hardware (stereo block) has a fixed 95 pixel disparity search, DepthAI will search from 0 pixels (depth=INF) to 95 pixels (depth=71cm). + +**Right graph** shows the same, but at disparity shift set to 30 pixels. This means that disparity search will be from 30 pixels (depth=2.2m) to 125 pixels (depth=50cm). +This also means that depth will be very accurate at the short range (**theoretically** below 5mm depth error). + +**Limitations**: + +- Because of the inverse relationship between disparity and depth, MaxZ will decrease much faster than MinZ as the disparity shift is increased. Therefore, it is **advised not to use a larger than necessary disparity shift**. +- Tradeoff in reducing the MinZ this way is that objects at **distances farther away than MaxZ will not be seen**. +- Because of the point above, **we only recommend using disparity shift when MaxZ is known**, such as having a depth camera mounted above a table pointing down at the table surface. +- Output disparity map is not expanded, only the depth map. So if disparity shift is set to 50, and disparity value obtained is 90, the real disparity is 140. + +**Compared to Extended disparity**, disparity shift: + +- **(+)** Is faster, as it doesn't require an extra computation, which means there's also no extra latency +- **(-)** Reduces the MaxZ (significantly), while extended disparity only reduces MinZ. + +Disparity shift can be combined with extended disparity. + +.. doxygenfunction:: dai::StereoDepthConfig::setDisparityShift + :project: depthai-core + :no-link: + +Max stereo depth distance +========================= + +The maximum depth perception distance depends on the :ref:`accuracy of the depth perception `. The formula used to calculate this distance is an approximation, but is as follows: + +.. code-block:: python + + Dm = (baseline/2) * tan((90 - HFOV / HPixels)*pi/180) + +So using this formula for existing models the *theoretical* max distance is: + +.. code-block:: python + + # For OAK-D (7.5cm baseline) + Dm = (7.5/2) * tan((90 - 71.9/1280)*pi/180) = 3825.03cm = 38.25 meters + + # For OAK-D-CM4 (9cm baseline) + Dm = (9/2) * tan((90 - 71.9/1280)*pi/180) = 4590.04cm = 45.9 meters + +If greater precision for long range measurements is required, consider enabling Subpixel Disparity or using a larger baseline distance between mono cameras. For a custom baseline, you could consider using `OAK-FFC `__ device or design your own baseboard PCB with required baseline. For more information see Subpixel Disparity under the Stereo Mode tab in :ref:`this table `. + +Depth perception accuracy +========================= + +Disparity depth works by matching features from one image to the other and its accuracy is based on multiple parameters: + +* Texture of objects / backgrounds + +Backgrounds may interfere with the object detection, since backgrounds are objects too, which will make depth perception less accurate. So disparity depth works very well outdoors as there are very rarely perfectly-clean/blank surfaces there - but these are relatively commonplace indoors (in clean buildings at least). + +* Lighting + +If the illumination is low, the diparity map will be of low confidence, which will result in a noisy depth map. + +* Baseline / distance to objects + +Lower baseline enables us to detect the depth at a closer distance as long as the object is visible in both the frames. However, this reduces the accuracy for large distances due to less pixels representing the object and disparity decreasing towards 0 much faster. +So the common norm is to adjust the baseline according to how far/close we want to be able to detect objects. + +Limitation +========== + +Since depth is calculated from disparity, which requires the pixels to overlap, there is inherently a vertical +band on the left side of the left mono camera and on the right side of the right mono camera, where depth +cannot be calculated, since it is seen by only 1 camera. That band is marked with :code:`B` +on the following picture. + +.. image:: https://user-images.githubusercontent.com/59799831/135310921-67726c28-07e7-4ffa-bc8d-74861049517e.png + +Meaning of variables on the picture: + +- ``BL [cm]`` - Baseline of stereo cameras. +- ``Dv [cm]`` - Minimum distace where both cameras see an object (thus where depth can be calculated). +- ``B [pixels]`` - Width of the band where depth cannot be calculated. +- ``W [pixels]`` - Width of mono in pixels camera or amount of horizontal pixels, also noted as :code:`HPixels` in other formulas. +- ``D [cm]`` - Distance from the **camera plane** to an object (see image :ref:`here `). +- ``F [cm]`` - Width of image at the distance ``D``. + +.. image:: https://user-images.githubusercontent.com/59799831/135310972-c37ba40b-20ad-4967-92a7-c71078bcef99.png + +With the use of the :code:`tan` function, the following formulas can be obtained: + +- :code:`F = 2 * D * tan(HFOV/2)` +- :code:`Dv = (BL/2) * tan(90 - HFOV/2)` + +In order to obtain :code:`B`, we can use :code:`tan` function again (same as for :code:`F`), but this time +we must also multiply it by the ratio between :code:`W` and :code:`F` in order to convert units to pixels. +That gives the following formula: + +.. code-block:: python + + B = 2 * Dv * tan(HFOV/2) * W / F + B = 2 * Dv * tan(HFOV/2) * W / (2 * D * tan(HFOV/2)) + B = W * Dv / D # pixels + +Example: If we are using OAK-D, which has a :code:`HFOV` of 72°, a baseline (:code:`BL`) of 7.5 cm and +:code:`640x400 (400P)` resolution is used, therefore :code:`W = 640` and an object is :code:`D = 100` cm away, we can +calculate :code:`B` in the following way: + +.. code-block:: + + Dv = 7.5 / 2 * tan(90 - 72/2) = 3.75 * tan(54°) = 5.16 cm + B = 640 * 5.16 / 100 = 33 # pixels + +Credit for calculations and images goes to our community member gregflurry, which he made on +`this `__ +forum post. + +.. note:: + + OAK-D-PRO will include both IR dot projector and IR LED, which will enable operation in no light. + IR LED is used to illuminate the whole area (for mono/color frames), while IR dot projector is mostly + for accurate disparity matching - to have good quality depth maps on blank surfaces as well. For outdoors, + the IR laser dot projector is only relevant at night. For more information see the development progress + `here `__. + Measuring real-world object dimensions ====================================== diff --git a/docs/source/samples/host_side/device_information.rst b/docs/source/samples/host_side/device_information.rst index 265c5c7f7..d19302bef 100644 --- a/docs/source/samples/host_side/device_information.rst +++ b/docs/source/samples/host_side/device_information.rst @@ -24,7 +24,7 @@ Demo Found device '192.168.33.192', MxId: '1844301011F4C51200', State: 'BOOTLOADER' Booting the first available camera (1.3)... - Available camera sensors: {: 'OV9282', : 'IMX378', : 'OV9282'} + Available camera sensors: {: 'OV9282', : 'IMX378', : 'OV9282'} Product name: OAK-D Pro AF, board name DM9098 diff --git a/examples/AprilTag/apriltag.py b/examples/AprilTag/apriltag.py index 2141ca132..bb41d5e70 100755 --- a/examples/AprilTag/apriltag.py +++ b/examples/AprilTag/apriltag.py @@ -19,7 +19,7 @@ # Properties monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") aprilTag.initialConfig.setFamily(dai.AprilTagConfig.Family.TAG_36H11) diff --git a/examples/AprilTag/apriltag_rgb.py b/examples/AprilTag/apriltag_rgb.py index 5d2858749..fa4f5ad31 100755 --- a/examples/AprilTag/apriltag_rgb.py +++ b/examples/AprilTag/apriltag_rgb.py @@ -20,7 +20,7 @@ # Properties camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) manip.initialConfig.setResize(480, 270) manip.initialConfig.setFrameType(dai.ImgFrame.Type.GRAY8) diff --git a/examples/ColorCamera/rgb_isp_scale.py b/examples/ColorCamera/rgb_isp_scale.py index 78f5370a8..fd8d725ea 100755 --- a/examples/ColorCamera/rgb_isp_scale.py +++ b/examples/ColorCamera/rgb_isp_scale.py @@ -13,7 +13,7 @@ xoutVideo.setStreamName("video") # Properties -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) camRgb.setIspScale(1, 2) camRgb.setVideoSize(1920, 1080) diff --git a/examples/ColorCamera/rgb_undistort.py b/examples/ColorCamera/rgb_undistort.py index 5111bbca6..97e71b184 100755 --- a/examples/ColorCamera/rgb_undistort.py +++ b/examples/ColorCamera/rgb_undistort.py @@ -3,7 +3,7 @@ import numpy as np camRes = dai.ColorCameraProperties.SensorResolution.THE_1080_P -camSocket = dai.CameraBoardSocket.RGB +camSocket = dai.CameraBoardSocket.CAM_A ispScale = (1,2) def getMesh(calibData, ispSize): diff --git a/examples/ColorCamera/rgb_video.py b/examples/ColorCamera/rgb_video.py index c808e333b..66a32d2ae 100755 --- a/examples/ColorCamera/rgb_video.py +++ b/examples/ColorCamera/rgb_video.py @@ -13,7 +13,7 @@ xoutVideo.setStreamName("video") # Properties -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) camRgb.setVideoSize(1920, 1080) diff --git a/examples/EdgeDetector/edge_detector.py b/examples/EdgeDetector/edge_detector.py index db699e404..b23dfd1a3 100755 --- a/examples/EdgeDetector/edge_detector.py +++ b/examples/EdgeDetector/edge_detector.py @@ -32,13 +32,13 @@ xinEdgeCfg.setStreamName(edgeCfgStr) # Properties -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") edgeDetectorRgb.setMaxOutputFrameSize(camRgb.getVideoWidth() * camRgb.getVideoHeight()) diff --git a/examples/FeatureTracker/feature_detector.py b/examples/FeatureTracker/feature_detector.py index c9a5642cc..ebad14411 100755 --- a/examples/FeatureTracker/feature_detector.py +++ b/examples/FeatureTracker/feature_detector.py @@ -27,9 +27,9 @@ # Properties monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # Disable optical flow featureTrackerLeft.initialConfig.setMotionEstimator(False) diff --git a/examples/FeatureTracker/feature_tracker.py b/examples/FeatureTracker/feature_tracker.py index 61ddf4ce7..4be337e91 100755 --- a/examples/FeatureTracker/feature_tracker.py +++ b/examples/FeatureTracker/feature_tracker.py @@ -94,9 +94,9 @@ def __init__(self, trackbarName, windowName): # Properties monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # Linking monoLeft.out.link(featureTrackerLeft.inputImage) diff --git a/examples/ImageManip/image_manip_rotate.py b/examples/ImageManip/image_manip_rotate.py index f986919e4..37b09733d 100755 --- a/examples/ImageManip/image_manip_rotate.py +++ b/examples/ImageManip/image_manip_rotate.py @@ -27,7 +27,7 @@ # Rotate mono frames monoLeft = pipeline.create(dai.node.MonoCamera) monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") manipLeft = pipeline.create(dai.node.ImageManip) rr = dai.RotatedRect() diff --git a/examples/MobileNet/mono_mobilenet.py b/examples/MobileNet/mono_mobilenet.py index 635dfc6af..49ccae728 100755 --- a/examples/MobileNet/mono_mobilenet.py +++ b/examples/MobileNet/mono_mobilenet.py @@ -33,7 +33,7 @@ nnOut.setStreamName("nn") # Properties -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) # Convert the grayscale frame into the nn-acceptable form diff --git a/examples/MonoCamera/mono_camera_control.py b/examples/MonoCamera/mono_camera_control.py index 059ae8c66..b70c59441 100755 --- a/examples/MonoCamera/mono_camera_control.py +++ b/examples/MonoCamera/mono_camera_control.py @@ -48,8 +48,8 @@ def clamp(num, v0, v1): bottomRight = dai.Point2f(0.8, 0.8) # Properties -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoRight.setCamera("right") +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) manipRight.initialConfig.setCropRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y) diff --git a/examples/MonoCamera/mono_full_resolution_saver.py b/examples/MonoCamera/mono_full_resolution_saver.py index 42a489361..06e263418 100755 --- a/examples/MonoCamera/mono_full_resolution_saver.py +++ b/examples/MonoCamera/mono_full_resolution_saver.py @@ -15,7 +15,7 @@ xoutRight.setStreamName("right") # Properties -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) # Linking diff --git a/examples/MonoCamera/mono_preview.py b/examples/MonoCamera/mono_preview.py index 204164609..2a7936d5b 100755 --- a/examples/MonoCamera/mono_preview.py +++ b/examples/MonoCamera/mono_preview.py @@ -16,9 +16,9 @@ xoutRight.setStreamName('right') # Properties -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) # Linking diff --git a/examples/MonoCamera/mono_preview_alternate_pro.py b/examples/MonoCamera/mono_preview_alternate_pro.py index 826d5d927..6f5997150 100755 --- a/examples/MonoCamera/mono_preview_alternate_pro.py +++ b/examples/MonoCamera/mono_preview_alternate_pro.py @@ -19,11 +19,11 @@ monoL = pipeline.create(dai.node.MonoCamera) monoR = pipeline.create(dai.node.MonoCamera) -monoL.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoL.setCamera("left") monoL.setResolution(res) monoL.setFps(fps) monoL.setNumFramesPool(poolSize) -monoR.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoR.setCamera("right") monoR.setResolution(res) monoR.setFps(fps) monoR.setNumFramesPool(poolSize) diff --git a/examples/NeuralNetwork/concat_multi_input.py b/examples/NeuralNetwork/concat_multi_input.py index 93e9d03d0..08b65a5f7 100755 --- a/examples/NeuralNetwork/concat_multi_input.py +++ b/examples/NeuralNetwork/concat_multi_input.py @@ -42,8 +42,8 @@ def create_mono(p, socket): nn.setNumInferenceThreads(2) camRgb.preview.link(nn.inputs['img2']) -create_mono(p, dai.CameraBoardSocket.LEFT).link(nn.inputs['img1']) -create_mono(p, dai.CameraBoardSocket.RIGHT).link(nn.inputs['img3']) +create_mono(p, dai.CameraBoardSocket.CAM_B).link(nn.inputs['img1']) +create_mono(p, dai.CameraBoardSocket.CAM_C).link(nn.inputs['img3']) # Send bouding box from the NN to the host via XLink nn_xout = p.createXLinkOut() diff --git a/examples/ObjectTracker/spatial_object_tracker.py b/examples/ObjectTracker/spatial_object_tracker.py index a181563cc..09053b09b 100755 --- a/examples/ObjectTracker/spatial_object_tracker.py +++ b/examples/ObjectTracker/spatial_object_tracker.py @@ -43,14 +43,14 @@ camRgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR) monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # setting node configs stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) # Align depth map to the perspective of RGB camera, on which inference is done -stereo.setDepthAlign(dai.CameraBoardSocket.RGB) +stereo.setDepthAlign(dai.CameraBoardSocket.CAM_A) stereo.setOutputSize(monoLeft.getResolutionWidth(), monoLeft.getResolutionHeight()) spatialDetectionNetwork.setBlobPath(args.nnPath) diff --git a/examples/Script/script_change_pipeline_flow.py b/examples/Script/script_change_pipeline_flow.py index 3774faf50..b528b99d7 100755 --- a/examples/Script/script_change_pipeline_flow.py +++ b/examples/Script/script_change_pipeline_flow.py @@ -10,7 +10,7 @@ pipeline = dai.Pipeline() cam = pipeline.createColorCamera() -cam.setBoardSocket(dai.CameraBoardSocket.RGB) +cam.setBoardSocket(dai.CameraBoardSocket.CAM_A) cam.setInterleaved(False) cam.setIspScale(2,3) cam.setVideoSize(720,720) diff --git a/examples/SpatialDetection/spatial_calculator_multi_roi.py b/examples/SpatialDetection/spatial_calculator_multi_roi.py index dbf7c6fce..a2fbb6c9f 100755 --- a/examples/SpatialDetection/spatial_calculator_multi_roi.py +++ b/examples/SpatialDetection/spatial_calculator_multi_roi.py @@ -23,9 +23,9 @@ # Properties monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) stereo.setLeftRightCheck(True) diff --git a/examples/SpatialDetection/spatial_location_calculator.py b/examples/SpatialDetection/spatial_location_calculator.py index 8e30f0a3c..6642e8865 100755 --- a/examples/SpatialDetection/spatial_location_calculator.py +++ b/examples/SpatialDetection/spatial_location_calculator.py @@ -26,9 +26,9 @@ # Properties monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") lrcheck = False subpixel = False diff --git a/examples/SpatialDetection/spatial_mobilenet.py b/examples/SpatialDetection/spatial_mobilenet.py index ec2eff715..08e574a61 100755 --- a/examples/SpatialDetection/spatial_mobilenet.py +++ b/examples/SpatialDetection/spatial_mobilenet.py @@ -52,14 +52,14 @@ camRgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR) monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # Setting node configs stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) # Align depth map to the perspective of RGB camera, on which inference is done -stereo.setDepthAlign(dai.CameraBoardSocket.RGB) +stereo.setDepthAlign(dai.CameraBoardSocket.CAM_A) stereo.setOutputSize(monoLeft.getResolutionWidth(), monoLeft.getResolutionHeight()) spatialDetectionNetwork.setBlobPath(nnBlobPath) diff --git a/examples/SpatialDetection/spatial_mobilenet_mono.py b/examples/SpatialDetection/spatial_mobilenet_mono.py index 30a237d2f..9b3a5ac5a 100755 --- a/examples/SpatialDetection/spatial_mobilenet_mono.py +++ b/examples/SpatialDetection/spatial_mobilenet_mono.py @@ -53,9 +53,9 @@ imageManip.initialConfig.setFrameType(dai.ImgFrame.Type.BGR888p) monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # StereoDepth stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) diff --git a/examples/SpatialDetection/spatial_tiny_yolo.py b/examples/SpatialDetection/spatial_tiny_yolo.py index 5575bccd6..2e1887dd6 100755 --- a/examples/SpatialDetection/spatial_tiny_yolo.py +++ b/examples/SpatialDetection/spatial_tiny_yolo.py @@ -75,14 +75,14 @@ camRgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR) monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # setting node configs stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) # Align depth map to the perspective of RGB camera, on which inference is done -stereo.setDepthAlign(dai.CameraBoardSocket.RGB) +stereo.setDepthAlign(dai.CameraBoardSocket.CAM_A) stereo.setOutputSize(monoLeft.getResolutionWidth(), monoLeft.getResolutionHeight()) spatialDetectionNetwork.setBlobPath(nnBlobPath) diff --git a/examples/StereoDepth/depth_colormap.py b/examples/StereoDepth/depth_colormap.py index 05d86f85c..26de44431 100755 --- a/examples/StereoDepth/depth_colormap.py +++ b/examples/StereoDepth/depth_colormap.py @@ -24,9 +24,9 @@ # Properties monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # Create a node that will produce the depth map (using disparity output as it's easier to visualize depth this way) depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) diff --git a/examples/StereoDepth/depth_crop_control.py b/examples/StereoDepth/depth_crop_control.py index eae2677a4..0eecf7c53 100755 --- a/examples/StereoDepth/depth_crop_control.py +++ b/examples/StereoDepth/depth_crop_control.py @@ -32,8 +32,8 @@ bottomRight = dai.Point2f(0.8, 0.8) # Properties -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoRight.setCamera("right") +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) diff --git a/examples/StereoDepth/depth_post_processing.py b/examples/StereoDepth/depth_post_processing.py index b1844b1ca..732e91232 100755 --- a/examples/StereoDepth/depth_post_processing.py +++ b/examples/StereoDepth/depth_post_processing.py @@ -24,9 +24,9 @@ # Properties monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # Create a node that will produce the depth map (using disparity output as it's easier to visualize depth this way) depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) diff --git a/examples/StereoDepth/depth_preview.py b/examples/StereoDepth/depth_preview.py index 017d3a403..b916ecb55 100755 --- a/examples/StereoDepth/depth_preview.py +++ b/examples/StereoDepth/depth_preview.py @@ -24,9 +24,9 @@ # Properties monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # Create a node that will produce the depth map (using disparity output as it's easier to visualize depth this way) depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) diff --git a/examples/StereoDepth/depth_preview_lr.py b/examples/StereoDepth/depth_preview_lr.py index 5cc37c063..8bf6fff6d 100755 --- a/examples/StereoDepth/depth_preview_lr.py +++ b/examples/StereoDepth/depth_preview_lr.py @@ -51,7 +51,7 @@ # Properties left.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1200_P) -left.setBoardSocket(dai.CameraBoardSocket.LEFT) +left.setCamera("left") left.setIspScale(2, 3) center.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1200_P) @@ -59,7 +59,7 @@ center.setIspScale(2, 3) right.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1200_P) -right.setBoardSocket(dai.CameraBoardSocket.RIGHT) +right.setCamera("right") right.setIspScale(2, 3) LC_depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) diff --git a/examples/StereoDepth/rgb_depth_aligned.py b/examples/StereoDepth/rgb_depth_aligned.py index c76dd7b79..3e72fc023 100755 --- a/examples/StereoDepth/rgb_depth_aligned.py +++ b/examples/StereoDepth/rgb_depth_aligned.py @@ -48,7 +48,7 @@ def updateBlendWeights(percent_rgb): queueNames.append("disp") #Properties -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) camRgb.setFps(fps) if downscaleColor: camRgb.setIspScale(2, 3) @@ -56,22 +56,22 @@ def updateBlendWeights(percent_rgb): # This value was used during calibration try: calibData = device.readCalibration2() - lensPosition = calibData.getLensPosition(dai.CameraBoardSocket.RGB) + lensPosition = calibData.getLensPosition(dai.CameraBoardSocket.CAM_A) if lensPosition: camRgb.initialControl.setManualFocus(lensPosition) except: raise left.setResolution(monoResolution) -left.setBoardSocket(dai.CameraBoardSocket.LEFT) +left.setCamera("left") left.setFps(fps) right.setResolution(monoResolution) -right.setBoardSocket(dai.CameraBoardSocket.RIGHT) +right.setCamera("right") right.setFps(fps) stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) # LR-check is required for depth alignment stereo.setLeftRightCheck(True) -stereo.setDepthAlign(dai.CameraBoardSocket.RGB) +stereo.setDepthAlign(dai.CameraBoardSocket.CAM_A) # Linking camRgb.isp.link(rgbOut.input) diff --git a/examples/StereoDepth/rgb_depth_confidence_aligned.py b/examples/StereoDepth/rgb_depth_confidence_aligned.py index e1487c9ac..226707a81 100755 --- a/examples/StereoDepth/rgb_depth_confidence_aligned.py +++ b/examples/StereoDepth/rgb_depth_confidence_aligned.py @@ -68,7 +68,7 @@ def updateConfBlendWeights(percent): queueNames.append("disp") #Properties -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) camRgb.setFps(fps) if downscaleColor: camRgb.setIspScale(2, 3) @@ -76,24 +76,24 @@ def updateConfBlendWeights(percent): # This value was used during calibration try: calibData = device.readCalibration2() - lensPosition = calibData.getLensPosition(dai.CameraBoardSocket.RGB) + lensPosition = calibData.getLensPosition(dai.CameraBoardSocket.CAM_A) if lensPosition: camRgb.initialControl.setManualFocus(lensPosition) except: raise left.setResolution(monoResolution) -left.setBoardSocket(dai.CameraBoardSocket.LEFT) +left.setCamera("left") left.setFps(fps) right.setResolution(monoResolution) -right.setBoardSocket(dai.CameraBoardSocket.RIGHT) +right.setCamera("right") right.setFps(fps) stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) # LR-check is required for depth alignment stereo.setLeftRightCheck(True) if 0: stereo.setSubpixel(True) # TODO enable for test -stereo.setDepthAlign(dai.CameraBoardSocket.RGB) +stereo.setDepthAlign(dai.CameraBoardSocket.CAM_A) xoutConfMap = pipeline.create(dai.node.XLinkOut) xoutConfMap.setStreamName('confidence_map') diff --git a/examples/StereoDepth/stereo_depth_from_host.py b/examples/StereoDepth/stereo_depth_from_host.py index d6082df2e..bf5328785 100755 --- a/examples/StereoDepth/stereo_depth_from_host.py +++ b/examples/StereoDepth/stereo_depth_from_host.py @@ -281,7 +281,7 @@ def trackbarDisparityShift(value): for tr in StereoConfigHandler.trDisparityShift: tr.set(value) - def trackbarCenterAlignmentShift(value): + def trackbarCenterAlignmentShift(value): if StereoConfigHandler.config.algorithmControl.depthAlign != dai.StereoDepthConfig.AlgorithmControl.DepthAlign.CENTER: print("Center alignment shift factor requires CENTER alignment enabled!") return @@ -641,7 +641,7 @@ def convertToCv2Frame(name, image, config): stereoDepthConfigInQueue = device.getInputQueue("stereoDepthConfig") inStreams = ['in_left', 'in_right'] - inStreamsCameraID = [dai.CameraBoardSocket.LEFT, dai.CameraBoardSocket.RIGHT] + inStreamsCameraID = [dai.CameraBoardSocket.CAM_B, dai.CameraBoardSocket.CAM_C] in_q_list = [] for s in inStreams: q = device.getInputQueue(s) diff --git a/examples/StereoDepth/stereo_depth_video.py b/examples/StereoDepth/stereo_depth_video.py index 8ee3a2269..6d909782a 100755 --- a/examples/StereoDepth/stereo_depth_video.py +++ b/examples/StereoDepth/stereo_depth_video.py @@ -121,11 +121,11 @@ def getMesh(calibData): - M1 = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.LEFT, resolution[0], resolution[1])) - d1 = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.LEFT)) + M1 = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.CAM_B, resolution[0], resolution[1])) + d1 = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.CAM_B)) R1 = np.array(calibData.getStereoLeftRectificationRotation()) - M2 = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.RIGHT, resolution[0], resolution[1])) - d2 = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.RIGHT)) + M2 = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.CAM_C, resolution[0], resolution[1])) + d2 = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.CAM_C)) R2 = np.array(calibData.getStereoRightRectificationRotation()) mapXL, mapYL = cv2.initUndistortRectifyMap(M1, d1, R1, M2, resolution, cv2.CV_32FC1) mapXR, mapYR = cv2.initUndistortRectifyMap(M2, d2, R2, M2, resolution, cv2.CV_32FC1) @@ -205,11 +205,11 @@ def getDisparityFrame(frame, cvColorMap): xoutRectifRight = pipeline.create(dai.node.XLinkOut) if args.swap_left_right: - camLeft.setBoardSocket(dai.CameraBoardSocket.RIGHT) - camRight.setBoardSocket(dai.CameraBoardSocket.LEFT) + camLeft.setCamera("right") + camRight.setCamera("left") else: - camLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) - camRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) + camLeft.setCamera("left") + camRight.setCamera("right") res = ( dai.MonoCameraProperties.SensorResolution.THE_800_P diff --git a/examples/VideoEncoder/disparity_colormap_encoding.py b/examples/VideoEncoder/disparity_colormap_encoding.py index 09d602b10..c0d2c8ce1 100755 --- a/examples/VideoEncoder/disparity_colormap_encoding.py +++ b/examples/VideoEncoder/disparity_colormap_encoding.py @@ -8,11 +8,11 @@ # Create left/right mono cameras for Stereo depth monoLeft = pipeline.create(dai.node.MonoCamera) monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight = pipeline.create(dai.node.MonoCamera) monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # Create a node that will produce the depth map depth = pipeline.create(dai.node.StereoDepth) diff --git a/examples/VideoEncoder/disparity_encoding.py b/examples/VideoEncoder/disparity_encoding.py index 951e1c1b8..1e9483d4f 100755 --- a/examples/VideoEncoder/disparity_encoding.py +++ b/examples/VideoEncoder/disparity_encoding.py @@ -10,11 +10,11 @@ # Create left/right mono cameras for Stereo depth monoLeft = pipeline.create(dai.node.MonoCamera) monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight = pipeline.create(dai.node.MonoCamera) monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # Create a node that will produce the depth map depth = pipeline.create(dai.node.StereoDepth) diff --git a/examples/VideoEncoder/encoding_max_limit.py b/examples/VideoEncoder/encoding_max_limit.py index b01f430ed..94c0b555d 100755 --- a/examples/VideoEncoder/encoding_max_limit.py +++ b/examples/VideoEncoder/encoding_max_limit.py @@ -22,10 +22,10 @@ ve3Out.setStreamName('ve3Out') # Properties -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoLeft.setCamera("left") +monoRight.setCamera("right") # Setting to 26fps will trigger error ve1.setDefaultProfilePreset(25, dai.VideoEncoderProperties.Profile.H264_MAIN) diff --git a/examples/VideoEncoder/rgb_encoding.py b/examples/VideoEncoder/rgb_encoding.py index 45b51f991..3d5eadcdd 100755 --- a/examples/VideoEncoder/rgb_encoding.py +++ b/examples/VideoEncoder/rgb_encoding.py @@ -13,7 +13,7 @@ xout.setStreamName('h265') # Properties -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) videoEnc.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H265_MAIN) diff --git a/examples/VideoEncoder/rgb_full_resolution_saver.py b/examples/VideoEncoder/rgb_full_resolution_saver.py index 381784f46..d2c06640a 100755 --- a/examples/VideoEncoder/rgb_full_resolution_saver.py +++ b/examples/VideoEncoder/rgb_full_resolution_saver.py @@ -9,7 +9,7 @@ pipeline = dai.Pipeline() camRgb = pipeline.create(dai.node.ColorCamera) -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) xoutRgb = pipeline.create(dai.node.XLinkOut) @@ -56,7 +56,7 @@ with open(fName, "wb") as f: f.write(qStill.get().getData()) print('Image saved to', fName) - + key = cv2.waitKey(1) if key == ord('q'): break diff --git a/examples/VideoEncoder/rgb_mono_encoding.py b/examples/VideoEncoder/rgb_mono_encoding.py index 09ec1c036..5aeffb92e 100755 --- a/examples/VideoEncoder/rgb_mono_encoding.py +++ b/examples/VideoEncoder/rgb_mono_encoding.py @@ -22,9 +22,9 @@ ve3Out.setStreamName('ve3Out') # Properties -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) +monoLeft.setCamera("left") +monoRight.setCamera("right") # Create encoders, one for each camera, consuming the frames and encoding them using H.264 / H.265 encoding ve1.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H264_MAIN) ve2.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H265_MAIN) diff --git a/examples/calibration/calibration_load.py b/examples/calibration/calibration_load.py index 9a2995a22..44e8996dc 100755 --- a/examples/calibration/calibration_load.py +++ b/examples/calibration/calibration_load.py @@ -28,10 +28,10 @@ # MonoCamera monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") # monoLeft.setFps(5.0) monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # monoRight.setFps(5.0) # Linking diff --git a/examples/calibration/calibration_reader.py b/examples/calibration/calibration_reader.py index c49b93b59..9f3d82564 100755 --- a/examples/calibration/calibration_reader.py +++ b/examples/calibration/calibration_reader.py @@ -14,66 +14,66 @@ calibData = device.readCalibration() calibData.eepromToJsonFile(calibFile) - M_rgb, width, height = calibData.getDefaultIntrinsics(dai.CameraBoardSocket.RGB) + M_rgb, width, height = calibData.getDefaultIntrinsics(dai.CameraBoardSocket.CAM_A) print("RGB Camera Default intrinsics...") print(M_rgb) print(width) print(height) if "OAK-1" in calibData.getEepromData().boardName or "BW1093OAK" in calibData.getEepromData().boardName: - M_rgb = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.RGB, 1280, 720)) + M_rgb = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.CAM_A, 1280, 720)) print("RGB Camera resized intrinsics...") print(M_rgb) - D_rgb = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.RGB)) + D_rgb = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.CAM_A)) print("RGB Distortion Coefficients...") [print(name + ": " + value) for (name, value) in zip(["k1", "k2", "p1", "p2", "k3", "k4", "k5", "k6", "s1", "s2", "s3", "s4", "τx", "τy"], [str(data) for data in D_rgb])] - print(f'RGB FOV {calibData.getFov(dai.CameraBoardSocket.RGB)}') + print(f'RGB FOV {calibData.getFov(dai.CameraBoardSocket.CAM_A)}') else: - M_rgb, width, height = calibData.getDefaultIntrinsics(dai.CameraBoardSocket.RGB) + M_rgb, width, height = calibData.getDefaultIntrinsics(dai.CameraBoardSocket.CAM_A) print("RGB Camera Default intrinsics...") print(M_rgb) print(width) print(height) - M_rgb = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.RGB, 3840, 2160)) + M_rgb = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.CAM_A, 3840, 2160)) print("RGB Camera resized intrinsics... 3840 x 2160 ") print(M_rgb) - M_rgb = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.RGB, 4056, 3040 )) + M_rgb = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.CAM_A, 4056, 3040 )) print("RGB Camera resized intrinsics... 4056 x 3040 ") print(M_rgb) - M_left, width, height = calibData.getDefaultIntrinsics(dai.CameraBoardSocket.LEFT) + M_left, width, height = calibData.getDefaultIntrinsics(dai.CameraBoardSocket.CAM_B) print("LEFT Camera Default intrinsics...") print(M_left) print(width) print(height) - M_left = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.LEFT, 1280, 720)) + M_left = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.CAM_B, 1280, 720)) print("LEFT Camera resized intrinsics... 1280 x 720") print(M_left) - M_right = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.RIGHT, 1280, 720)) + M_right = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.CAM_C, 1280, 720)) print("RIGHT Camera resized intrinsics... 1280 x 720") print(M_right) - D_left = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.LEFT)) + D_left = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.CAM_B)) print("LEFT Distortion Coefficients...") [print(name+": "+value) for (name, value) in zip(["k1","k2","p1","p2","k3","k4","k5","k6","s1","s2","s3","s4","τx","τy"],[str(data) for data in D_left])] - D_right = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.RIGHT)) + D_right = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.CAM_C)) print("RIGHT Distortion Coefficients...") [print(name+": "+value) for (name, value) in zip(["k1","k2","p1","p2","k3","k4","k5","k6","s1","s2","s3","s4","τx","τy"],[str(data) for data in D_right])] - print(f"RGB FOV {calibData.getFov(dai.CameraBoardSocket.RGB)}, Mono FOV {calibData.getFov(dai.CameraBoardSocket.LEFT)}") + print(f"RGB FOV {calibData.getFov(dai.CameraBoardSocket.CAM_A)}, Mono FOV {calibData.getFov(dai.CameraBoardSocket.CAM_B)}") R1 = np.array(calibData.getStereoLeftRectificationRotation()) R2 = np.array(calibData.getStereoRightRectificationRotation()) @@ -87,10 +87,10 @@ print("RIGHT Camera stereo rectification matrix...") print(H_right) - lr_extrinsics = np.array(calibData.getCameraExtrinsics(dai.CameraBoardSocket.LEFT, dai.CameraBoardSocket.RIGHT)) + lr_extrinsics = np.array(calibData.getCameraExtrinsics(dai.CameraBoardSocket.CAM_B, dai.CameraBoardSocket.CAM_C)) print("Transformation matrix of where left Camera is W.R.T right Camera's optical center") print(lr_extrinsics) - l_rgb_extrinsics = np.array(calibData.getCameraExtrinsics(dai.CameraBoardSocket.LEFT, dai.CameraBoardSocket.RGB)) + l_rgb_extrinsics = np.array(calibData.getCameraExtrinsics(dai.CameraBoardSocket.CAM_B, dai.CameraBoardSocket.CAM_A)) print("Transformation matrix of where left Camera is W.R.T RGB Camera's optical center") print(l_rgb_extrinsics) diff --git a/examples/host_side/opencv_support.py b/examples/host_side/opencv_support.py index f439c3fe3..ae98a9b02 100755 --- a/examples/host_side/opencv_support.py +++ b/examples/host_side/opencv_support.py @@ -16,7 +16,7 @@ # Properties camRgb.setPreviewSize(300, 300) -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) camRgb.setInterleaved(True) camRgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR) diff --git a/examples/host_side/queue_add_callback.py b/examples/host_side/queue_add_callback.py index e9c21d0f3..6feb194d0 100755 --- a/examples/host_side/queue_add_callback.py +++ b/examples/host_side/queue_add_callback.py @@ -17,9 +17,9 @@ # Properties camRgb.setPreviewSize(300, 300) -left.setBoardSocket(dai.CameraBoardSocket.LEFT) +left.setCamera("left") left.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -right.setBoardSocket(dai.CameraBoardSocket.RIGHT) +right.setCamera("right") right.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) # Stream all the camera streams through the same XLink node diff --git a/examples/mixed/frame_sync.py b/examples/mixed/frame_sync.py index 2fff31edc..29226e285 100755 --- a/examples/mixed/frame_sync.py +++ b/examples/mixed/frame_sync.py @@ -15,12 +15,12 @@ left = pipeline.create(dai.node.MonoCamera) left.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -left.setBoardSocket(dai.CameraBoardSocket.LEFT) +left.setCamera("left") left.setFps(FPS) right = pipeline.create(dai.node.MonoCamera) right.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -right.setBoardSocket(dai.CameraBoardSocket.RIGHT) +right.setCamera("right") right.setFps(FPS) stereo = pipeline.createStereoDepth() diff --git a/examples/mixed/mono_depth_mobilenetssd.py b/examples/mixed/mono_depth_mobilenetssd.py index 145b1f915..d7bc31127 100755 --- a/examples/mixed/mono_depth_mobilenetssd.py +++ b/examples/mixed/mono_depth_mobilenetssd.py @@ -38,9 +38,9 @@ nnOut.setStreamName("nn") # Properties -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) # Produce the depth map (using disparity output as it's easier to visualize depth this way) diff --git a/examples/mixed/multiple_devices.py b/examples/mixed/multiple_devices.py index 7c3252823..789694d63 100755 --- a/examples/mixed/multiple_devices.py +++ b/examples/mixed/multiple_devices.py @@ -11,7 +11,7 @@ def createPipeline(): camRgb = pipeline.create(dai.node.ColorCamera) camRgb.setPreviewSize(300, 300) - camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) + camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) camRgb.setInterleaved(False) @@ -47,7 +47,7 @@ def createPipeline(): print(" >>> Board name:", eepromData.boardName) if eepromData.productName != "": print(" >>> Product name:", eepromData.productName) - + pipeline = createPipeline() device.startPipeline(pipeline) @@ -55,7 +55,7 @@ def createPipeline(): q_rgb = device.getOutputQueue(name="rgb", maxSize=4, blocking=False) stream_name = "rgb-" + mxId + "-" + eepromData.productName qRgbMap.append((q_rgb, stream_name)) - + while True: for q_rgb, stream_name in qRgbMap: if q_rgb.has(): diff --git a/examples/mixed/report_camera_settings.py b/examples/mixed/report_camera_settings.py index 25b1780fc..78283f09d 100755 --- a/examples/mixed/report_camera_settings.py +++ b/examples/mixed/report_camera_settings.py @@ -16,7 +16,7 @@ camLeft = pipeline.create(dai.node.MonoCamera) camLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -camLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +camLeft.setCamera("left") xoutLeft = pipeline.create(dai.node.XLinkOut) xoutLeft.setStreamName("left") diff --git a/examples/mixed/rgb_encoding_mobilenet.py b/examples/mixed/rgb_encoding_mobilenet.py index fbcb10192..bf00c8bb8 100755 --- a/examples/mixed/rgb_encoding_mobilenet.py +++ b/examples/mixed/rgb_encoding_mobilenet.py @@ -36,7 +36,7 @@ nnOut.setStreamName("nn") # Properties -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) camRgb.setPreviewSize(300, 300) camRgb.setInterleaved(False) diff --git a/examples/mixed/rgb_encoding_mono_mobilenet.py b/examples/mixed/rgb_encoding_mono_mobilenet.py index f4822ab0e..850635b21 100755 --- a/examples/mixed/rgb_encoding_mono_mobilenet.py +++ b/examples/mixed/rgb_encoding_mono_mobilenet.py @@ -40,9 +40,9 @@ nnOut.setStreamName("nn") # Properties -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) videoEncoder.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H265_MAIN) diff --git a/examples/mixed/rgb_encoding_mono_mobilenet_depth.py b/examples/mixed/rgb_encoding_mono_mobilenet_depth.py index 5b08ce42f..f6a0f627d 100755 --- a/examples/mixed/rgb_encoding_mono_mobilenet_depth.py +++ b/examples/mixed/rgb_encoding_mono_mobilenet_depth.py @@ -44,11 +44,11 @@ nnOut.setStreamName('nn') # Properties -camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) +camRgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) videoEncoder.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H265_MAIN) diff --git a/examples/mixed/rotated_spatial_detections.py b/examples/mixed/rotated_spatial_detections.py index 3b386a48b..90913d84d 100755 --- a/examples/mixed/rotated_spatial_detections.py +++ b/examples/mixed/rotated_spatial_detections.py @@ -50,14 +50,14 @@ camRgb.setImageOrientation(dai.CameraImageOrientation.ROTATE_180_DEG) monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) +monoLeft.setCamera("left") monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) +monoRight.setCamera("right") # Setting node configs stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) # Align depth map to the perspective of RGB camera, on which inference is done -stereo.setDepthAlign(dai.CameraBoardSocket.RGB) +stereo.setDepthAlign(dai.CameraBoardSocket.CAM_A) stereo.setOutputSize(monoLeft.getResolutionWidth(), monoLeft.getResolutionHeight()) rotate_stereo_manip = pipeline.createImageManip() diff --git a/src/CalibrationHandlerBindings.cpp b/src/CalibrationHandlerBindings.cpp index 734692c07..52f7bf3ae 100644 --- a/src/CalibrationHandlerBindings.cpp +++ b/src/CalibrationHandlerBindings.cpp @@ -47,7 +47,7 @@ void CalibrationHandlerBindings::bind(pybind11::module& m, void* pCallstack){ .def("getCameraExtrinsics", &CalibrationHandler::getCameraExtrinsics, py::arg("srcCamera"), py::arg("dstCamera"), py::arg("useSpecTranslation") = false, DOC(dai, CalibrationHandler, getCameraExtrinsics)) .def("getCameraTranslationVector", &CalibrationHandler::getCameraTranslationVector, py::arg("srcCamera"), py::arg("dstCamera"), py::arg("useSpecTranslation") = true, DOC(dai, CalibrationHandler, getCameraTranslationVector)) - .def("getBaselineDistance", &CalibrationHandler::getBaselineDistance, py::arg("cam1") = dai::CameraBoardSocket::RIGHT, py::arg("cam2") = dai::CameraBoardSocket::LEFT, py::arg("useSpecTranslation") = true, DOC(dai, CalibrationHandler, getBaselineDistance)) + .def("getBaselineDistance", &CalibrationHandler::getBaselineDistance, py::arg("cam1") = dai::CameraBoardSocket::CAM_C, py::arg("cam2") = dai::CameraBoardSocket::CAM_B, py::arg("useSpecTranslation") = true, DOC(dai, CalibrationHandler, getBaselineDistance)) .def("getCameraToImuExtrinsics", &CalibrationHandler::getCameraToImuExtrinsics, py::arg("cameraId"), py::arg("useSpecTranslation") = false, DOC(dai, CalibrationHandler, getCameraToImuExtrinsics)) .def("getImuToCameraExtrinsics", &CalibrationHandler::getImuToCameraExtrinsics, py::arg("cameraId"), py::arg("useSpecTranslation") = false, DOC(dai, CalibrationHandler, getImuToCameraExtrinsics)) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 18e6c3fc7..6d04596e7 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -109,10 +109,10 @@ def socket_type_pair(arg): print("DepthAI path:", dai.__file__) cam_socket_opts = { - 'rgb': dai.CameraBoardSocket.RGB, # Or CAM_A - 'left': dai.CameraBoardSocket.LEFT, # Or CAM_B - 'right': dai.CameraBoardSocket.RIGHT, # Or CAM_C - 'camd': dai.CameraBoardSocket.CAM_D, + 'rgb' : dai.CameraBoardSocket.CAM_A, + 'left' : dai.CameraBoardSocket.CAM_B, + 'right': dai.CameraBoardSocket.CAM_C, + 'camd' : dai.CameraBoardSocket.CAM_D, } cam_socket_to_name = { From 2aa41fba27e3b16426c2401846dbeee50e909e7a Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Sun, 23 Apr 2023 15:26:06 +0200 Subject: [PATCH 278/385] Added deprecation of usb2Mode constructors --- src/DeviceBindings.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 673bac270..cb0990572 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -219,6 +219,7 @@ static void bindConstructors(ARG& arg){ return std::make_unique(pipeline, dev); }), py::arg("pipeline"), DOC(dai, DeviceBase, DeviceBase)) .def(py::init([](const Pipeline& pipeline, bool usb2Mode){ + PyErr_WarnEx(PyExc_DeprecationWarning, "Use constructor taking 'UsbSpeed' instead", 1); auto dev = deviceSearchHelper(); py::gil_scoped_release release; return std::make_unique(pipeline, dev, usb2Mode); @@ -234,6 +235,7 @@ static void bindConstructors(ARG& arg){ return std::make_unique(pipeline, dev, pathToCmd); }), py::arg("pipeline"), py::arg("pathToCmd"), DOC(dai, DeviceBase, DeviceBase, 4)) .def(py::init([](const Pipeline& pipeline, const DeviceInfo& deviceInfo, bool usb2Mode){ + PyErr_WarnEx(PyExc_DeprecationWarning, "Use constructor taking 'UsbSpeed' instead", 1); py::gil_scoped_release release; return std::make_unique(pipeline, deviceInfo, usb2Mode); }), py::arg("pipeline"), py::arg("devInfo"), py::arg("usb2Mode") = false, DOC(dai, DeviceBase, DeviceBase, 6)) @@ -253,6 +255,7 @@ static void bindConstructors(ARG& arg){ return std::make_unique(version, dev); }), py::arg("version") = OpenVINO::VERSION_UNIVERSAL, DOC(dai, DeviceBase, DeviceBase, 10)) .def(py::init([](OpenVINO::Version version, bool usb2Mode){ + PyErr_WarnEx(PyExc_DeprecationWarning, "Use constructor taking 'UsbSpeed' instead", 1); auto dev = deviceSearchHelper(); py::gil_scoped_release release; return std::make_unique(version, dev, usb2Mode); @@ -268,6 +271,7 @@ static void bindConstructors(ARG& arg){ return std::make_unique(version, dev, pathToCmd); }), py::arg("version"), py::arg("pathToCmd"), DOC(dai, DeviceBase, DeviceBase, 13)) .def(py::init([](OpenVINO::Version version, const DeviceInfo& deviceInfo, bool usb2Mode){ + PyErr_WarnEx(PyExc_DeprecationWarning, "Use constructor taking 'UsbSpeed' instead", 1); py::gil_scoped_release release; return std::make_unique(version, deviceInfo, usb2Mode); }), py::arg("version"), py::arg("deviceInfo"), py::arg("usb2Mode") = false, DOC(dai, DeviceBase, DeviceBase, 15)) From 3d4440b04d2a6b65073fbd2aecaa19af95ad80e4 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Sun, 23 Apr 2023 15:26:17 +0200 Subject: [PATCH 279/385] Added a comment to deprecated values --- src/pipeline/CommonBindings.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/pipeline/CommonBindings.cpp b/src/pipeline/CommonBindings.cpp index abb40023c..d4be59051 100644 --- a/src/pipeline/CommonBindings.cpp +++ b/src/pipeline/CommonBindings.cpp @@ -131,10 +131,6 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ // CameraBoardSocket enum bindings cameraBoardSocket .value("AUTO", CameraBoardSocket::AUTO) - .value("RGB", CameraBoardSocket::RGB) - .value("LEFT", CameraBoardSocket::LEFT) - .value("RIGHT", CameraBoardSocket::RIGHT) - .value("CENTER", CameraBoardSocket::CENTER) .value("CAM_A", CameraBoardSocket::CAM_A) .value("CAM_B", CameraBoardSocket::CAM_B) .value("CAM_C", CameraBoardSocket::CAM_C) @@ -143,6 +139,13 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ .value("CAM_F", CameraBoardSocket::CAM_F) .value("CAM_G", CameraBoardSocket::CAM_G) .value("CAM_H", CameraBoardSocket::CAM_H) + + // Deprecated + // TODO(themarpe) - issue a Deprecation warning + .value("RGB", CameraBoardSocket::RGB, "**Deprecated:** Use CAM_A or address camera by name instead") + .value("LEFT", CameraBoardSocket::LEFT, "**Deprecated:** Use CAM_B or address camera by name instead") + .value("RIGHT", CameraBoardSocket::RIGHT, "**Deprecated:** Use CAM_C or address camera by name instead") + .value("CENTER", CameraBoardSocket::CENTER, "**Deprecated:** Use CAM_A or address camera by name instead") ; // CameraSensorType enum bindings From 2f0f17ac463ef8dcdb9b966eb5b6d19b29213f5d Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Sun, 23 Apr 2023 15:26:29 +0200 Subject: [PATCH 280/385] Fixed deprecation for CameraBoardSocket --- src/pipeline/CommonBindings.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/pipeline/CommonBindings.cpp b/src/pipeline/CommonBindings.cpp index d4be59051..9469d4156 100644 --- a/src/pipeline/CommonBindings.cpp +++ b/src/pipeline/CommonBindings.cpp @@ -1,5 +1,8 @@ #include "CommonBindings.hpp" +// Libraries +#include "hedley/hedley.h" + // depthai-shared #include "depthai-shared/common/CameraBoardSocket.hpp" #include "depthai-shared/common/EepromData.hpp" @@ -141,11 +144,31 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ .value("CAM_H", CameraBoardSocket::CAM_H) // Deprecated - // TODO(themarpe) - issue a Deprecation warning + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED .value("RGB", CameraBoardSocket::RGB, "**Deprecated:** Use CAM_A or address camera by name instead") .value("LEFT", CameraBoardSocket::LEFT, "**Deprecated:** Use CAM_B or address camera by name instead") .value("RIGHT", CameraBoardSocket::RIGHT, "**Deprecated:** Use CAM_C or address camera by name instead") .value("CENTER", CameraBoardSocket::CENTER, "**Deprecated:** Use CAM_A or address camera by name instead") + + // Deprecated overriden + .def_property_readonly_static("RGB", [](py::object){ + PyErr_WarnEx(PyExc_DeprecationWarning, "RGB is deprecated, use CAM_A or address camera by name instead.", 1); + return CameraBoardSocket::CAM_A; + }) + .def_property_readonly_static("CENTER", [](py::object){ + PyErr_WarnEx(PyExc_DeprecationWarning, "CENTER is deprecated, use CAM_A or address camera by name instead.", 1); + return CameraBoardSocket::CAM_A; + }) + .def_property_readonly_static("LEFT", [](py::object){ + PyErr_WarnEx(PyExc_DeprecationWarning, "LEFT is deprecated, use CAM_B or address camera by name instead.", 1); + return CameraBoardSocket::CAM_B; + }) + .def_property_readonly_static("RIGHT", [](py::object){ + PyErr_WarnEx(PyExc_DeprecationWarning, "RIGHT is deprecated, use CAM_C or address camera by name instead.", 1); + return CameraBoardSocket::CAM_C; + }) + HEDLEY_DIAGNOSTIC_POP ; // CameraSensorType enum bindings From 6ccf5b5c13ca1ae3b822278f0f7ba2622bb6109f Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Sun, 23 Apr 2023 17:22:58 +0200 Subject: [PATCH 281/385] Fixed Hedley usage --- src/pipeline/CommonBindings.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pipeline/CommonBindings.cpp b/src/pipeline/CommonBindings.cpp index 9469d4156..4c3d4d36f 100644 --- a/src/pipeline/CommonBindings.cpp +++ b/src/pipeline/CommonBindings.cpp @@ -132,6 +132,10 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ ; // CameraBoardSocket enum bindings + + // Deprecated + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED cameraBoardSocket .value("AUTO", CameraBoardSocket::AUTO) .value("CAM_A", CameraBoardSocket::CAM_A) @@ -143,9 +147,6 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ .value("CAM_G", CameraBoardSocket::CAM_G) .value("CAM_H", CameraBoardSocket::CAM_H) - // Deprecated - HEDLEY_DIAGNOSTIC_PUSH - HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED .value("RGB", CameraBoardSocket::RGB, "**Deprecated:** Use CAM_A or address camera by name instead") .value("LEFT", CameraBoardSocket::LEFT, "**Deprecated:** Use CAM_B or address camera by name instead") .value("RIGHT", CameraBoardSocket::RIGHT, "**Deprecated:** Use CAM_C or address camera by name instead") @@ -168,8 +169,8 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ PyErr_WarnEx(PyExc_DeprecationWarning, "RIGHT is deprecated, use CAM_C or address camera by name instead.", 1); return CameraBoardSocket::CAM_C; }) - HEDLEY_DIAGNOSTIC_POP ; + HEDLEY_DIAGNOSTIC_POP // CameraSensorType enum bindings cameraSensorType From 4cac921ad3520b29cd85730a8b6bf8ce4dd73962 Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Sun, 23 Apr 2023 18:47:25 +0200 Subject: [PATCH 282/385] Added more checks when parsing message and a test --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index dbb0f95bd..a24e90707 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit dbb0f95bd0fff6ef5111c7c1440b214067e0102d +Subproject commit a24e9070703c99a7df226696d3d5d806452946d4 From c53a7e68c0bd0668853268ed94960df655a7fd4f Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sun, 23 Apr 2023 19:25:45 +0200 Subject: [PATCH 283/385] Added note that only ffc-4p should be used --- docs/source/samples/Script/script_uart.rst | 4 ++++ examples/Script/script_uart.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/source/samples/Script/script_uart.rst b/docs/source/samples/Script/script_uart.rst index e046b6031..5e4656890 100644 --- a/docs/source/samples/Script/script_uart.rst +++ b/docs/source/samples/Script/script_uart.rst @@ -5,6 +5,10 @@ This example uses :ref:`Script` node for `UART communication `__ to expose UART pins. +.. note:: + + This should only be run on OAK-FFC-4P, as other OAK cameras might have different GPIO configuration. + Demo #### diff --git a/examples/Script/script_uart.py b/examples/Script/script_uart.py index d20bc8b47..329a0ed59 100644 --- a/examples/Script/script_uart.py +++ b/examples/Script/script_uart.py @@ -1,3 +1,7 @@ +#!/usr/bin/env python3 +''' +NOTE: This should only be run on OAK-FFC-4P, as other OAK cameras might have different GPIO configuration! +''' import depthai as dai import time From a4621cea6ed348fbc4219f0708523d789f238112 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sun, 23 Apr 2023 19:26:26 +0200 Subject: [PATCH 284/385] Fixed docs building --- docs/requirements_mkdoc.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements_mkdoc.txt b/docs/requirements_mkdoc.txt index 5217cfef6..700047e08 100644 --- a/docs/requirements_mkdoc.txt +++ b/docs/requirements_mkdoc.txt @@ -1 +1 @@ -git+https://github.com/luxonis/pybind11_mkdoc.git@47a353ae22a3ca2fe1ca47f47b38613dcfb1043b +git+https://github.com/luxonis/pybind11_mkdoc.git@59746f8d1645c9f00ebfb534186334d0154b5bd6 From d4071b3a47dd7ca5483666244e7121ec9be488c6 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sun, 23 Apr 2023 19:27:22 +0200 Subject: [PATCH 285/385] Fix docs building --- docs/requirements_mkdoc.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements_mkdoc.txt b/docs/requirements_mkdoc.txt index 5217cfef6..700047e08 100644 --- a/docs/requirements_mkdoc.txt +++ b/docs/requirements_mkdoc.txt @@ -1 +1 @@ -git+https://github.com/luxonis/pybind11_mkdoc.git@47a353ae22a3ca2fe1ca47f47b38613dcfb1043b +git+https://github.com/luxonis/pybind11_mkdoc.git@59746f8d1645c9f00ebfb534186334d0154b5bd6 From 34813695349dfcdc0e9593ccfa0822cec589db7c Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sun, 23 Apr 2023 19:28:13 +0200 Subject: [PATCH 286/385] Update footer (#814) * Update docs footer (cherry picked from commit 3a452669844cc1dfab829af8dd54515f1ae6f7f5) * update clang version * Updated dockerfile * Second test, change pybind_mkdoc * Updated back to luxonis pybind mkdoc, new commit --- docs/requirements_mkdoc.txt | 2 +- docs/source/includes/footer-short.rst | 27 ++----------------- .../tutorials/configuring-stereo-depth.rst | 2 +- 3 files changed, 4 insertions(+), 27 deletions(-) diff --git a/docs/requirements_mkdoc.txt b/docs/requirements_mkdoc.txt index 5217cfef6..700047e08 100644 --- a/docs/requirements_mkdoc.txt +++ b/docs/requirements_mkdoc.txt @@ -1 +1 @@ -git+https://github.com/luxonis/pybind11_mkdoc.git@47a353ae22a3ca2fe1ca47f47b38613dcfb1043b +git+https://github.com/luxonis/pybind11_mkdoc.git@59746f8d1645c9f00ebfb534186334d0154b5bd6 diff --git a/docs/source/includes/footer-short.rst b/docs/source/includes/footer-short.rst index e868bce60..67cb68912 100644 --- a/docs/source/includes/footer-short.rst +++ b/docs/source/includes/footer-short.rst @@ -1,28 +1,5 @@ .. raw:: html -

Got questions?

+

Got questions?

-We're always happy to help with code or other questions you might have. - -.. raw:: html - -
+ Head over to Discussion Forum for technical support or any other questions you might have. diff --git a/docs/source/tutorials/configuring-stereo-depth.rst b/docs/source/tutorials/configuring-stereo-depth.rst index 1562fd5e4..f65a39c65 100644 --- a/docs/source/tutorials/configuring-stereo-depth.rst +++ b/docs/source/tutorials/configuring-stereo-depth.rst @@ -277,7 +277,7 @@ just note it's a very large output (eg. 1280*800*96 => 98MB for each frame). Stereo Subpixel mode will calculate subpixel disparity by looking at the confidence values of the 2 neighboring disparity pixels in each direction. In the above example graph, in normal mode, StereoDepth would just get the max disparity = 34 pixels, but in Subpixel -mode, it will return a bit more, eg. 37.375 pixels, as confidences for pixels 35 and 36 are quite high as well. +mode, it will return a bit more, eg. 34.375 pixels, as confidences for pixels 35 and 36 are quite high as well. **TL;DR:** Stereo Subpixel mode should always provide more accurate depth, but will consume additional HW resources (see :ref:`Stereo depth FPS` for impact). From 091845ebf2b3ca82c51ee7d50eea3e45cb11ca76 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 24 Apr 2023 15:25:33 +0200 Subject: [PATCH 287/385] Add get/set to all config messages --- depthai-core | 2 +- src/pipeline/datatype/CameraControlBindings.cpp | 2 ++ src/pipeline/datatype/EdgeDetectorConfigBindings.cpp | 2 ++ src/pipeline/datatype/ImageManipConfigBindings.cpp | 2 ++ .../datatype/SpatialLocationCalculatorConfigBindings.cpp | 2 ++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index dbb0f95bd..cb80458e7 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit dbb0f95bd0fff6ef5111c7c1440b214067e0102d +Subproject commit cb80458e799923f4ac749ef34c7f69326a32f02a diff --git a/src/pipeline/datatype/CameraControlBindings.cpp b/src/pipeline/datatype/CameraControlBindings.cpp index 9fb47a62d..06ebb4ab4 100644 --- a/src/pipeline/datatype/CameraControlBindings.cpp +++ b/src/pipeline/datatype/CameraControlBindings.cpp @@ -214,11 +214,13 @@ std::vector camCtrlAttr; .def("setChromaDenoise", &CameraControl::setChromaDenoise, py::arg("value"), DOC(dai, CameraControl, setChromaDenoise)) .def("setSceneMode", &CameraControl::setSceneMode, py::arg("mode"), DOC(dai, CameraControl, setSceneMode)) .def("setEffectMode", &CameraControl::setEffectMode, py::arg("mode"), DOC(dai, CameraControl, setEffectMode)) + .def("set", &CameraControl::set, py::arg("config"), DOC(dai, CameraControl, set)) // getters .def("getCaptureStill", &CameraControl::getCaptureStill, DOC(dai, CameraControl, getCaptureStill)) .def("getExposureTime", &CameraControl::getExposureTime, DOC(dai, CameraControl, getExposureTime)) .def("getSensitivity", &CameraControl::getSensitivity, DOC(dai, CameraControl, getSensitivity)) .def("getLensPosition", &CameraControl::getLensPosition, DOC(dai, CameraControl, getLensPosition)) + .def("get", &CameraControl::get, DOC(dai, CameraControl, get)) ; // Add also enum attributes from RawCameraControl for (const auto& a : camCtrlAttr) { diff --git a/src/pipeline/datatype/EdgeDetectorConfigBindings.cpp b/src/pipeline/datatype/EdgeDetectorConfigBindings.cpp index fd5e39a11..eaefb6825 100644 --- a/src/pipeline/datatype/EdgeDetectorConfigBindings.cpp +++ b/src/pipeline/datatype/EdgeDetectorConfigBindings.cpp @@ -50,6 +50,8 @@ void bind_edgedetectorconfig(pybind11::module& m, void* pCallstack){ .def(py::init<>()) .def("setSobelFilterKernels", &EdgeDetectorConfig::setSobelFilterKernels, py::arg("horizontalKernel"), py::arg("verticalKernel"), DOC(dai, EdgeDetectorConfig, setSobelFilterKernels)) .def("getConfigData", &EdgeDetectorConfig::getConfigData, DOC(dai, EdgeDetectorConfig, getConfigData)) + .def("get", &EdgeDetectorConfig::get, DOC(dai, EdgeDetectorConfig, get)) + .def("set", &EdgeDetectorConfig::set, py::arg("config"), DOC(dai, EdgeDetectorConfig, set)) ; } diff --git a/src/pipeline/datatype/ImageManipConfigBindings.cpp b/src/pipeline/datatype/ImageManipConfigBindings.cpp index e94ced202..694912999 100644 --- a/src/pipeline/datatype/ImageManipConfigBindings.cpp +++ b/src/pipeline/datatype/ImageManipConfigBindings.cpp @@ -120,6 +120,7 @@ void bind_imagemanipconfig(pybind11::module& m, void* pCallstack){ .def("setReusePreviousImage", &ImageManipConfig::setReusePreviousImage, py::arg("reuse"), DOC(dai, ImageManipConfig, setReusePreviousImage)) .def("setSkipCurrentImage", &ImageManipConfig::setSkipCurrentImage, py::arg("skip"), DOC(dai, ImageManipConfig, setSkipCurrentImage)) .def("setKeepAspectRatio", &ImageManipConfig::setKeepAspectRatio, py::arg("keep"), DOC(dai, ImageManipConfig, setKeepAspectRatio)) + .def("set", &ImageManipConfig::set, py::arg("config"), DOC(dai, ImageManipConfig, set)) // getters .def("getCropXMin", &ImageManipConfig::getCropXMin, DOC(dai, ImageManipConfig, getCropXMin)) @@ -133,6 +134,7 @@ void bind_imagemanipconfig(pybind11::module& m, void* pCallstack){ .def("getFormatConfig", &ImageManipConfig::getFormatConfig, DOC(dai, ImageManipConfig, getFormatConfig)) .def("isResizeThumbnail", &ImageManipConfig::isResizeThumbnail, DOC(dai, ImageManipConfig, isResizeThumbnail)) .def("getColormap", &ImageManipConfig::getColormap, DOC(dai, ImageManipConfig, getColormap)) + .def("get", &ImageManipConfig::get, DOC(dai, ImageManipConfig, get)) ; diff --git a/src/pipeline/datatype/SpatialLocationCalculatorConfigBindings.cpp b/src/pipeline/datatype/SpatialLocationCalculatorConfigBindings.cpp index 84b6a4851..7d56a7d5e 100644 --- a/src/pipeline/datatype/SpatialLocationCalculatorConfigBindings.cpp +++ b/src/pipeline/datatype/SpatialLocationCalculatorConfigBindings.cpp @@ -64,6 +64,8 @@ void bind_spatiallocationcalculatorconfig(pybind11::module& m, void* pCallstack) .def("setROIs", &SpatialLocationCalculatorConfig::setROIs, py::arg("ROIs"), DOC(dai, SpatialLocationCalculatorConfig, setROIs)) .def("addROI", &SpatialLocationCalculatorConfig::addROI, py::arg("ROI"), DOC(dai, SpatialLocationCalculatorConfig, addROI)) .def("getConfigData", &SpatialLocationCalculatorConfig::getConfigData, DOC(dai, SpatialLocationCalculatorConfig, getConfigData)) + .def("set", &SpatialLocationCalculatorConfig::set, py::arg("config"), DOC(dai, SpatialLocationCalculatorConfig, set)) + .def("get", &SpatialLocationCalculatorConfig::get, DOC(dai, SpatialLocationCalculatorConfig, get)) ; } From d92ed41e715c77c1ff7e04831122dbdb2f91a206 Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Wed, 26 Apr 2023 20:26:36 +0200 Subject: [PATCH 288/385] Added bindings for some logging and profiling modifications --- depthai-core | 2 +- src/DeviceBindings.cpp | 8 ++++++-- src/XLinkBindings.cpp | 1 + src/pipeline/CommonBindings.cpp | 7 +++++++ src/pipeline/datatype/ImgFrameBindings.cpp | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/depthai-core b/depthai-core index cb80458e7..d27ad1e39 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit cb80458e799923f4ac749ef34c7f69326a32f02a +Subproject commit d27ad1e39528f2ecdd160d41feb5716790cb8309 diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index cb0990572..868322e43 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -482,13 +482,15 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("version", &Device::Config::version) .def_readwrite("board", &Device::Config::board) .def_readwrite("nonExclusiveMode", &Device::Config::nonExclusiveMode) + .def_readwrite("outputLogLevel", &Device::Config::outputLogLevel) + .def_readwrite("logLevel", &Device::Config::logLevel) ; // Bind CrashDump crashDump .def(py::init<>()) .def("serializeToJson", &CrashDump::serializeToJson, DOC(dai, CrashDump, serializeToJson)) - + .def_readwrite("crashReports", &CrashDump::crashReports, DOC(dai, CrashDump, crashReports)) .def_readwrite("depthaiCommitHash", &CrashDump::depthaiCommitHash, DOC(dai, CrashDump, depthaiCommitHash)) .def_readwrite("deviceId", &CrashDump::deviceId, DOC(dai, CrashDump, deviceId)) @@ -501,7 +503,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("crashedThreadId", &CrashDump::CrashReport::crashedThreadId, DOC(dai, CrashDump, CrashReport, crashedThreadId)) .def_readwrite("threadCallstack", &CrashDump::CrashReport::threadCallstack, DOC(dai, CrashDump, CrashReport, threadCallstack)) ; - + errorSourceInfo .def(py::init<>()) .def_readwrite("assertContext", &CrashDump::CrashReport::ErrorSourceInfo::assertContext, DOC(dai, CrashDump, CrashReport, ErrorSourceInfo, assertContext)) @@ -566,6 +568,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_static("getEmbeddedDeviceBinary", py::overload_cast(&DeviceBase::getEmbeddedDeviceBinary), py::arg("config"), DOC(dai, DeviceBase, getEmbeddedDeviceBinary, 2)) .def_static("getDeviceByMxId", &DeviceBase::getDeviceByMxId, py::arg("mxId"), DOC(dai, DeviceBase, getDeviceByMxId)) .def_static("getAllConnectedDevices", &DeviceBase::getAllConnectedDevices, DOC(dai, DeviceBase, getAllConnectedDevices)) + .def_static("getGlobalProfilingData", &DeviceBase::getGlobalProfilingData, DOC(dai, DeviceBase, getGlobalProfilingData)) // methods .def("getBootloaderVersion", &DeviceBase::getBootloaderVersion, DOC(dai, DeviceBase, getBootloaderVersion)) @@ -612,6 +615,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def("getUsbSpeed", [](DeviceBase& d) { py::gil_scoped_release release; return d.getUsbSpeed(); }, DOC(dai, DeviceBase, getUsbSpeed)) .def("getDeviceInfo", [](DeviceBase& d) { py::gil_scoped_release release; return d.getDeviceInfo(); }, DOC(dai, DeviceBase, getDeviceInfo)) .def("getMxId", [](DeviceBase& d) { py::gil_scoped_release release; return d.getMxId(); }, DOC(dai, DeviceBase, getMxId)) + .def("getProfilingData", [](DeviceBase& d) { py::gil_scoped_release release; return d.getProfilingData(); }, DOC(dai, DeviceBase, getProfilingData)) .def("readCalibration", [](DeviceBase& d) { py::gil_scoped_release release; return d.readCalibration(); }, DOC(dai, DeviceBase, readCalibration)) .def("flashCalibration", [](DeviceBase& d, CalibrationHandler calibrationDataHandler) { py::gil_scoped_release release; return d.flashCalibration(calibrationDataHandler); }, py::arg("calibrationDataHandler"), DOC(dai, DeviceBase, flashCalibration)) .def("setXLinkChunkSize", [](DeviceBase& d, int s) { py::gil_scoped_release release; d.setXLinkChunkSize(s); }, py::arg("sizeBytes"), DOC(dai, DeviceBase, setXLinkChunkSize)) diff --git a/src/XLinkBindings.cpp b/src/XLinkBindings.cpp index 080a0deb8..bb5475fd3 100644 --- a/src/XLinkBindings.cpp +++ b/src/XLinkBindings.cpp @@ -137,6 +137,7 @@ void XLinkBindings::bind(pybind11::module &m, void *pCallstack) .def_static("getFirstDevice", &XLinkConnection::getFirstDevice, py::arg("state") = X_LINK_ANY_STATE, py::arg("skipInvalidDevice") = true) .def_static("getDeviceByMxId", &XLinkConnection::getDeviceByMxId, py::arg("mxId"), py::arg("state") = X_LINK_ANY_STATE, py::arg("skipInvalidDevice") = true) .def_static("bootBootloader", &XLinkConnection::bootBootloader, py::arg("devInfo")) + .def_static("getGlobalProfilingData", &XLinkConnection::getGlobalProfilingData, DOC(dai, XLinkConnection, getGlobalProfilingData)) ; xLinkError diff --git a/src/pipeline/CommonBindings.cpp b/src/pipeline/CommonBindings.cpp index 4c3d4d36f..cca7e769d 100644 --- a/src/pipeline/CommonBindings.cpp +++ b/src/pipeline/CommonBindings.cpp @@ -28,6 +28,7 @@ // depthai #include "depthai/common/CameraFeatures.hpp" #include "depthai/common/CameraExposureOffset.hpp" +#include "depthai/utility/ProfilingData.hpp" void CommonBindings::bind(pybind11::module& m, void* pCallstack){ @@ -60,6 +61,7 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ py::enum_ cameraExposureOffset(m, "CameraExposureOffset"); py::enum_ colormap(m, "Colormap", DOC(dai, Colormap)); py::enum_ frameEvent(m, "FrameEvent", DOC(dai, FrameEvent)); + py::class_ profilingData(m, "ProfilingData", DOC(dai, ProfilingData)); /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// @@ -375,4 +377,9 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ .value("READOUT_END", FrameEvent::READOUT_END) ; + profilingData + .def_readwrite("numBytesWritten", &ProfilingData::numBytesWritten, DOC(dai, ProfilingData, numBytesWritten)) + .def_readwrite("numBytesRead", &ProfilingData::numBytesRead, DOC(dai, ProfilingData, numBytesRead)) + ; + } diff --git a/src/pipeline/datatype/ImgFrameBindings.cpp b/src/pipeline/datatype/ImgFrameBindings.cpp index bc2c8b768..b39b1e721 100644 --- a/src/pipeline/datatype/ImgFrameBindings.cpp +++ b/src/pipeline/datatype/ImgFrameBindings.cpp @@ -234,7 +234,7 @@ void bind_imgframe(pybind11::module& m, void* pCallstack){ + ", actual " + std::to_string(actualSize) + ". Maybe metadataOnly transfer was made?"); } else if(actualSize > requiredSize) { // FIXME check build on Windows - // spdlog::warn("ImgFrame has excess data: actual {}, expected {}", actualSize, requiredSize); + // logger::warn("ImgFrame has excess data: actual {}, expected {}", actualSize, requiredSize); } if(img.getWidth() <= 0 || img.getHeight() <= 0){ throw std::runtime_error("ImgFrame size invalid (width: " + std::to_string(img.getWidth()) + ", height: " + std::to_string(img.getHeight()) + ")"); From cadde475de5eaf6db97557d3e54ee65f01212628 Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Thu, 27 Apr 2023 03:20:26 +0200 Subject: [PATCH 289/385] [DeviceManager] improvements & [FW] Updated for IMX296 support --- depthai-core | 2 +- utilities/device_manager.py | 181 ++++++++++++++++++++++-------------- 2 files changed, 113 insertions(+), 70 deletions(-) diff --git a/depthai-core b/depthai-core index d27ad1e39..3793cf5a3 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit d27ad1e39528f2ecdd160d41feb5716790cb8309 +Subproject commit 3793cf5a3a556322ed6b8dfbf004a75d65238eba diff --git a/utilities/device_manager.py b/utilities/device_manager.py index ea2cf44e9..99814f10b 100755 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -10,6 +10,7 @@ from typing import Dict import platform import os +import numpy if USE_OPENCV: # import cv2 @@ -20,10 +21,14 @@ SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +PLATFORM_ICON_PATH = None if platform.system() == 'Windows': - sg.set_global_icon(f'{SCRIPT_DIR}/assets/icon.ico') + PLATFORM_ICON_PATH = f'{SCRIPT_DIR}/assets/icon.ico' else: - sg.set_global_icon(f'{SCRIPT_DIR}/assets/icon.png') + PLATFORM_ICON_PATH = f'{SCRIPT_DIR}/assets/icon.png' + +# Apply icon globally +sg.set_global_icon(PLATFORM_ICON_PATH) CONF_TEXT_POE = ['ipTypeText', 'ipText', 'maskText', 'gatewayText', 'dnsText', 'dnsAltText', 'networkTimeoutText', 'macText'] CONF_INPUT_POE = ['staticBut', 'dynamicBut', 'ip', 'mask', 'gateway', 'dns', 'dnsAlt', 'networkTimeout', 'mac'] @@ -38,27 +43,35 @@ def PrintException(): filename = f.f_code.co_filename print('Exception in {}, line {}; {}'.format(filename, lineno, exc_obj)) -def check_ip(s: str, req = True): +def Popup(msg, window): + main_window_location = window.CurrentLocation() + main_window_size = window.size + # Calculate the centered location for the new window + centered_location = (main_window_location[0] + (main_window_size[0] - 50) // 2, + main_window_location[1] + (main_window_size[1] - 50) // 2) + return sg.Popup(msg, location=centered_location) + +def check_ip(window, s: str, req = True): if s == "": return not req spl = s.split(".") if len(spl) != 4: - sg.Popup("Wrong IP format.\nValue should be similar to 255.255.255.255") + Popup("Wrong IP format.\nValue should be similar to 255.255.255.255", window=window) return False for num in spl: if 255 < int(num): - sg.Popup("Wrong IP format.\nValues can not be above 255!") + Popup("Wrong IP format.\nValues can not be above 255!", window=window) return False return True -def check_mac(s): +def check_mac(window, s): if s.count(":") != 5: - sg.Popup("Wrong MAC format.\nValue should be similar to FF:FF:FF:FF:FF:FF") + Popup("Wrong MAC format.\nValue should be similar to FF:FF:FF:FF:FF:FF", window=window) return False for i in s.split(":"): for j in i: if j > "F" or (j < "A" and not j.isdigit()) or len(i) != 2: - sg.Popup("Wrong MAC format.\nValue should be similar to FF:FF:FF:FF:FF:FF") + Popup("Wrong MAC format.\nValue should be similar to FF:FF:FF:FF:FF:FF", window=window) return False return True @@ -113,7 +126,7 @@ def wait(self): class SelectIP: - def __init__(self): + def __init__(self, window): self.ok = False layout = [ [sg.Text("Specify the custom IP of the OAK PoE\ncamera you want to connect to")], @@ -124,15 +137,24 @@ def __init__(self): [sg.Submit(), sg.Cancel()], ] self.window = sg.Window("Specify IP", layout, size=(300,110), modal=True, finalize=True) + + main_window_location = window.CurrentLocation() + main_window_size = window.size + new_window_size = self.window.size + # Calculate the centered location for the new window + centered_location = (main_window_location[0] + (main_window_size[0] - new_window_size[0]) // 2, + main_window_location[1] + (main_window_size[1] - new_window_size[1]) // 2) + self.window.move(*centered_location) + def wait(self): event, values = self.window.Read() self.window.close() - if str(event) == "Cancel" or values is None or not check_ip(values["ip"]): + if str(event) == "Cancel" or values is None or not check_ip(self.window, values["ip"]): return False, "" return True, values["ip"] class SearchDevice: - def __init__(self): + def __init__(self, window): self.infos = [] layout = [ [sg.Text("Select an OAK camera you would like to connect to.", font=('Arial', 10, 'bold'))], @@ -156,12 +178,22 @@ def __init__(self): [sg.Button('Search', size=(15, 2), font=('Arial', 10, 'bold'))], ] self.window = sg.Window("Select Device", layout, size=(550,375), modal=True, finalize=True) + + main_window_location = window.CurrentLocation() + main_window_size = window.size + new_window_size = self.window.size + # Calculate the centered location for the new window + centered_location = (main_window_location[0] + (main_window_size[0] - new_window_size[0]) // 2, + main_window_location[1] + (main_window_size[1] - new_window_size[1]) // 2) + self.window.move(*centered_location) + self.search_devices() def search_devices(self): self.infos = dai.XLinkConnection.getAllConnectedDevices() if not self.infos: - sg.Popup("No devices found.") + pass + # sg.Popup("No devices found.") else: rows = [] for info in self.infos: @@ -257,21 +289,20 @@ def factoryReset(device: dai.DeviceInfo, type: dai.DeviceBootloader.Type): def connectAndStartStreaming(dev): - # OpenCV - if USE_OPENCV: + with dai.Device(dev) as d: # Create pipeline pipeline = dai.Pipeline() + # OpenCV + if USE_OPENCV: + camRgb = pipeline.create(dai.node.ColorCamera) + camRgb.setIspScale(1,3) + videnc = pipeline.create(dai.node.VideoEncoder) + videnc.setDefaultProfilePreset(camRgb.getFps(), videnc.Properties.Profile.MJPEG) + xout = pipeline.create(dai.node.XLinkOut) + xout.setStreamName("mjpeg") + camRgb.video.link(videnc.input) + videnc.bitstream.link(xout.input) - camRgb = pipeline.create(dai.node.ColorCamera) - camRgb.setIspScale(1,3) - videnc = pipeline.create(dai.node.VideoEncoder) - videnc.setDefaultProfilePreset(camRgb.getFps(), videnc.Properties.Profile.MJPEG) - xout = pipeline.create(dai.node.XLinkOut) - xout.setStreamName("mjpeg") - camRgb.video.link(videnc.input) - videnc.bitstream.link(xout.input) - - with dai.Device(pipeline, dev) as d: while not d.isClosed(): mjpeg = d.getOutputQueue('mjpeg').get() frame = cv2.imdecode(mjpeg.getData(), cv2.IMREAD_UNCHANGED) @@ -279,21 +310,22 @@ def connectAndStartStreaming(dev): if cv2.waitKey(1) == ord('q'): cv2.destroyWindow('Color Camera') break - else: - # Create pipeline (no opencv) - pipeline = dai.Pipeline() - camRgb = pipeline.create(dai.node.ColorCamera) - camRgb.setIspScale(1,3) - camRgb.setPreviewSize(camRgb.getIspSize()) - camRgb.setColorOrder(camRgb.Properties.ColorOrder.RGB) - - xout = pipeline.create(dai.node.XLinkOut) - xout.input.setQueueSize(2) - xout.input.setBlocking(False) - xout.setStreamName("color") - camRgb.preview.link(xout.input) - - with dai.Device(pipeline, dev) as d: + else: + camRgb = pipeline.create(dai.node.ColorCamera) + camRgb.setIspScale(1,3) + firstSensor = d.getConnectedCameraFeatures()[0] + camRgb.setPreviewSize(firstSensor.width // 3, firstSensor.height // 3) + camRgb.setColorOrder(camRgb.Properties.ColorOrder.RGB) + + xout = pipeline.create(dai.node.XLinkOut) + xout.input.setQueueSize(2) + xout.input.setBlocking(False) + xout.setStreamName("color") + camRgb.preview.link(xout.input) + + # Start pipeline + d.startPipeline(pipeline) + frame = d.getOutputQueue('color', 2, False).get() width, height = frame.getWidth(), frame.getHeight() @@ -540,7 +572,7 @@ class DeviceManager: def __init__(self) -> None: self.window = sg.Window(title="Device Manager", - icon="assets/icon.png", + icon=PLATFORM_ICON_PATH, layout=layout, size=(645, 380), finalize=True # So we can do First search for devices @@ -556,7 +588,7 @@ def isPoE(self) -> bool: return self.bl.getType() == dai.DeviceBootloader.Type.NETWORK except Exception as ex: PrintException() - sg.Popup(f'{ex}') + Popup(f'{ex}', self.window) def isUsb(self) -> bool: return not self.isPoE() @@ -577,7 +609,7 @@ def run(self) -> None: device = self.device if deviceStateTxt(device.state) == "BOOTED": # device is already booted somewhere else - sg.Popup("Device is already booted somewhere else!") + Popup("Device is already booted somewhere else!", self.window) else: self.resetGui() self.bl = connectToDevice(device) @@ -588,7 +620,7 @@ def run(self) -> None: self.window.Element('progress').update("No device selected.") elif event == "Search": self.getDevices() # Re-search devices for dropdown - selDev = SearchDevice() + selDev = SearchDevice(window=self.window) di = selDev.wait() if di is not None: self.resetGui() @@ -601,7 +633,7 @@ def run(self) -> None: self.getConfigs() self.unlockConfig() elif event == "Specify IP": - select = SelectIP() + select = SelectIP(window=self.window) ok, ip = select.wait() if ok: self.resetGui() @@ -655,14 +687,14 @@ def run(self) -> None: print("Factory reset cancelled.") elif event == "Flash configuration": - self.flashConfig() - self.getConfigs() - self.resetGui() - if self.isUsb(): - self.unlockConfig() - else: - self.devices.clear() - self.window.Element('devices').update("Search for devices", values=[]) + if self.flashConfig() is not None: + self.getConfigs() + self.resetGui() + if self.isUsb(): + self.unlockConfig() + else: + self.devices.clear() + self.window.Element('devices').update("Search for devices", values=[]) elif event == "Clear configuration": self.clearConfig() self.getConfigs() @@ -677,7 +709,7 @@ def run(self) -> None: confJson = self.bl.readConfigData() sg.popup_scrolled(confJson, title='Configuration') except Exception as ex: - sg.popup(f'No existing config to view ({ex})') + Popup(f'No existing config to view ({ex})', self.window) elif event == "Flash application": file = sg.popup_get_file("Select .dap file", file_types=(('DepthAI Application Package', '*.dap'), ('All Files', '*.* *'))) @@ -685,9 +717,9 @@ def run(self) -> None: elif event == "Remove application": try: self.bl.flashClear() - sg.popup(f'Successfully removed application') + Popup(f'Successfully removed application', self.window) except Exception as ex: - sg.popup(f"Couldn't remove application ({ex})") + sg.popup(f"Couldn't remove application ({ex})", self.window) elif event.startswith("_unique_configBtn"): self.window['-COL1-'].update(visible=False) @@ -713,7 +745,7 @@ def run(self) -> None: elif event == "recoveryMode": if recoveryMode(self.bl): - sg.Popup(f'Device successfully put into USB recovery mode.') + Popup(f'Device successfully put into USB recovery mode.', self.window) # Device will reboot, close previous and reset GUI self.closeDevice() self.resetGui() @@ -872,6 +904,12 @@ def resetGui(self): self.window.Element('commit').update("-version-") self.window.Element('devState').update("-state-") + # Move back to 'About' page + self.window['-COL1-'].update(visible=True) + self.window['-COL2-'].update(visible=False) + self.window['-COL3-'].update(visible=False) + self.window['-COL4-'].update(visible=False) + def closeDevice(self): if self.bl is not None: self.bl.close() @@ -885,7 +923,7 @@ def getDevices(self): deviceInfos = dai.XLinkConnection.getAllConnectedDevices() if not deviceInfos: self.window.Element('devices').update("No devices") - sg.Popup("No devices found.") + # sg.Popup("No devices found.") else: for deviceInfo in deviceInfos: deviceTxt = deviceInfo.getMxId() @@ -896,7 +934,7 @@ def getDevices(self): self.window.Element('devices').update("Select device", values=listedDevices) except Exception as ex: PrintException() - sg.Popup(f'{ex}') + Popup(f'{ex}', window=self.window) def flashConfig(self): values = self.values @@ -912,12 +950,12 @@ def flashConfig(self): try: if self.isPoE: if self.values['staticBut']: - if check_ip(values['ip']) and check_ip(values['mask']) and check_ip(values['gateway'], req=False): + if check_ip(self.window, values['ip']) and check_ip(self.window, values['mask']) and check_ip(self.window, values['gateway'], req=False): conf.setStaticIPv4(values['ip'], values['mask'], values['gateway']) else: raise Exception('IP or Mask missing using static IP configuration') else: - if check_ip(values['ip'], req=False) and check_ip(values['mask'], req=False) and check_ip(values['gateway'], req=False): + if check_ip(self.window, values['ip'], req=False) and check_ip(self.window, values['mask'], req=False) and check_ip(self.window, values['gateway'], req=False): conf.setDynamicIPv4(values['ip'], values['mask'], values['gateway']) conf.setDnsIPv4(values['dns'], values['dnsAlt']) @@ -925,9 +963,9 @@ def flashConfig(self): if int(values['networkTimeout']) >= 0: conf.setNetworkTimeout(timedelta(seconds=int(values['networkTimeout']) / 1000)) else: - sg.Popup("Values can not be negative!") + Popup("Values can not be negative!", window=self.window) if values['mac'] != "": - if check_mac(values['mac']): + if check_mac(self.window, values['mac']): conf.setMacAddress(values['mac']) else: conf.setMacAddress('00:00:00:00:00:00') @@ -936,29 +974,34 @@ def flashConfig(self): if int(values['usbTimeout']) >= 0: conf.setUsbTimeout(timedelta(seconds=int(values['usbTimeout']) / 1000)) else: - sg.Popup("Values can not be negative!") + Popup("Values can not be negative!", window=self.window) if values['usbSpeed'] != "": conf.setUsbMaxSpeed(getattr(dai.UsbSpeed, values['usbSpeed'])) success, error = self.bl.flashConfig(conf) if not success: - sg.Popup(f"Flashing failed: {error}") + Popup(f"Flashing failed: {error}", window=self.window) + return False else: - sg.Popup("Flashing successful.") + Popup("Flashing successful.", window=self.window) + return True + except Exception as ex: PrintException() - sg.Popup(f'{ex}') + Popup(f'{ex}', window=self.window) + + return None def clearConfig(self): try: success, error = self.bl.flashConfigClear() if not success: - sg.Popup(f"Clearing configuration failed: {error}") + Popup(f"Clearing configuration failed: {error}", window=self.window) else: - sg.Popup("Successfully cleared configuration.") + Popup("Successfully cleared configuration.", window=self.window) except Exception as ex: PrintException() - sg.Popup(f'{ex}') + Popup(f'{ex}', window=self.window) app = DeviceManager() From cc6ea3e147408c275b477494bfce44749b010572 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Tue, 2 May 2023 05:41:47 +0300 Subject: [PATCH 290/385] cam_test.py: fix KeyError due to socket name changes, add -cams cama/camb/camc options --- utilities/cam_test.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 6d04596e7..70ff2f0ce 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -48,7 +48,7 @@ def socket_type_pair(arg): socket, type = arg.split(',') - if not (socket in ['rgb', 'left', 'right', 'camd']): + if not (socket in ['rgb', 'left', 'right', 'cama', 'camb', 'camc', 'camd']): raise ValueError("") if not (type in ['m', 'mono', 'c', 'color']): raise ValueError("") @@ -112,20 +112,19 @@ def socket_type_pair(arg): 'rgb' : dai.CameraBoardSocket.CAM_A, 'left' : dai.CameraBoardSocket.CAM_B, 'right': dai.CameraBoardSocket.CAM_C, + 'cama' : dai.CameraBoardSocket.CAM_A, + 'camb' : dai.CameraBoardSocket.CAM_B, + 'camc' : dai.CameraBoardSocket.CAM_C, 'camd' : dai.CameraBoardSocket.CAM_D, } -cam_socket_to_name = { - 'RGB': 'rgb', - 'LEFT': 'left', - 'RIGHT': 'right', - 'CAM_D': 'camd', -} - rotate = { 'rgb': args.rotate in ['all', 'rgb'], 'left': args.rotate in ['all', 'mono'], 'right': args.rotate in ['all', 'mono'], + 'cama': args.rotate in ['all', 'rgb'], + 'camb': args.rotate in ['all', 'mono'], + 'camc': args.rotate in ['all', 'mono'], 'camd': args.rotate in ['all', 'rgb'], } @@ -231,7 +230,7 @@ def exit_cleanly(signum, frame): f' -socket {p.socket.name:6}: {p.sensorName:6} {p.width:4} x {p.height:4} focus:', end='') print('auto ' if p.hasAutofocus else 'fixed', '- ', end='') print(*[type.name for type in p.supportedTypes]) - cam_name[cam_socket_to_name[p.socket.name]] = p.sensorName + cam_name[p.socket.name] = p.sensorName print('USB speed:', device.getUsbSpeed().name) @@ -311,7 +310,7 @@ def exit_cleanly(signum, frame): frame = pkt.getCvFrame() if c in capture_list: width, height = pkt.getWidth(), pkt.getHeight() - capture_file_name = ('capture_' + c + '_' + cam_name[c] + capture_file_name = ('capture_' + c + '_' + cam_name[cam_socket_opts[c].name] + '_' + str(width) + 'x' + str(height) + '_exp_' + str(int( From 7533b5a9bfc5b8648612cd5a1562a9295a6e9020 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 2 May 2023 23:36:37 +0200 Subject: [PATCH 291/385] Updated XLink with 255.255.255.255 discovery added --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index cb80458e7..d7ee0f7bb 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit cb80458e799923f4ac749ef34c7f69326a32f02a +Subproject commit d7ee0f7bba653e9c9857ca482f656ce4d67bdf59 From e3b975bdf3f1984e148c6f66abdd2b918113be14 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 2 May 2023 23:37:07 +0200 Subject: [PATCH 292/385] Applied formatting --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index d7ee0f7bb..c8187b554 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit d7ee0f7bba653e9c9857ca482f656ce4d67bdf59 +Subproject commit c8187b5548d3862b45c4811d40e0e73e92898179 From 1ca3ab68eb2e152afafc34bc622f9e67f7ee8315 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 3 May 2023 00:31:49 +0200 Subject: [PATCH 293/385] [Stereo] Fix auto distortion correction for 400p --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index c8187b554..8ed7b6a43 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit c8187b5548d3862b45c4811d40e0e73e92898179 +Subproject commit 8ed7b6a4352ebe5ea65f2861ce9e09a3f3574851 From c83551c057576c53439e65928d15cebd31e59cab Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 3 May 2023 01:33:41 +0200 Subject: [PATCH 294/385] [Stereo] Fix temporal filter crash on startup --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 8ed7b6a43..9096d62e3 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 8ed7b6a4352ebe5ea65f2861ce9e09a3f3574851 +Subproject commit 9096d62e3598afbc005f20d7d9f0420470da7234 From f26d0ca43b574dec9d5c05c97cafbff0e721d2c9 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 5 May 2023 13:35:46 +0200 Subject: [PATCH 295/385] Add missing info log level --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 9096d62e3..41681497a 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 9096d62e3598afbc005f20d7d9f0420470da7234 +Subproject commit 41681497a365de42562eceea35528cb4e25689df From 26c1b3bd85c0330d5668c1f83978333bd48488eb Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Sat, 6 May 2023 23:00:36 +0300 Subject: [PATCH 296/385] core/Logging: fixed `DEPTHAI_DEBUG=1` causing a crash at init --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 41681497a..1d990f993 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 41681497a365de42562eceea35528cb4e25689df +Subproject commit 1d990f993764204220ca799c04f78fa0cad9b570 From 4d3192f6ddd6626f16718f5659feb6324cf0e7a3 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Sun, 7 May 2023 14:02:24 +0300 Subject: [PATCH 297/385] Add BoardConfig bindings for USB product/vendor strings and UVC config --- src/DeviceBindings.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 868322e43..18f138f39 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -343,6 +343,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ py::enum_ boardConfigGpioPull(boardConfigGpio, "Pull", DOC(dai, BoardConfig, GPIO, Pull)); py::enum_ boardConfigGpioDrive(boardConfigGpio, "Drive", DOC(dai, BoardConfig, GPIO, Drive)); py::class_ boardConfigUart(boardConfig, "UART", DOC(dai, BoardConfig, UART)); + py::class_ boardConfigUvc(boardConfig, "UVC", DOC(dai, BoardConfig, UVC)); struct PyClock{}; py::class_ clock(m, "Clock"); @@ -379,6 +380,8 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("flashBootedVid", &BoardConfig::USB::flashBootedVid) .def_readwrite("flashBootedPid", &BoardConfig::USB::flashBootedPid) .def_readwrite("maxSpeed", &BoardConfig::USB::maxSpeed) + .def_readwrite("productName", &BoardConfig::USB::productName) + .def_readwrite("manufacturer", &BoardConfig::USB::manufacturer) ; // Bind BoardConfig::Network @@ -456,6 +459,17 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("tmp", &BoardConfig::UART::tmp) ; + // Bind BoardConfig::UVC + boardConfigUvc + .def(py::init<>()) + .def(py::init()) + .def_readwrite("cameraName", &BoardConfig::UVC::cameraName) + .def_readwrite("width", &BoardConfig::UVC::width) + .def_readwrite("height", &BoardConfig::UVC::height) + .def_readwrite("frameType", &BoardConfig::UVC::frameType) + .def_readwrite("enable", &BoardConfig::UVC::enable) + ; + // Bind BoardConfig boardConfig .def(py::init<>()) @@ -474,6 +488,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("logSizeMax", &BoardConfig::logSizeMax, DOC(dai, BoardConfig, logSizeMax)) .def_readwrite("logVerbosity", &BoardConfig::logVerbosity, DOC(dai, BoardConfig, logVerbosity)) .def_readwrite("logDevicePrints", &BoardConfig::logDevicePrints, DOC(dai, BoardConfig, logDevicePrints)) + .def_readwrite("uvc", &BoardConfig::uvc, DOC(dai, BoardConfig, uvc)) ; // Bind Device::Config From 9920badc11adb303c5b9ea9efb1aa77798b75e8b Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Sun, 7 May 2023 14:04:17 +0300 Subject: [PATCH 298/385] Move rgb_uvc.py to examples/ColorCamera --- examples/{ => ColorCamera}/rgb_uvc.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{ => ColorCamera}/rgb_uvc.py (100%) diff --git a/examples/rgb_uvc.py b/examples/ColorCamera/rgb_uvc.py similarity index 100% rename from examples/rgb_uvc.py rename to examples/ColorCamera/rgb_uvc.py From 145b877a8298cad332d3d3c72e3b8ccb14af7875 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Sun, 7 May 2023 14:56:45 +0300 Subject: [PATCH 299/385] rgb_uvc.py: add UVC boardConfig, `--load-and-exit` script option. Flashing is possible as well on certain devices --- examples/ColorCamera/rgb_uvc.py | 50 +++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/examples/ColorCamera/rgb_uvc.py b/examples/ColorCamera/rgb_uvc.py index 00f7d76aa..42e510c95 100755 --- a/examples/ColorCamera/rgb_uvc.py +++ b/examples/ColorCamera/rgb_uvc.py @@ -4,20 +4,20 @@ import time import argparse -enable_4k = True # Will downscale 4K -> 1080p - parser = argparse.ArgumentParser() parser.add_argument('-fb', '--flash-bootloader', default=False, action="store_true") parser.add_argument('-f', '--flash-app', default=False, action="store_true") +parser.add_argument('-l', '--load-and-exit', default=False, action="store_true") args = parser.parse_args() def getPipeline(): - # Start defining a pipeline + enable_4k = True # Will downscale 4K -> 1080p + pipeline = dai.Pipeline() # Define a source - color camera cam_rgb = pipeline.createColorCamera() - cam_rgb.setBoardSocket(dai.CameraBoardSocket.RGB) + cam_rgb.setBoardSocket(dai.CameraBoardSocket.CAM_A) cam_rgb.setInterleaved(False) #cam_rgb.initialControl.setManualFocus(130) @@ -27,25 +27,29 @@ def getPipeline(): else: cam_rgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) - # Create an UVC (USB Video Class) output node. It needs 1920x1080, NV12 input + # Create an UVC (USB Video Class) output node uvc = pipeline.createUVC() cam_rgb.video.link(uvc.input) - return pipeline + # Note: if the pipeline is sent later to device (using startPipeline()), + # it is important to pass the device config separately when creating the device + config = dai.Device.Config() + # config.board.uvc = dai.BoardConfig.UVC() # enable default 1920x1080 NV12 + config.board.uvc = dai.BoardConfig.UVC(1920, 1080) + config.board.uvc.frameType = dai.ImgFrame.Type.NV12 + # config.board.uvc.cameraName = "My Custom Cam" + pipeline.setBoardConfig(config.board) -# Workaround for a bug with the timeout-enabled bootloader -progressCalled = False -# TODO move this under flash(), will need to handle `progressCalled` differently -def progress(p): - global progressCalled - progressCalled = True - print(f'Flashing progress: {p*100:.1f}%') + return pipeline # Will flash the bootloader if no pipeline is provided as argument def flash(pipeline=None): (f, bl) = dai.DeviceBootloader.getFirstAvailableDevice() bootloader = dai.DeviceBootloader(bl, True) + # Create a progress callback lambda + progress = lambda p : print(f'Flashing progress: {p*100:.1f}%') + startTime = time.monotonic() if pipeline is None: print("Flashing bootloader...") @@ -54,8 +58,6 @@ def flash(pipeline=None): print("Flashing application pipeline...") bootloader.flash(progress, pipeline) - if not progressCalled: - raise RuntimeError('Flashing failed, please try again') elapsedTime = round(time.monotonic() - startTime, 2) print("Done in", elapsedTime, "seconds") @@ -65,11 +67,23 @@ def flash(pipeline=None): print("Flashing successful. Please power-cycle the device") quit() -# Pipeline defined, now the device is connected to +if args.load_and_exit: + import os + # Disabling device watchdog, so it doesn't need the host to ping periodically + os.environ["DEPTHAI_WATCHDOG"] = "0" + device = dai.Device(getPipeline()) + print("\nDevice started, open a UVC viewer to check the camera stream.") + print("Attempting to force-quit this process...") + print("To reconnect with depthai, a device power-cycle may be required") + # We do not want the device to be closed, so kill the process. + # (TODO add depthai API to be able to cleanly exit without closing device) + import signal + os.kill(os.getpid(),signal.SIGKILL) + +# Standard UVC load with depthai with dai.Device(getPipeline()) as device: print("\nDevice started, please keep this process running") - print("and open an UVC viewer. Example on Linux:") - print(" guvcview -d /dev/video0") + print("and open an UVC viewer to check the camera stream.") print("\nTo close: Ctrl+C") # Doing nothing here, just keeping the host feeding the watchdog From 2b97f9dcfdcd550414641d30ce24da212e776bc1 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 9 May 2023 18:00:50 +0200 Subject: [PATCH 300/385] Add Stereo depth custom mesh example and docs (cherry picked from commit 747682cff7348b25b3a2d738e54e6e9997420757) (cherry picked from commit 3e2a109b3815b8a7cbac19781c9103e429b32066) --- .../StereoDepth/stereo_depth_custom_mesh.rst | 41 +++++ docs/source/tutorials/code_samples.rst | 1 + .../StereoDepth/stereo_depth_custom_mesh.py | 152 ++++++++++++++++++ examples/StereoDepth/stereo_depth_video.py | 32 ++-- 4 files changed, 204 insertions(+), 22 deletions(-) create mode 100644 docs/source/samples/StereoDepth/stereo_depth_custom_mesh.rst create mode 100644 examples/StereoDepth/stereo_depth_custom_mesh.py diff --git a/docs/source/samples/StereoDepth/stereo_depth_custom_mesh.rst b/docs/source/samples/StereoDepth/stereo_depth_custom_mesh.rst new file mode 100644 index 000000000..6921e451d --- /dev/null +++ b/docs/source/samples/StereoDepth/stereo_depth_custom_mesh.rst @@ -0,0 +1,41 @@ +Stereo Depth custom Mesh +======================== + +This example shows how you can load custom mesh to the device and use it for depth calculation. + +By default, :ref:`StereoDepth` will use the same logic as inside the ``def getMesh()`` to calculate +mesh files whenever horizontal FOV is larger than 90°. You could also force calculate the mesh using: + +.. code-block:: python + + stereo = pipeline.create(dai.node.StereoDepth) + # Enable mesh calculation to correct distortion: + stereo.enableDistortionCorrection(True) + + +StereoDepth node also allows you to load mesh files directly from a file path: + +.. code-block:: python + + stereo = pipeline.create(dai.node.StereoDepth) + stereo.loadMeshFiles('path/to/left_mesh', 'path/to/right_mesh') + +Setup +##### + +.. include:: /includes/install_from_pypi.rst + +Source code +########### + +.. tabs:: + + .. tab:: Python + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../examples/StereoDepth/stereo_depth_custom_mesh.py + :language: python + :linenos: + +.. include:: /includes/footer-short.rst diff --git a/docs/source/tutorials/code_samples.rst b/docs/source/tutorials/code_samples.rst index c924c1f9d..b8d6fe618 100644 --- a/docs/source/tutorials/code_samples.rst +++ b/docs/source/tutorials/code_samples.rst @@ -137,6 +137,7 @@ are presented with code. - :ref:`Depth Crop Control` - Demonstrates how to control cropping of depth frames from the host - :ref:`Depth Post-Processing` - Depth post-processing filters - :ref:`Depth Preview` - Displays colorized stereo disparity +- :ref:`Stereo Depth custom Mesh` - Calculate and load custom mesh for stereo depth calculation - :ref:`Stereo Depth from host` - Generates stereo depth frame from a set of mono images from the host - :ref:`Stereo Depth Video` - An extended version of **Depth Preview** - :ref:`RGB Depth alignment` - Displays RGB depth aligned frames diff --git a/examples/StereoDepth/stereo_depth_custom_mesh.py b/examples/StereoDepth/stereo_depth_custom_mesh.py new file mode 100644 index 000000000..6cd398698 --- /dev/null +++ b/examples/StereoDepth/stereo_depth_custom_mesh.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 + +import cv2 +import numpy as np +import depthai as dai +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument("-res", "--resolution", type=str, default="720", + help="Sets the resolution on mono cameras. Options: 800 | 720 | 400") +parser.add_argument("-md", "--mesh_dir", type=str, default=None, + help="Output directory for mesh files. If not specified mesh files won't be saved") +parser.add_argument("-lm", "--load_mesh", default=False, action="store_true", + help="Read camera intrinsics, generate mesh files and load them into the stereo node.") +args = parser.parse_args() + +meshDirectory = args.mesh_dir # Output dir for mesh files +generateMesh = args.load_mesh # Load mesh files +RES_MAP = { + '800': {'w': 1280, 'h': 800, 'res': dai.MonoCameraProperties.SensorResolution.THE_800_P }, + '720': {'w': 1280, 'h': 720, 'res': dai.MonoCameraProperties.SensorResolution.THE_720_P }, + '400': {'w': 640, 'h': 400, 'res': dai.MonoCameraProperties.SensorResolution.THE_400_P } +} +if args.resolution not in RES_MAP: + exit("Unsupported resolution!") + +resolution = RES_MAP[args.resolution] + +def getMesh(calibData): + M1 = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.CAM_B, resolution['w'], resolution['h'])) + d1 = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.CAM_B)) + R1 = np.array(calibData.getStereoLeftRectificationRotation()) + M2 = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.CAM_C, resolution['w'], resolution['h'])) + d2 = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.CAM_C)) + R2 = np.array(calibData.getStereoRightRectificationRotation()) + mapXL, mapYL = cv2.initUndistortRectifyMap(M1, d1, R1, M2, (resolution['w'], resolution['h']), cv2.CV_32FC1) + mapXR, mapYR = cv2.initUndistortRectifyMap(M2, d2, R2, M2, (resolution['w'], resolution['h']), cv2.CV_32FC1) + + meshCellSize = 16 + meshLeft = [] + meshRight = [] + + for y in range(mapXL.shape[0] + 1): + if y % meshCellSize == 0: + rowLeft = [] + rowRight = [] + for x in range(mapXL.shape[1] + 1): + if x % meshCellSize == 0: + if y == mapXL.shape[0] and x == mapXL.shape[1]: + rowLeft.append(mapYL[y - 1, x - 1]) + rowLeft.append(mapXL[y - 1, x - 1]) + rowRight.append(mapYR[y - 1, x - 1]) + rowRight.append(mapXR[y - 1, x - 1]) + elif y == mapXL.shape[0]: + rowLeft.append(mapYL[y - 1, x]) + rowLeft.append(mapXL[y - 1, x]) + rowRight.append(mapYR[y - 1, x]) + rowRight.append(mapXR[y - 1, x]) + elif x == mapXL.shape[1]: + rowLeft.append(mapYL[y, x - 1]) + rowLeft.append(mapXL[y, x - 1]) + rowRight.append(mapYR[y, x - 1]) + rowRight.append(mapXR[y, x - 1]) + else: + rowLeft.append(mapYL[y, x]) + rowLeft.append(mapXL[y, x]) + rowRight.append(mapYR[y, x]) + rowRight.append(mapXR[y, x]) + if (mapXL.shape[1] % meshCellSize) % 2 != 0: + rowLeft.append(0) + rowLeft.append(0) + rowRight.append(0) + rowRight.append(0) + + meshLeft.append(rowLeft) + meshRight.append(rowRight) + + meshLeft = np.array(meshLeft) + meshRight = np.array(meshRight) + + return meshLeft, meshRight + +def saveMeshFiles(meshLeft, meshRight, outputPath): + print("Saving mesh to:", outputPath) + meshLeft.tofile(outputPath + "/left_mesh.calib") + meshRight.tofile(outputPath + "/right_mesh.calib") + + +def create_pipeline(device: dai.Device) -> dai.Pipeline: + calibData = device.readCalibration() + print("Creating Stereo Depth pipeline") + pipeline = dai.Pipeline() + + camLeft = pipeline.create(dai.node.MonoCamera) + camLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) + + camRight = pipeline.create(dai.node.MonoCamera) + camRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) + + xoutRight = pipeline.create(dai.node.XLinkOut) + xoutRight.setStreamName("right") + camRight.out.link(xoutRight.input) + + for monoCam in (camLeft, camRight): # Common config + monoCam.setResolution(resolution['res']) + # monoCam.setFps(20.0) + + stereo = pipeline.create(dai.node.StereoDepth) + camLeft.out.link(stereo.left) + camRight.out.link(stereo.right) + stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) + stereo.setRectifyEdgeFillColor(0) # Black, to better see the cutout + stereo.setLeftRightCheck(True) + stereo.setExtendedDisparity(True) + + + + + xoutDisparity = pipeline.create(dai.node.XLinkOut) + xoutDisparity.setStreamName("disparity") + stereo.disparity.link(xoutDisparity.input) + + xoutRectifRight = pipeline.create(dai.node.XLinkOut) + xoutRectifRight.setStreamName("rectifiedRight") + stereo.rectifiedRight.link(xoutRectifRight.input) + + # Create custom meshes from calibration data. Here you could also + # load your own mesh files, or generate them in any other way. + leftMesh, rightMesh = getMesh(calibData) + if generateMesh: + meshLeft = list(leftMesh.tobytes()) + meshRight = list(rightMesh.tobytes()) + # Load mesh data to the StereoDepth node + stereo.loadMeshData(meshLeft, meshRight) + + if meshDirectory is not None: + saveMeshFiles(leftMesh, rightMesh, meshDirectory) + return pipeline + +with dai.Device() as device: + device.startPipeline(create_pipeline(device)) + + # Create a receive queue for each stream + qList = [device.getOutputQueue(stream, 8, blocking=False) for stream in ['right', 'rectifiedRight', 'disparity']] + + while True: + for q in qList: + name = q.getName() + frame = q.get().getCvFrame() + cv2.imshow(name, frame) + if cv2.waitKey(1) == ord("q"): + break diff --git a/examples/StereoDepth/stereo_depth_video.py b/examples/StereoDepth/stereo_depth_video.py index 8ee3a2269..3289c0202 100755 --- a/examples/StereoDepth/stereo_depth_video.py +++ b/examples/StereoDepth/stereo_depth_video.py @@ -85,11 +85,16 @@ ) args = parser.parse_args() -resolutionMap = {"800": (1280, 800), "720": (1280, 720), "400": (640, 400)} -if args.resolution not in resolutionMap: +RES_MAP = { + '800': {'w': 1280, 'h': 800, 'res': dai.MonoCameraProperties.SensorResolution.THE_800_P }, + '720': {'w': 1280, 'h': 720, 'res': dai.MonoCameraProperties.SensorResolution.THE_720_P }, + '400': {'w': 640, 'h': 400, 'res': dai.MonoCameraProperties.SensorResolution.THE_400_P } +} +if args.resolution not in RES_MAP: exit("Unsupported resolution!") -resolution = resolutionMap[args.resolution] +resolution = RES_MAP[args.resolution] + meshDirectory = args.mesh_dir # Output dir for mesh files generateMesh = args.load_mesh # Load mesh files @@ -111,7 +116,7 @@ median = medianMap[args.median] print("StereoDepth config options:") -print(" Resolution: ", resolution) +print(f" Resolution: {resolution['w']}x{resolution['h']}") print(" Left-Right check: ", lrcheck) print(" Extended disparity:", extended) print(" Subpixel: ", subpixel) @@ -188,7 +193,6 @@ def getDisparityFrame(frame, cvColorMap): return disp - device = dai.Device() calibData = device.readCalibration() print("Creating Stereo Depth pipeline") @@ -211,15 +215,8 @@ def getDisparityFrame(frame, cvColorMap): camLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) camRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) -res = ( - dai.MonoCameraProperties.SensorResolution.THE_800_P - if resolution[1] == 800 - else dai.MonoCameraProperties.SensorResolution.THE_720_P - if resolution[1] == 720 - else dai.MonoCameraProperties.SensorResolution.THE_400_P -) for monoCam in (camLeft, camRight): # Common config - monoCam.setResolution(res) + monoCam.setResolution(resolution['res']) # monoCam.setFps(20.0) stereo.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY) @@ -259,15 +256,6 @@ def getDisparityFrame(frame, cvColorMap): if depth: streams.append("depth") -leftMesh, rightMesh = getMesh(calibData) -if generateMesh: - meshLeft = list(leftMesh.tobytes()) - meshRight = list(rightMesh.tobytes()) - stereo.loadMeshData(meshLeft, meshRight) - -if meshDirectory is not None: - saveMeshFiles(leftMesh, rightMesh, meshDirectory) - cvColorMap = cv2.applyColorMap(np.arange(256, dtype=np.uint8), cv2.COLORMAP_JET) cvColorMap[0] = [0, 0, 0] print("Creating DepthAI device") From 9323c7d7ef11b7ef100391951d4d3b383868a4b4 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 9 May 2023 18:01:47 +0200 Subject: [PATCH 301/385] Added 13mp option to cam_test (for OAK-D-Lite) (cherry picked from commit a16a5f5c974770bd005492f207a73f282ce23808) --- utilities/cam_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 18e6c3fc7..e54b676dc 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -64,7 +64,7 @@ def socket_type_pair(arg): "E.g: -cams rgb,m right,c . Default: rgb,c left,m right,m camd,c") parser.add_argument('-mres', '--mono-resolution', type=int, default=800, choices={480, 400, 720, 800}, help="Select mono camera resolution (height). Default: %(default)s") -parser.add_argument('-cres', '--color-resolution', default='1080', choices={'720', '800', '1080', '1200', '4k', '5mp', '12mp', '48mp'}, +parser.add_argument('-cres', '--color-resolution', default='1080', choices={'720', '800', '1080', '1200', '4k', '5mp', '12mp', '13mp', '48mp'}, help="Select color camera resolution / height. Default: %(default)s") parser.add_argument('-rot', '--rotate', const='all', choices={'all', 'rgb', 'mono'}, nargs="?", help="Which cameras to rotate 180 degrees. All if not filtered") @@ -145,6 +145,7 @@ def socket_type_pair(arg): '4k': dai.ColorCameraProperties.SensorResolution.THE_4_K, '5mp': dai.ColorCameraProperties.SensorResolution.THE_5_MP, '12mp': dai.ColorCameraProperties.SensorResolution.THE_12_MP, + '13mp': dai.ColorCameraProperties.SensorResolution.THE_13_MP, '48mp': dai.ColorCameraProperties.SensorResolution.THE_48_MP, } From 8608bdcd805a251b1b0172d9bdc5a194b09beeef Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 9 May 2023 18:11:29 +0200 Subject: [PATCH 302/385] Updated readme --- .../samples/StereoDepth/stereo_depth_custom_mesh.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/source/samples/StereoDepth/stereo_depth_custom_mesh.rst b/docs/source/samples/StereoDepth/stereo_depth_custom_mesh.rst index 6921e451d..ae51f1eb9 100644 --- a/docs/source/samples/StereoDepth/stereo_depth_custom_mesh.rst +++ b/docs/source/samples/StereoDepth/stereo_depth_custom_mesh.rst @@ -2,6 +2,8 @@ Stereo Depth custom Mesh ======================== This example shows how you can load custom mesh to the device and use it for depth calculation. +In this example, mesh files are generated from camera calibration data, but you can also use +your own mesh files. By default, :ref:`StereoDepth` will use the same logic as inside the ``def getMesh()`` to calculate mesh files whenever horizontal FOV is larger than 90°. You could also force calculate the mesh using: @@ -20,6 +22,15 @@ StereoDepth node also allows you to load mesh files directly from a file path: stereo = pipeline.create(dai.node.StereoDepth) stereo.loadMeshFiles('path/to/left_mesh', 'path/to/right_mesh') +Demo +#### + +.. image:: https://github.com/luxonis/depthai-python/assets/18037362/f2031bd4-0748-4a06-abb1-b52e9a17134e + +On the image above you can see that the rectified frame isn't as wide FOV as the original one, +that's because the distortion correction is applied (in this case via custom mesh files), so the +disparity matching can be performed correctly. + Setup ##### From ff104383af059be5fde8cac5be3ab720aaa7a11d Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 10 May 2023 12:29:22 +0200 Subject: [PATCH 303/385] Trying to fix docs building --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index 21301ae81..680df7b44 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -8,4 +8,5 @@ sphinx-autopackagesummary==1.3 autodocsumm==0.2.2 pathlib==1.0.1 jinja2==3.0.3 +urllib3==1.26.15 # New urllib version breaks sphinx -r ./requirements_mkdoc.txt From a1791a02b982acf937d898122817347649dff729 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 10 May 2023 17:05:22 +0200 Subject: [PATCH 304/385] Added 3A FPS limiting to debugging docs (debugging CPU usage) --- docs/source/tutorials/debugging.rst | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/source/tutorials/debugging.rst b/docs/source/tutorials/debugging.rst index 378780a8f..2417a79cd 100644 --- a/docs/source/tutorials/debugging.rst +++ b/docs/source/tutorials/debugging.rst @@ -121,4 +121,43 @@ specifically SHAVE core and CMX memory usage: In total, this pipeline consumes 15 SHAVE cores and 16 CMX slices. The pipeline is running an object detection model compiled for 6 SHAVE cores. +CPU usage +========= + +When setting the :ref:`DepthAI debugging level` to debug (or lower), depthai will also print our CPU usage for LeonOS and LeonRT. CPU usage +at 100% (or close to it) can cause many undesirable effects, such as higher frame latency, lower FPS, and in some cases even firmware crash. + +Compared to OAK USB cameras, OAK PoE cameras will have increased CPU consumption, as the networking stack is running on the LeonOS core. Besides +reducing pipeline (doing less processing), a good alternative is to reduce 3A FPS (ISP). This means that 3A algorithms (auto exposure, auto white balance +and auto focus) won't be run every frame, but every N frames. When updating DepthAI SDK's `camera_preview.py `__ +example (code change below), the LeonOS CPU usage decreased from 100% to ~46%: + +.. code-block:: bash + + # Without 3A FPS limit on OAK PoE camera: + Cpu Usage - LeonOS 99.99%, LeonRT: 6.91% + + # Limiting 3A to 15 FPS on OAK PoE camera: + Cpu Usage - LeonOS 46.24%, LeonRT: 3.90% + +Not having 100% CPU usage also drastically decreased frame latency, in the example for the script below it went from ~710 ms to ~110ms: + +.. image:: + +.. code-block:: diff + + from depthai_sdk import OakCamera + + with OakCamera() as oak: + color = oak.create_camera('color') + left = oak.create_camera('left') + right = oak.create_camera('right') + + + # Limiting 3A to 15 FPS + + for node in [color.node, left.node, right.node]: + + node.setIsp3aFps(15) + + oak.visualize([color, left, right], fps=True, scale=2/3) + oak.start(blocking=True) + .. include:: /includes/footer-short.rst \ No newline at end of file From ae74102c9a3a24024281e882532105134305e9c0 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 10 May 2023 17:11:12 +0200 Subject: [PATCH 305/385] Added image to CPU usage docs --- docs/source/tutorials/debugging.rst | 4 +++- docs/source/tutorials/low-latency.rst | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/source/tutorials/debugging.rst b/docs/source/tutorials/debugging.rst index 2417a79cd..5ca71019b 100644 --- a/docs/source/tutorials/debugging.rst +++ b/docs/source/tutorials/debugging.rst @@ -142,7 +142,7 @@ example (code change below), the LeonOS CPU usage decreased from 100% to ~46%: Not having 100% CPU usage also drastically decreased frame latency, in the example for the script below it went from ~710 ms to ~110ms: -.. image:: +.. image:: https://github.com/luxonis/depthai-python/assets/18037362/84ec8de8-58ce-49c7-b882-048141d284e0 .. code-block:: diff @@ -160,4 +160,6 @@ Not having 100% CPU usage also drastically decreased frame latency, in the examp oak.visualize([color, left, right], fps=True, scale=2/3) oak.start(blocking=True) +Limiting 3A FPS can be achieved by calling :code:`setIsp3aFps()` function on the camera node (either :ref:`ColorCamera` or :ref:`MonoCamera`). + .. include:: /includes/footer-short.rst \ No newline at end of file diff --git a/docs/source/tutorials/low-latency.rst b/docs/source/tutorials/low-latency.rst index 5e88a2b4a..4d27dec4a 100644 --- a/docs/source/tutorials/low-latency.rst +++ b/docs/source/tutorials/low-latency.rst @@ -125,7 +125,7 @@ On PoE, the latency can vary quite a bit due to a number of factors: * Network/computer is saturated with other traffic. You can test the actual bandwidth with `OAK bandwidth test `__ script. With direct link I got ~800mbps downlink and ~210mbps uplink. * Computer's **Network Interface Card settings**, `documentation here `__ -* 100% OAK Leon CSS (CPU) usage. The Leon CSS core handles the POE communication (`see docs here `__), and if the CPU is 100% used, it will not be able to handle the communication as fast as it should. +* 100% OAK Leon CSS (CPU) usage. The Leon CSS core handles the POE communication (`see docs here `__), and if the CPU is 100% used, it will not be able to handle the communication as fast as it should. **Workaround:** See :ref:`CPU usage` docs. * Another potential way to improve PoE latency would be to fine-tune network settings, like MTU, TCP window size, etc. (see `here `__ for more info) Bandwidth From 64ca9c4c13a16d7b2a4e98a427e0d5b6cf31bf9b Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 10 May 2023 17:23:32 +0200 Subject: [PATCH 306/385] Added crash report docs --- .../samples/crash_report/crash_report.rst | 50 +++++++++++++++++++ docs/source/tutorials/code_samples.rst | 5 ++ 2 files changed, 55 insertions(+) create mode 100644 docs/source/samples/crash_report/crash_report.rst diff --git a/docs/source/samples/crash_report/crash_report.rst b/docs/source/samples/crash_report/crash_report.rst new file mode 100644 index 000000000..b6bb4fa00 --- /dev/null +++ b/docs/source/samples/crash_report/crash_report.rst @@ -0,0 +1,50 @@ +Crash report +============ + +In case of a firmware crash, OAK cameras will automatically generate a crash report and store it in the device. +The crash report contains information about the crash, such as the stack trace, the device's configuration, +and the device's state at the time of the crash. The crash report can be read from the device and sent to Luxonis for debugging purposes. + + +Demo +#### + +In case a crash report was found on the device, this example will read it and save it to a json file: + +.. code-block:: bash + + > python crash_report.py + Crash dump found on your device! + Saved to crashDump_0_184430102163DB0F00_3575b77f20e796b4e79953bf3d2ba22f0416ee8b.json + Please report to developers! + +Please **send the crash reports** together `with an MRE `__ (DepthAI issue) +to our `Discuss Forum `__. Thank you! + +Setup +##### + +.. include:: /includes/install_from_pypi.rst + +Source code +########### + +.. tabs:: + + .. tab:: Python + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../examples/CrashReport/crash_report.py + :language: python + :linenos: + + .. tab:: C++ + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../depthai-core/examples/CrashReport/crash_report.cpp + :language: cpp + :linenos: + +.. include:: /includes/footer-short.rst diff --git a/docs/source/tutorials/code_samples.rst b/docs/source/tutorials/code_samples.rst index b8d6fe618..21f8f691f 100644 --- a/docs/source/tutorials/code_samples.rst +++ b/docs/source/tutorials/code_samples.rst @@ -8,6 +8,7 @@ Code Samples ../samples/bootloader/* ../samples/calibration/* ../samples/ColorCamera/* + ../samples/crash_report/* ../samples/EdgeDetector/* ../samples/FeatureTracker/* ../samples/host_side/* @@ -51,6 +52,10 @@ are presented with code. - :ref:`RGB scene` - Shows how to select ColorCamera's scene and effect - :ref:`RGB video` - Displays high resolution frames of the RGB camera +.. rubtic:: Crash report + +- :ref:`Crash report` - In case of a firmware crash, example reads it from the device and saves it to a json file + .. rubric:: EdgeDetector - :ref:`Edge Detector` - Performs edge detection on all camera streams From 33907177d3c773e4e84d29f738a9e2c34ba11aa7 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Wed, 10 May 2023 18:03:16 +0200 Subject: [PATCH 307/385] CrashDump: add optional clear of crash dump, enabled by default --- depthai-core | 2 +- src/DeviceBindings.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depthai-core b/depthai-core index 1d990f993..bdbbf828d 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 1d990f993764204220ca799c04f78fa0cad9b570 +Subproject commit bdbbf828da5c392c8e028a83715257b30c91b18f diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 868322e43..443034438 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -593,7 +593,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def("getLogLevel", [](DeviceBase& d) { py::gil_scoped_release release; return d.getLogLevel(); }, DOC(dai, DeviceBase, getLogLevel)) .def("setSystemInformationLoggingRate", [](DeviceBase& d, float hz) { py::gil_scoped_release release; d.setSystemInformationLoggingRate(hz); }, py::arg("rateHz"), DOC(dai, DeviceBase, setSystemInformationLoggingRate)) .def("getSystemInformationLoggingRate", [](DeviceBase& d) { py::gil_scoped_release release; return d.getSystemInformationLoggingRate(); }, DOC(dai, DeviceBase, getSystemInformationLoggingRate)) - .def("getCrashDump", [](DeviceBase& d) { py::gil_scoped_release release; return d.getCrashDump(); }, DOC(dai, DeviceBase, getCrashDump)) + .def("getCrashDump", [](DeviceBase& d, bool clearCrashDump) { py::gil_scoped_release release; return d.getCrashDump(clearCrashDump); }, py::arg("clearCrashDump") = true, DOC(dai, DeviceBase, getCrashDump)) .def("hasCrashDump", [](DeviceBase& d) { py::gil_scoped_release release; return d.hasCrashDump(); }, DOC(dai, DeviceBase, hasCrashDump)) .def("getConnectedCameras", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedCameras(); }, DOC(dai, DeviceBase, getConnectedCameras)) .def("getConnectedCameraFeatures", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedCameraFeatures(); }, DOC(dai, DeviceBase, getConnectedCameraFeatures)) From 890076a797cd8e417ebbce48861ea7051992708d Mon Sep 17 00:00:00 2001 From: Erol444 Date: Wed, 10 May 2023 19:33:12 +0200 Subject: [PATCH 308/385] fix typo --- docs/source/tutorials/code_samples.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/tutorials/code_samples.rst b/docs/source/tutorials/code_samples.rst index 21f8f691f..66fc52ba4 100644 --- a/docs/source/tutorials/code_samples.rst +++ b/docs/source/tutorials/code_samples.rst @@ -52,7 +52,7 @@ are presented with code. - :ref:`RGB scene` - Shows how to select ColorCamera's scene and effect - :ref:`RGB video` - Displays high resolution frames of the RGB camera -.. rubtic:: Crash report +.. rubric:: Crash report - :ref:`Crash report` - In case of a firmware crash, example reads it from the device and saves it to a json file From 89cbed7224770b5d2bc55f4ae20dc99a6016bd1d Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 12 May 2023 15:51:53 +0200 Subject: [PATCH 309/385] RGB-depth alignment doesn't work when using disparity shift --- docs/source/components/nodes/stereo_depth.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/components/nodes/stereo_depth.rst b/docs/source/components/nodes/stereo_depth.rst index 80ed6d362..87d2d3041 100644 --- a/docs/source/components/nodes/stereo_depth.rst +++ b/docs/source/components/nodes/stereo_depth.rst @@ -171,6 +171,7 @@ Limitations - Median filtering is disabled when subpixel mode is set to 4 or 5 bits. - For RGB-depth alignment the RGB camera has to be placed on the same horizontal line as the stereo camera pair. +- RGB-depth alignment doesn't work when using disparity shift. Stereo depth FPS ================ From c4b58d66d2d8d325c416594948ac55b09590f849 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Wed, 11 Jan 2023 05:26:04 +0200 Subject: [PATCH 310/385] cam_test: add `-raw` for streaming/saving raw frames (+ISP YUV) --- utilities/cam_test.py | 76 +++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 491ae52cf..3ae24ae22 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -36,6 +36,7 @@ #os.environ["DEPTHAI_LEVEL"] = "debug" import cv2 +import numpy as np import argparse import collections import time @@ -80,6 +81,8 @@ def socket_type_pair(arg): help="Path to custom camera tuning database") parser.add_argument('-d', '--device', default="", type=str, help="Optional MX ID of the device to connect to.") +parser.add_argument('-raw', '--enable-raw', default=False, action="store_true", + help='Enable the RAW camera streams') parser.add_argument('-ctimeout', '--connection-timeout', default=30000, help="Connection timeout in ms. Default: %(default)s (sets DEPTHAI_CONNECTION_TIMEOUT environment variable)") @@ -178,9 +181,12 @@ def get(self): cam = {} xout = {} +xout_raw = {} +streams = [] for c in cam_list: xout[c] = pipeline.createXLinkOut() xout[c].setStreamName(c) + streams.append(c) if cam_type_color[c]: cam[c] = pipeline.createColorCamera() cam[c].setResolution(color_res_opts[args.color_resolution]) @@ -206,6 +212,15 @@ def get(self): cam[c].setFps(args.fps) cam[c].setIsp3aFps(args.isp3afps) + if args.enable_raw: + raw_name = 'raw_' + c + xout_raw[c] = pipeline.create(dai.node.XLinkOut) + xout_raw[c].setStreamName(raw_name) + if args.enable_raw: + streams.append(raw_name) + cam[c].raw.link(xout_raw[c].input) + # to be added cam[c].setRawOutputPacked(False) + if args.camera_tuning: pipeline.setCameraTuningBlobPath(str(args.camera_tuning)) @@ -232,6 +247,8 @@ def exit_cleanly(signum, frame): print('auto ' if p.hasAutofocus else 'fixed', '- ', end='') print(*[type.name for type in p.supportedTypes]) cam_name[p.socket.name] = p.sensorName + if args.enable_raw: + cam_name['raw_'+p.socket.name] = p.sensorName print('USB speed:', device.getUsbSpeed().name) @@ -240,7 +257,7 @@ def exit_cleanly(signum, frame): q = {} fps_host = {} # FPS computed based on the time we receive frames in app fps_capt = {} # FPS computed based on capture timestamps from device - for c in cam_list: + for c in streams: q[c] = device.getOutputQueue(name=c, maxSize=4, blocking=False) # The OpenCV window resize may produce some artifacts if args.resizable_windows: @@ -299,7 +316,7 @@ def exit_cleanly(signum, frame): capture_list = [] while True: - for c in cam_list: + for c in streams: try: pkt = q[c].tryGet() except Exception as e: @@ -308,25 +325,44 @@ def exit_cleanly(signum, frame): if pkt is not None: fps_host[c].update() fps_capt[c].update(pkt.getTimestamp().total_seconds()) + width, height = pkt.getWidth(), pkt.getHeight() frame = pkt.getCvFrame() - if c in capture_list: - width, height = pkt.getWidth(), pkt.getHeight() - capture_file_name = ('capture_' + c + '_' + cam_name[cam_socket_opts[c].name] - + '_' + str(width) + 'x' + str(height) - + '_exp_' + - str(int( - pkt.getExposureTime().total_seconds()*1e6)) - + '_iso_' + str(pkt.getSensitivity()) - + '_lens_' + - str(pkt.getLensPosition()) - + '_' + capture_time - + '_' + str(pkt.getSequenceNum()) - + ".png" - ) - print("\nSaving:", capture_file_name) - cv2.imwrite(capture_file_name, frame) + capture = c in capture_list + if capture: + capture_file_info = ('capture_' + c + '_' + cam_name[c] + + '_' + str(width) + 'x' + str(height) + + '_exp_' + str(int(pkt.getExposureTime().total_seconds()*1e6)) + + '_iso_' + str(pkt.getSensitivity()) + + '_lens_' + str(pkt.getLensPosition()) + + '_' + capture_time + + '_' + str(pkt.getSequenceNum()) + ) capture_list.remove(c) - + print() + if c.startswith('raw_'): + if capture: + filename = capture_file_info + '_10bit.bw' + print('Saving:', filename) + frame.tofile(filename) + # Full range for display, use bits [15:6] of the 16-bit pixels + frame = frame * (1 << 6) + # Debayer color for preview/png + if cam_type_color[c.split('_')[-1]]: + # See this for the ordering, at the end of page: + # https://docs.opencv.org/4.5.1/de/d25/imgproc_color_conversions.html + # TODO add bayer order to ImgFrame getType() + frame = cv2.cvtColor(frame, cv2.COLOR_BayerGB2BGR) + else: + # Save YUV too, but only when RAW is also enabled (for tuning purposes) + if capture and args.enable_raw: + payload = pkt.getData() + filename = capture_file_info + '_P420.yuv' + print('Saving:', filename) + payload.tofile(filename) + if capture: + filename = capture_file_info + '.png' + print('Saving:', filename) + cv2.imwrite(filename, frame) cv2.imshow(c, frame) print("\rFPS:", *["{:6.2f}|{:6.2f}".format(fps_host[c].get(), @@ -337,7 +373,7 @@ def exit_cleanly(signum, frame): if key == ord('q'): break elif key == ord('c'): - capture_list = cam_list.copy() + capture_list = streams.copy() capture_time = time.strftime('%Y%m%d_%H%M%S') elif key == ord('t'): print("Autofocus trigger (and disable continuous)") From ae60f11d3b5c2ded5e2c7350af36fc4e564a321b Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Sun, 15 Jan 2023 14:43:58 +0200 Subject: [PATCH 311/385] cam_test.py: `/` to toggle printing camera settings: exp/ISO/lens/colortemp --- utilities/cam_test.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 3ae24ae22..ef0fc2cac 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -30,6 +30,8 @@ For the 'Select control: ...' options, use these keys to modify the value: '-' or '_' to decrease '+' or '=' to increase + +'/' to toggle printing camera settings: exposure, ISO, lens position, color temperature """ import os @@ -310,6 +312,7 @@ def exit_cleanly(signum, frame): luma_denoise = 0 chroma_denoise = 0 control = 'none' + show = False print("Cam:", *[' ' + c.ljust(8) for c in cam_list], "[host | capture timestamp]") @@ -327,6 +330,13 @@ def exit_cleanly(signum, frame): fps_capt[c].update(pkt.getTimestamp().total_seconds()) width, height = pkt.getWidth(), pkt.getHeight() frame = pkt.getCvFrame() + if show: + txt = f"[{c:5}, {pkt.getSequenceNum():4}] " + txt += f"Exp: {pkt.getExposureTime().total_seconds()*1000:6.3f} ms, " + txt += f"ISO: {pkt.getSensitivity():4}, " + txt += f"Lens pos: {pkt.getLensPosition():3}, " + txt += f"Color temp: {pkt.getColorTemperature()} K" + print(txt) capture = c in capture_list if capture: capture_file_info = ('capture_' + c + '_' + cam_name[c] @@ -365,13 +375,17 @@ def exit_cleanly(signum, frame): cv2.imwrite(filename, frame) cv2.imshow(c, frame) print("\rFPS:", - *["{:6.2f}|{:6.2f}".format(fps_host[c].get(), - fps_capt[c].get()) for c in cam_list], - end='', flush=True) + *["{:6.2f}|{:6.2f}".format(fps_host[c].get(), fps_capt[c].get()) for c in cam_list], + end=' ', flush=True) + if show: print() key = cv2.waitKey(1) if key == ord('q'): break + elif key == ord('/'): + show = not show + # Print empty string as FPS status new-line separator + print("" if show else "Printing camera settings: OFF") elif key == ord('c'): capture_list = streams.copy() capture_time = time.strftime('%Y%m%d_%H%M%S') From 5493fb643524460c55a7f149b8f8758492644e61 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Fri, 17 Mar 2023 05:36:41 +0200 Subject: [PATCH 312/385] Color/Mono/Camera: add `setRawOutputPacked` config -- `core` updated in upcoming commit --- src/pipeline/node/CameraBindings.cpp | 1 + src/pipeline/node/ColorCameraBindings.cpp | 2 ++ src/pipeline/node/MonoCameraBindings.cpp | 2 ++ 3 files changed, 5 insertions(+) diff --git a/src/pipeline/node/CameraBindings.cpp b/src/pipeline/node/CameraBindings.cpp index d99d0eab0..2c52bb282 100644 --- a/src/pipeline/node/CameraBindings.cpp +++ b/src/pipeline/node/CameraBindings.cpp @@ -159,6 +159,7 @@ void bind_camera(pybind11::module& m, void* pCallstack){ .def("setCalibrationAlpha", &Camera::setCalibrationAlpha, py::arg("alpha"), DOC(dai, node, Camera, setCalibrationAlpha)) .def("getCalibrationAlpha", &Camera::getCalibrationAlpha, DOC(dai, node, Camera, getCalibrationAlpha)) + .def("setRawOutputPacked", &Camera::setRawOutputPacked, py::arg("packed"), DOC(dai, node, Camera, setRawOutputPacked)) ; // ALIAS daiNodeModule.attr("Camera").attr("Properties") = cameraProperties; diff --git a/src/pipeline/node/ColorCameraBindings.cpp b/src/pipeline/node/ColorCameraBindings.cpp index 0816ffefd..fb345d221 100644 --- a/src/pipeline/node/ColorCameraBindings.cpp +++ b/src/pipeline/node/ColorCameraBindings.cpp @@ -188,6 +188,8 @@ void bind_colorcamera(pybind11::module& m, void* pCallstack){ .def("getIspNumFramesPool", &ColorCamera::getIspNumFramesPool, DOC(dai, node, ColorCamera, getIspNumFramesPool)) .def("setCamera", &ColorCamera::setCamera, py::arg("name"), DOC(dai, node, ColorCamera, setCamera)) .def("getCamera", &ColorCamera::getCamera, DOC(dai, node, ColorCamera, getCamera)) + + .def("setRawOutputPacked", &ColorCamera::setRawOutputPacked, py::arg("packed"), DOC(dai, node, ColorCamera, setRawOutputPacked)) ; // ALIAS daiNodeModule.attr("ColorCamera").attr("Properties") = colorCameraProperties; diff --git a/src/pipeline/node/MonoCameraBindings.cpp b/src/pipeline/node/MonoCameraBindings.cpp index 41c3b981f..51365fda0 100644 --- a/src/pipeline/node/MonoCameraBindings.cpp +++ b/src/pipeline/node/MonoCameraBindings.cpp @@ -92,6 +92,8 @@ void bind_monocamera(pybind11::module& m, void* pCallstack){ .def("getRawNumFramesPool", &MonoCamera::getRawNumFramesPool, DOC(dai, node, MonoCamera, getRawNumFramesPool)) .def("setCamera", &MonoCamera::setCamera, py::arg("name"), DOC(dai, node, MonoCamera, setCamera)) .def("getCamera", &MonoCamera::getCamera, DOC(dai, node, MonoCamera, getCamera)) + + .def("setRawOutputPacked", &MonoCamera::setRawOutputPacked, py::arg("packed"), DOC(dai, node, MonoCamera, setRawOutputPacked)) ; // ALIAS daiNodeModule.attr("MonoCamera").attr("Properties") = monoCameraProperties; From c3ddc3995a9753c1c96bea60ce87198ef82a441f Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Fri, 17 Mar 2023 05:41:11 +0200 Subject: [PATCH 313/385] cam_test.py: `-raw` streaming unpacked --- utilities/cam_test.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index ef0fc2cac..5e9ceb5eb 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -212,7 +212,8 @@ def get(self): if rotate[c]: cam[c].setImageOrientation(dai.CameraImageOrientation.ROTATE_180_DEG) cam[c].setFps(args.fps) - cam[c].setIsp3aFps(args.isp3afps) + if args.isp3afps: + cam[c].setIsp3aFps(args.isp3afps) if args.enable_raw: raw_name = 'raw_' + c @@ -221,7 +222,7 @@ def get(self): if args.enable_raw: streams.append(raw_name) cam[c].raw.link(xout_raw[c].input) - # to be added cam[c].setRawOutputPacked(False) + cam[c].setRawOutputPacked(False) if args.camera_tuning: pipeline.setCameraTuningBlobPath(str(args.camera_tuning)) @@ -355,7 +356,11 @@ def exit_cleanly(signum, frame): print('Saving:', filename) frame.tofile(filename) # Full range for display, use bits [15:6] of the 16-bit pixels - frame = frame * (1 << 6) + type = pkt.getType() + multiplier = 1 + if type == dai.ImgFrame.Type.RAW10: multiplier = (1 << (16-10)) + if type == dai.ImgFrame.Type.RAW12: multiplier = (1 << (16-4)) + frame = frame * multiplier # Debayer color for preview/png if cam_type_color[c.split('_')[-1]]: # See this for the ordering, at the end of page: From bddb42e52e4ea54c00bec73acaf448ca7d236978 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Sun, 9 Apr 2023 23:05:54 +0300 Subject: [PATCH 314/385] cam_test.py: some fixes for ToF: `-cams rgb,t` and other options --- utilities/cam_test.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 5e9ceb5eb..9730e0d54 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -182,14 +182,24 @@ def get(self): control.setStreamName('control') cam = {} +tof = {} xout = {} xout_raw = {} streams = [] for c in cam_list: + tofEnableRaw = False xout[c] = pipeline.createXLinkOut() xout[c].setStreamName(c) streams.append(c) - if cam_type_color[c]: + if cam_type_tof[c]: + cam[c] = pipeline.create(dai.node.ColorCamera) # .Camera + if args.tof_raw: + tofEnableRaw = True + else: + tof[c] = pipeline.create(dai.node.ToF) + cam[c].raw.link(tof[c].input) + tof[c].depth.link(xout[c].input) + elif cam_type_color[c]: cam[c] = pipeline.createColorCamera() cam[c].setResolution(color_res_opts[args.color_resolution]) cam[c].setIspScale(1, args.isp_downscale) @@ -215,12 +225,11 @@ def get(self): if args.isp3afps: cam[c].setIsp3aFps(args.isp3afps) - if args.enable_raw: + if args.enable_raw or tofEnableRaw: raw_name = 'raw_' + c xout_raw[c] = pipeline.create(dai.node.XLinkOut) xout_raw[c].setStreamName(raw_name) - if args.enable_raw: - streams.append(raw_name) + streams.append(raw_name) cam[c].raw.link(xout_raw[c].input) cam[c].setRawOutputPacked(False) @@ -331,6 +340,14 @@ def exit_cleanly(signum, frame): fps_capt[c].update(pkt.getTimestamp().total_seconds()) width, height = pkt.getWidth(), pkt.getHeight() frame = pkt.getCvFrame() + if cam_type_tof[c.split('_')[-1]] and not c.startswith('raw_'): + if args.tof_cm: + # pixels represent `cm`, capped to 255. Value can be checked hovering the mouse + frame = (frame // 10).clip(0, 255).astype(np.uint8) + else: + frame = (frame.view(np.int16).astype(float)) + frame = cv2.normalize(frame, frame, alpha=255, beta=0, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U) + frame = cv2.applyColorMap(frame, jet_custom) if show: txt = f"[{c:5}, {pkt.getSequenceNum():4}] " txt += f"Exp: {pkt.getExposureTime().total_seconds()*1000:6.3f} ms, " @@ -361,7 +378,7 @@ def exit_cleanly(signum, frame): if type == dai.ImgFrame.Type.RAW10: multiplier = (1 << (16-10)) if type == dai.ImgFrame.Type.RAW12: multiplier = (1 << (16-4)) frame = frame * multiplier - # Debayer color for preview/png + # Debayer as color for preview/png if cam_type_color[c.split('_')[-1]]: # See this for the ordering, at the end of page: # https://docs.opencv.org/4.5.1/de/d25/imgproc_color_conversions.html From 32831ae99bc05cadd8bb07717a1f490a20a94119 Mon Sep 17 00:00:00 2001 From: Florin Buica Date: Fri, 21 Apr 2023 14:51:10 +0300 Subject: [PATCH 315/385] adding bindings for ToF --- CMakeLists.txt | 3 + depthai-core | 2 +- src/pipeline/datatype/ToFConfigBindings.cpp | 71 +++++++++++++++++++++ src/pipeline/node/ToFBindings.cpp | 47 ++++++++++++++ utilities/cam_test.py | 12 +++- 5 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 src/pipeline/datatype/ToFConfigBindings.cpp create mode 100644 src/pipeline/node/ToFBindings.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2183df54f..35c956c01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,9 +124,11 @@ pybind11_add_module(${TARGET_NAME} src/pipeline/node/IMUBindings.cpp src/pipeline/node/EdgeDetectorBindings.cpp src/pipeline/node/FeatureTrackerBindings.cpp + src/pipeline/node/ToFBindings.cpp src/pipeline/node/AprilTagBindings.cpp src/pipeline/node/DetectionParserBindings.cpp src/pipeline/node/WarpBindings.cpp + src/pipeline/node/ToFBindings.cpp src/pipeline/datatype/ADatatypeBindings.cpp src/pipeline/datatype/AprilTagConfigBindings.cpp @@ -135,6 +137,7 @@ pybind11_add_module(${TARGET_NAME} src/pipeline/datatype/CameraControlBindings.cpp src/pipeline/datatype/EdgeDetectorConfigBindings.cpp src/pipeline/datatype/FeatureTrackerConfigBindings.cpp + src/pipeline/datatype/ToFConfigBindings.cpp src/pipeline/datatype/ImageManipConfigBindings.cpp src/pipeline/datatype/ImgDetectionsBindings.cpp src/pipeline/datatype/ImgFrameBindings.cpp diff --git a/depthai-core b/depthai-core index bdbbf828d..e58b85be2 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit bdbbf828da5c392c8e028a83715257b30c91b18f +Subproject commit e58b85be2b5acc6fffee4cd4862fcca070467ef8 diff --git a/src/pipeline/datatype/ToFConfigBindings.cpp b/src/pipeline/datatype/ToFConfigBindings.cpp new file mode 100644 index 000000000..8c4c8dab3 --- /dev/null +++ b/src/pipeline/datatype/ToFConfigBindings.cpp @@ -0,0 +1,71 @@ +#include "DatatypeBindings.hpp" +#include "pipeline/CommonBindings.hpp" +#include +#include + +// depthai +#include "depthai/pipeline/datatype/ToFConfig.hpp" + +//pybind +#include +#include + +// #include "spdlog/spdlog.h" + +void bind_tofconfig(pybind11::module& m, void* pCallstack){ + + using namespace dai; + + py::class_> rawToFConfig(m, "RawToFConfig", DOC(dai, RawToFConfig)); + py::class_ depthParams(rawToFConfig, "DepthParams", DOC(dai, RawToFConfig, DepthParams)); + py::enum_ depthParamsTypeFMod(depthParams, "TypeFMod", DOC(dai, RawToFConfig, DepthParams, TypeFMod)); + py::class_> toFConfig(m, "ToFConfig", DOC(dai, ToFConfig)); + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + // Call the rest of the type defines, then perform the actual bindings + Callstack* callstack = (Callstack*) pCallstack; + auto cb = callstack->top(); + callstack->pop(); + cb(m, pCallstack); + // Actual bindings + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + // Metadata / raw + rawToFConfig + .def(py::init<>()) + .def_readwrite("depthParams", &RawToFConfig::depthParams, DOC(dai, RawToFConfig, depthParams)) + ; + + depthParamsTypeFMod + .value("ALL", RawToFConfig::DepthParams::TypeFMod::F_MOD_ALL) + .value("MIN", RawToFConfig::DepthParams::TypeFMod::F_MOD_MIN) + .value("MAX", RawToFConfig::DepthParams::TypeFMod::F_MOD_MAX) + ; + + depthParams + .def(py::init<>()) + .def_readwrite("enable", &RawToFConfig::DepthParams::enable, DOC(dai, RawToFConfig, DepthParams, enable)) + .def_readwrite("freqModUsed", &RawToFConfig::DepthParams::freqModUsed, DOC(dai, RawToFConfig, DepthParams, freqModUsed)) + .def_readwrite("avgPhaseShuffle", &RawToFConfig::DepthParams::avgPhaseShuffle, DOC(dai, RawToFConfig, DepthParams, avgPhaseShuffle)) + .def_readwrite("minimumAmplitude", &RawToFConfig::DepthParams::minimumAmplitude, DOC(dai, RawToFConfig, DepthParams, minimumAmplitude)) + ; + + // Message + toFConfig + .def(py::init<>()) + .def(py::init>()) + + .def("setDepthParams", static_cast(&ToFConfig::setDepthParams), py::arg("config"), DOC(dai, ToFConfig, setDepthParams, 2)) + + .def("set", &ToFConfig::set, py::arg("config"), DOC(dai, ToFConfig, set)) + .def("get", &ToFConfig::get, DOC(dai, ToFConfig, get)) + ; + + // add aliases + m.attr("ToFConfig").attr("DepthParams") = m.attr("RawToFConfig").attr("DepthParams"); + +} diff --git a/src/pipeline/node/ToFBindings.cpp b/src/pipeline/node/ToFBindings.cpp new file mode 100644 index 000000000..9bde78539 --- /dev/null +++ b/src/pipeline/node/ToFBindings.cpp @@ -0,0 +1,47 @@ +#include "NodeBindings.hpp" +#include "Common.hpp" + +#include "depthai/pipeline/Pipeline.hpp" +#include "depthai/pipeline/Node.hpp" +#include "depthai/pipeline/node/ToF.hpp" + +void bind_tof(pybind11::module& m, void* pCallstack){ + + using namespace dai; + using namespace dai::node; + + // Node and Properties declare upfront + py::class_ tofProperties(m, "ToFProperties", DOC(dai, ToFProperties)); + auto tof = ADD_NODE(ToF); + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + // Call the rest of the type defines, then perform the actual bindings + Callstack* callstack = (Callstack*) pCallstack; + auto cb = callstack->top(); + callstack->pop(); + cb(m, pCallstack); + // Actual bindings + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + // Properties + tofProperties + .def_readwrite("initialConfig", &ToFProperties::initialConfig) + ; + + // Node + tof + .def_readonly("input", &ToF::input, DOC(dai, node, ToF, input), DOC(dai, node, ToF, input)) + .def_readonly("inputConfig", &ToF::inputConfig, DOC(dai, node, ToF, inputConfig), DOC(dai, node, ToF, inputConfig)) + .def_readonly("depth", &ToF::depth, DOC(dai, node, ToF, depth), DOC(dai, node, ToF, depth)) + .def_readonly("amplitude", &ToF::amplitude, DOC(dai, node, ToF, amplitude), DOC(dai, node, ToF, amplitude)) + .def_readonly("error", &ToF::error, DOC(dai, node, ToF, error), DOC(dai, node, ToF, error)) + .def_readonly("initialConfig", &ToF::initialConfig, DOC(dai, node, ToF, initialConfig), DOC(dai, node, ToF, initialConfig)) + ; + // ALIAS + daiNodeModule.attr("ToF").attr("Properties") = tofProperties; + +} diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 9730e0d54..ddca1828a 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -45,7 +45,7 @@ from itertools import cycle from pathlib import Path import sys -import cam_test_gui +#import cam_test_gui import signal @@ -181,6 +181,9 @@ def get(self): control = pipeline.createXLinkIn() control.setStreamName('control') +xinTofConfig = pipeline.createXLinkIn() +xinTofConfig.setStreamName('tofConfig') + cam = {} tof = {} xout = {} @@ -199,6 +202,11 @@ def get(self): tof[c] = pipeline.create(dai.node.ToF) cam[c].raw.link(tof[c].input) tof[c].depth.link(xout[c].input) + xinTofConfig.out.link(tof[c].inputConfig) + tofConfig = tof[c].initialConfig.get() + tofConfig.setFreqModUsed(dai.ToFConfig.DepthParams.TypeFMod.F_MOD_MIN) + tofConfig.SetAvgPhaseShuffle(True) + tofConfig.setMinAmplitude(20.0) elif cam_type_color[c]: cam[c] = pipeline.createColorCamera() cam[c].setResolution(color_res_opts[args.color_resolution]) @@ -230,7 +238,7 @@ def get(self): xout_raw[c] = pipeline.create(dai.node.XLinkOut) xout_raw[c].setStreamName(raw_name) streams.append(raw_name) - cam[c].raw.link(xout_raw[c].input) + tof[c].amplitude.link(xout_raw[c].input) cam[c].setRawOutputPacked(False) if args.camera_tuning: From 8c2b0cd6ca984c3e4065d5ec4be07105bdda8772 Mon Sep 17 00:00:00 2001 From: Florin Buica Date: Tue, 25 Apr 2023 10:07:54 +0300 Subject: [PATCH 316/385] preparing to merge to tof vga --- src/DatatypeBindings.cpp | 2 ++ src/pipeline/datatype/ToFConfigBindings.cpp | 5 ++++- src/pipeline/node/NodeBindings.cpp | 2 ++ src/pipeline/node/ToFBindings.cpp | 4 ++-- utilities/cam_test.py | 24 +++++++++++++++++---- 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/DatatypeBindings.cpp b/src/DatatypeBindings.cpp index deae33696..5ff683f3d 100644 --- a/src/DatatypeBindings.cpp +++ b/src/DatatypeBindings.cpp @@ -21,6 +21,7 @@ void bind_spatiallocationcalculatordata(pybind11::module& m, void* pCallstack); void bind_stereodepthconfig(pybind11::module& m, void* pCallstack); void bind_systeminformation(pybind11::module& m, void* pCallstack); void bind_trackedfeatures(pybind11::module& m, void* pCallstack); +void bind_tofconfig(pybind11::module& m, void* pCallstack); void bind_tracklets(pybind11::module& m, void* pCallstack); void DatatypeBindings::addToCallstack(std::deque& callstack) { @@ -46,6 +47,7 @@ void DatatypeBindings::addToCallstack(std::deque& callstack) { callstack.push_front(bind_stereodepthconfig); callstack.push_front(bind_systeminformation); callstack.push_front(bind_trackedfeatures); + callstack.push_front(bind_tofconfig); callstack.push_front(bind_tracklets); } diff --git a/src/pipeline/datatype/ToFConfigBindings.cpp b/src/pipeline/datatype/ToFConfigBindings.cpp index 8c4c8dab3..34716a611 100644 --- a/src/pipeline/datatype/ToFConfigBindings.cpp +++ b/src/pipeline/datatype/ToFConfigBindings.cpp @@ -58,8 +58,11 @@ void bind_tofconfig(pybind11::module& m, void* pCallstack){ toFConfig .def(py::init<>()) .def(py::init>()) - + .def("setDepthParams", static_cast(&ToFConfig::setDepthParams), py::arg("config"), DOC(dai, ToFConfig, setDepthParams, 2)) + .def("setFreqModUsed", static_cast(&ToFConfig::setFreqModUsed), DOC(dai, ToFConfig, setDepthParams, 2)) + .def("setAvgPhaseShuffle", &ToFConfig::setAvgPhaseShuffle, DOC(dai, node, ToFConfig, setAvgPhaseShuffle)) + .def("setMinAmplitude", &ToFConfig::setMinAmplitude, DOC(dai, node, ToFConfig, setMinAmplitude)) .def("set", &ToFConfig::set, py::arg("config"), DOC(dai, ToFConfig, set)) .def("get", &ToFConfig::get, DOC(dai, ToFConfig, get)) diff --git a/src/pipeline/node/NodeBindings.cpp b/src/pipeline/node/NodeBindings.cpp index ef2bd8c2e..56c089e80 100644 --- a/src/pipeline/node/NodeBindings.cpp +++ b/src/pipeline/node/NodeBindings.cpp @@ -114,6 +114,7 @@ void bind_edgedetector(pybind11::module& m, void* pCallstack); void bind_featuretracker(pybind11::module& m, void* pCallstack); void bind_apriltag(pybind11::module& m, void* pCallstack); void bind_detectionparser(pybind11::module& m, void* pCallstack); +void bind_tof(pybind11::module& m, void* pCallstack); void NodeBindings::addToCallstack(std::deque& callstack) { // Bind Node et al @@ -143,6 +144,7 @@ void NodeBindings::addToCallstack(std::deque& callstack) { callstack.push_front(bind_featuretracker); callstack.push_front(bind_apriltag); callstack.push_front(bind_detectionparser); + callstack.push_front(bind_tof); } void NodeBindings::bind(pybind11::module& m, void* pCallstack){ diff --git a/src/pipeline/node/ToFBindings.cpp b/src/pipeline/node/ToFBindings.cpp index 9bde78539..ae67b149e 100644 --- a/src/pipeline/node/ToFBindings.cpp +++ b/src/pipeline/node/ToFBindings.cpp @@ -29,13 +29,13 @@ void bind_tof(pybind11::module& m, void* pCallstack){ // Properties tofProperties - .def_readwrite("initialConfig", &ToFProperties::initialConfig) + .def_readwrite("initialConfig", &ToFProperties::initialConfig, DOC(dai, ToFProperties, initialConfig)) ; // Node tof - .def_readonly("input", &ToF::input, DOC(dai, node, ToF, input), DOC(dai, node, ToF, input)) .def_readonly("inputConfig", &ToF::inputConfig, DOC(dai, node, ToF, inputConfig), DOC(dai, node, ToF, inputConfig)) + .def_readonly("input", &ToF::input, DOC(dai, node, ToF, input), DOC(dai, node, ToF, input)) .def_readonly("depth", &ToF::depth, DOC(dai, node, ToF, depth), DOC(dai, node, ToF, depth)) .def_readonly("amplitude", &ToF::amplitude, DOC(dai, node, ToF, amplitude), DOC(dai, node, ToF, amplitude)) .def_readonly("error", &ToF::error, DOC(dai, node, ToF, error), DOC(dai, node, ToF, error)) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index ddca1828a..7d0df63e4 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -189,6 +189,7 @@ def get(self): xout = {} xout_raw = {} streams = [] +tofConfig = {} for c in cam_list: tofEnableRaw = False xout[c] = pipeline.createXLinkOut() @@ -204,9 +205,10 @@ def get(self): tof[c].depth.link(xout[c].input) xinTofConfig.out.link(tof[c].inputConfig) tofConfig = tof[c].initialConfig.get() - tofConfig.setFreqModUsed(dai.ToFConfig.DepthParams.TypeFMod.F_MOD_MIN) - tofConfig.SetAvgPhaseShuffle(True) - tofConfig.setMinAmplitude(20.0) + tofConfig.depthParams.freqModUsed = dai.RawToFConfig.DepthParams.TypeFMod.MIN + tofConfig.depthParams.avgPhaseShuffle = False + tofConfig.depthParams.minimumAmplitude = 3.0 + tof[c].initialConfig.set(tofConfig) elif cam_type_color[c]: cam[c] = pipeline.createColorCamera() cam[c].setResolution(color_res_opts[args.color_resolution]) @@ -287,6 +289,7 @@ def exit_cleanly(signum, frame): fps_capt[c] = FPS() controlQueue = device.getInputQueue('control') + tofCfgQueue = device.getInputQueue('tofConfig') # Manual exposure/focus set step EXP_STEP = 500 # us @@ -331,6 +334,7 @@ def exit_cleanly(signum, frame): chroma_denoise = 0 control = 'none' show = False + tof_amp_min = tofConfig.depthParams.minimumAmplitude print("Cam:", *[' ' + c.ljust(8) for c in cam_list], "[host | capture timestamp]") @@ -419,6 +423,11 @@ def exit_cleanly(signum, frame): elif key == ord('c'): capture_list = streams.copy() capture_time = time.strftime('%Y%m%d_%H%M%S') + elif key == ord('g'): + f_mod = dai.RawToFConfig.DepthParams.TypeFMod.MAX if tofConfig.depthParams.freqModUsed == dai.RawToFConfig.DepthParams.TypeFMod.MIN else dai.RawToFConfig.DepthParams.TypeFMod.MIN + print("ToF toggling f_mod value to:", f_mod) + tofConfig.depthParams.freqModUsed = f_mod + tofCfgQueue.send(tofConfig) elif key == ord('t'): print("Autofocus trigger (and disable continuous)") ctrl = dai.CameraControl() @@ -493,7 +502,7 @@ def exit_cleanly(signum, frame): if floodIntensity < 0: floodIntensity = 0 device.setIrFloodLightBrightness(floodIntensity) - elif key >= 0 and chr(key) in '34567890[]': + elif key >= 0 and chr(key) in '34567890[]p': if key == ord('3'): control = 'awb_mode' elif key == ord('4'): @@ -514,6 +523,8 @@ def exit_cleanly(signum, frame): control = 'luma_denoise' elif key == ord(']'): control = 'chroma_denoise' + elif key == ord('p'): + control = 'tof_amplitude_min' print("Selected control:", control) elif key in [ord('-'), ord('_'), ord('+'), ord('=')]: change = 0 @@ -564,4 +575,9 @@ def exit_cleanly(signum, frame): chroma_denoise = clamp(chroma_denoise + change, 0, 4) print("Chroma denoise:", chroma_denoise) ctrl.setChromaDenoise(chroma_denoise) + elif control == 'tof_amplitude_min': + amp_min = clamp(tofConfig.depthParams.minimumAmplitude + change, 0, 50) + print("Setting min amplitude(confidence) to:", amp_min) + tofConfig.depthParams.minimumAmplitude = amp_min + tofCfgQueue.send(tofConfig) controlQueue.send(ctrl) From 2c43c8c526782c5d3a524b78f8ec80876e0597e5 Mon Sep 17 00:00:00 2001 From: Florin Buica Date: Tue, 25 Apr 2023 13:17:54 +0300 Subject: [PATCH 317/385] add configurable phase avg --- utilities/cam_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 7d0df63e4..4c2644881 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -428,6 +428,10 @@ def exit_cleanly(signum, frame): print("ToF toggling f_mod value to:", f_mod) tofConfig.depthParams.freqModUsed = f_mod tofCfgQueue.send(tofConfig) + elif key == ord('h'): + tofConfig.depthParams.avgPhaseShuffle = not tofConfig.depthParams.avgPhaseShuffle + print("ToF toggling avgPhaseShuffle value to:", tofConfig.depthParams.avgPhaseShuffle) + tofCfgQueue.send(tofConfig) elif key == ord('t'): print("Autofocus trigger (and disable continuous)") ctrl = dai.CameraControl() From 147eec19ff396b92e592646b64e7801b2d8af439 Mon Sep 17 00:00:00 2001 From: Florin Buica Date: Tue, 25 Apr 2023 15:24:58 +0300 Subject: [PATCH 318/385] add amplitude separately -- cherry-picked, with few more ToF related changes included --- utilities/cam_test.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 4c2644881..7d6d126e5 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -81,10 +81,19 @@ def socket_type_pair(arg): help="Make OpenCV windows resizable. Note: may introduce some artifacts") parser.add_argument('-tun', '--camera-tuning', type=Path, help="Path to custom camera tuning database") -parser.add_argument('-d', '--device', default="", type=str, - help="Optional MX ID of the device to connect to.") parser.add_argument('-raw', '--enable-raw', default=False, action="store_true", help='Enable the RAW camera streams') +parser.add_argument('-tofraw', '--tof-raw', action='store_true', + help="Show just ToF raw output instead of post-processed depth") +parser.add_argument('-tofamp', '--tof-amplitude', action='store_true', + help="Show also ToF amplitude output alongside depth") +parser.add_argument('-tofcm', '--tof-cm', action='store_true', + help="Show ToF depth output in centimeters, capped to 255") +parser.add_argument('-rgbprev', '--rgb-preview', action='store_true', + help="Show RGB `preview` stream instead of full size `isp`") + +parser.add_argument('-d', '--device', default="", type=str, + help="Optional MX ID of the device to connect to.") parser.add_argument('-ctimeout', '--connection-timeout', default=30000, help="Connection timeout in ms. Default: %(default)s (sets DEPTHAI_CONNECTION_TIMEOUT environment variable)") @@ -188,6 +197,7 @@ def get(self): tof = {} xout = {} xout_raw = {} +xout_tof_amp = {} streams = [] tofConfig = {} for c in cam_list: @@ -209,6 +219,12 @@ def get(self): tofConfig.depthParams.avgPhaseShuffle = False tofConfig.depthParams.minimumAmplitude = 3.0 tof[c].initialConfig.set(tofConfig) + if args.tof_amplitude: + amp_name = 'tof_amplitude_' + c + xout_tof_amp[c] = pipeline.create(dai.node.XLinkOut) + xout_tof_amp[c].setStreamName(amp_name) + streams.append(amp_name) + tof[c].amplitude.link(xout_tof_amp[c].input) elif cam_type_color[c]: cam[c] = pipeline.createColorCamera() cam[c].setResolution(color_res_opts[args.color_resolution]) @@ -240,7 +256,7 @@ def get(self): xout_raw[c] = pipeline.create(dai.node.XLinkOut) xout_raw[c].setStreamName(raw_name) streams.append(raw_name) - tof[c].amplitude.link(xout_raw[c].input) + cam[c].raw.link(xout_raw[c].input) cam[c].setRawOutputPacked(False) if args.camera_tuning: @@ -271,6 +287,8 @@ def exit_cleanly(signum, frame): cam_name[p.socket.name] = p.sensorName if args.enable_raw: cam_name['raw_'+p.socket.name] = p.sensorName + if args.tof_amplitude: + cam_name['tof_amplitude_'+p.socket.name] = p.sensorName print('USB speed:', device.getUsbSpeed().name) @@ -352,7 +370,7 @@ def exit_cleanly(signum, frame): fps_capt[c].update(pkt.getTimestamp().total_seconds()) width, height = pkt.getWidth(), pkt.getHeight() frame = pkt.getCvFrame() - if cam_type_tof[c.split('_')[-1]] and not c.startswith('raw_'): + if cam_type_tof[c.split('_')[-1]] and not (c.startswith('raw_') or c.startswith('tof_amplitude_')): if args.tof_cm: # pixels represent `cm`, capped to 255. Value can be checked hovering the mouse frame = (frame // 10).clip(0, 255).astype(np.uint8) @@ -379,7 +397,7 @@ def exit_cleanly(signum, frame): ) capture_list.remove(c) print() - if c.startswith('raw_'): + if c.startswith('raw_') or c.startswith('tof_amplitude_'): if capture: filename = capture_file_info + '_10bit.bw' print('Saving:', filename) From cc12e2ba25fc5870058894d2803e5f460bd66910 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Tue, 2 May 2023 15:48:42 +0300 Subject: [PATCH 319/385] ToFConfigBindings: fix docs/wheels build --- src/pipeline/datatype/ToFConfigBindings.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pipeline/datatype/ToFConfigBindings.cpp b/src/pipeline/datatype/ToFConfigBindings.cpp index 34716a611..a51fae8ac 100644 --- a/src/pipeline/datatype/ToFConfigBindings.cpp +++ b/src/pipeline/datatype/ToFConfigBindings.cpp @@ -59,10 +59,10 @@ void bind_tofconfig(pybind11::module& m, void* pCallstack){ .def(py::init<>()) .def(py::init>()) - .def("setDepthParams", static_cast(&ToFConfig::setDepthParams), py::arg("config"), DOC(dai, ToFConfig, setDepthParams, 2)) - .def("setFreqModUsed", static_cast(&ToFConfig::setFreqModUsed), DOC(dai, ToFConfig, setDepthParams, 2)) - .def("setAvgPhaseShuffle", &ToFConfig::setAvgPhaseShuffle, DOC(dai, node, ToFConfig, setAvgPhaseShuffle)) - .def("setMinAmplitude", &ToFConfig::setMinAmplitude, DOC(dai, node, ToFConfig, setMinAmplitude)) + .def("setDepthParams", static_cast(&ToFConfig::setDepthParams), py::arg("config"), DOC(dai, ToFConfig, setDepthParams)) + .def("setFreqModUsed", static_cast(&ToFConfig::setFreqModUsed), DOC(dai, ToFConfig, setFreqModUsed)) + .def("setAvgPhaseShuffle", &ToFConfig::setAvgPhaseShuffle, DOC(dai, ToFConfig, setAvgPhaseShuffle)) + .def("setMinAmplitude", &ToFConfig::setMinAmplitude, DOC(dai, ToFConfig, setMinAmplitude)) .def("set", &ToFConfig::set, py::arg("config"), DOC(dai, ToFConfig, set)) .def("get", &ToFConfig::get, DOC(dai, ToFConfig, get)) From 25bed1f66d5ac7538d79f35e810ad8bb39833b22 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 8 May 2023 10:49:07 +0300 Subject: [PATCH 320/385] Some cleanup, some fixes for cam_test.py -- includes few missing things from previous cherry-picks... --- CMakeLists.txt | 1 - utilities/cam_test.py | 41 +++++++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 35c956c01..a20e65da9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,7 +124,6 @@ pybind11_add_module(${TARGET_NAME} src/pipeline/node/IMUBindings.cpp src/pipeline/node/EdgeDetectorBindings.cpp src/pipeline/node/FeatureTrackerBindings.cpp - src/pipeline/node/ToFBindings.cpp src/pipeline/node/AprilTagBindings.cpp src/pipeline/node/DetectionParserBindings.cpp src/pipeline/node/WarpBindings.cpp diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 7d6d126e5..240007de3 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -45,7 +45,6 @@ from itertools import cycle from pathlib import Path import sys -#import cam_test_gui import signal @@ -53,17 +52,18 @@ def socket_type_pair(arg): socket, type = arg.split(',') if not (socket in ['rgb', 'left', 'right', 'cama', 'camb', 'camc', 'camd']): raise ValueError("") - if not (type in ['m', 'mono', 'c', 'color']): + if not (type in ['m', 'mono', 'c', 'color', 't', 'tof']): raise ValueError("") is_color = True if type in ['c', 'color'] else False - return [socket, is_color] + is_tof = True if type in ['t', 'tof'] else False + return [socket, is_color, is_tof] parser = argparse.ArgumentParser() parser.add_argument('-cams', '--cameras', type=socket_type_pair, nargs='+', - default=[['rgb', True], ['left', False], - ['right', False], ['camd', True]], - help="Which camera sockets to enable, and type: c[olor] / m[ono]. " + default=[['rgb', True, False], ['left', False, False], + ['right', False, False], ['camd', True, False]], + help="Which camera sockets to enable, and type: c[olor] / m[ono] / t[of]. " "E.g: -cams rgb,m right,c . Default: rgb,c left,m right,m camd,c") parser.add_argument('-mres', '--mono-resolution', type=int, default=800, choices={480, 400, 720, 800}, help="Select mono camera resolution (height). Default: %(default)s") @@ -109,15 +109,18 @@ def socket_type_pair(arg): import depthai as dai if len(sys.argv) == 1: + import cam_test_gui cam_test_gui.main() cam_list = [] cam_type_color = {} +cam_type_tof = {} print("Enabled cameras:") -for socket, is_color in args.cameras: +for socket, is_color, is_tof in args.cameras: cam_list.append(socket) cam_type_color[socket] = is_color - print(socket.rjust(7), ':', 'color' if is_color else 'mono') + cam_type_tof[socket] = is_tof + print(socket.rjust(7), ':', 'tof' if is_tof else 'color' if is_color else 'mono') print("DepthAI version:", dai.__version__) print("DepthAI path:", dai.__file__) @@ -230,7 +233,10 @@ def get(self): cam[c].setResolution(color_res_opts[args.color_resolution]) cam[c].setIspScale(1, args.isp_downscale) # cam[c].initialControl.setManualFocus(85) # TODO - cam[c].isp.link(xout[c].input) + if args.rgb_preview: + cam[c].preview.link(xout[c].input) + else: + cam[c].isp.link(xout[c].input) else: cam[c] = pipeline.createMonoCamera() cam[c].setResolution(mono_res_opts[args.mono_resolution]) @@ -352,7 +358,9 @@ def exit_cleanly(signum, frame): chroma_denoise = 0 control = 'none' show = False - tof_amp_min = tofConfig.depthParams.minimumAmplitude + + jet_custom = cv2.applyColorMap(np.arange(256, dtype=np.uint8), cv2.COLORMAP_JET) + jet_custom[0] = [0, 0, 0] print("Cam:", *[' ' + c.ljust(8) for c in cam_list], "[host | capture timestamp]") @@ -370,7 +378,8 @@ def exit_cleanly(signum, frame): fps_capt[c].update(pkt.getTimestamp().total_seconds()) width, height = pkt.getWidth(), pkt.getHeight() frame = pkt.getCvFrame() - if cam_type_tof[c.split('_')[-1]] and not (c.startswith('raw_') or c.startswith('tof_amplitude_')): + cam_skt = c.split('_')[-1] + if cam_type_tof[cam_skt] and not (c.startswith('raw_') or c.startswith('tof_amplitude_')): if args.tof_cm: # pixels represent `cm`, capped to 255. Value can be checked hovering the mouse frame = (frame // 10).clip(0, 255).astype(np.uint8) @@ -387,7 +396,7 @@ def exit_cleanly(signum, frame): print(txt) capture = c in capture_list if capture: - capture_file_info = ('capture_' + c + '_' + cam_name[c] + capture_file_info = ('capture_' + c + '_' + cam_name[cam_socket_opts[cam_skt].name] + '_' + str(width) + 'x' + str(height) + '_exp_' + str(int(pkt.getExposureTime().total_seconds()*1e6)) + '_iso_' + str(pkt.getSensitivity()) @@ -409,7 +418,7 @@ def exit_cleanly(signum, frame): if type == dai.ImgFrame.Type.RAW12: multiplier = (1 << (16-4)) frame = frame * multiplier # Debayer as color for preview/png - if cam_type_color[c.split('_')[-1]]: + if cam_type_color[cam_skt]: # See this for the ordering, at the end of page: # https://docs.opencv.org/4.5.1/de/d25/imgproc_color_conversions.html # TODO add bayer order to ImgFrame getType() @@ -441,12 +450,12 @@ def exit_cleanly(signum, frame): elif key == ord('c'): capture_list = streams.copy() capture_time = time.strftime('%Y%m%d_%H%M%S') - elif key == ord('g'): + elif key == ord('g') and tof: f_mod = dai.RawToFConfig.DepthParams.TypeFMod.MAX if tofConfig.depthParams.freqModUsed == dai.RawToFConfig.DepthParams.TypeFMod.MIN else dai.RawToFConfig.DepthParams.TypeFMod.MIN print("ToF toggling f_mod value to:", f_mod) tofConfig.depthParams.freqModUsed = f_mod tofCfgQueue.send(tofConfig) - elif key == ord('h'): + elif key == ord('h') and tof: tofConfig.depthParams.avgPhaseShuffle = not tofConfig.depthParams.avgPhaseShuffle print("ToF toggling avgPhaseShuffle value to:", tofConfig.depthParams.avgPhaseShuffle) tofCfgQueue.send(tofConfig) @@ -597,7 +606,7 @@ def exit_cleanly(signum, frame): chroma_denoise = clamp(chroma_denoise + change, 0, 4) print("Chroma denoise:", chroma_denoise) ctrl.setChromaDenoise(chroma_denoise) - elif control == 'tof_amplitude_min': + elif control == 'tof_amplitude_min' and tof: amp_min = clamp(tofConfig.depthParams.minimumAmplitude + change, 0, 50) print("Setting min amplitude(confidence) to:", amp_min) tofConfig.depthParams.minimumAmplitude = amp_min From d9c7f15c127c513e1f6eff4d5c71b6733bae9ed1 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Fri, 17 Mar 2023 05:37:05 +0200 Subject: [PATCH 321/385] ImgFrame: handle RAW10/12/14 (unpacked) like RAW16 --- src/pipeline/datatype/ImgFrameBindings.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pipeline/datatype/ImgFrameBindings.cpp b/src/pipeline/datatype/ImgFrameBindings.cpp index b39b1e721..a89ff4230 100644 --- a/src/pipeline/datatype/ImgFrameBindings.cpp +++ b/src/pipeline/datatype/ImgFrameBindings.cpp @@ -202,6 +202,9 @@ void bind_imgframe(pybind11::module& m, void* pCallstack){ break; case ImgFrame::Type::RAW16: + case ImgFrame::Type::RAW14: + case ImgFrame::Type::RAW12: + case ImgFrame::Type::RAW10: shape = {img.getHeight(), img.getWidth()}; dtype = py::dtype::of(); break; @@ -303,6 +306,9 @@ void bind_imgframe(pybind11::module& m, void* pCallstack){ case ImgFrame::Type::RAW8: case ImgFrame::Type::RAW16: + case ImgFrame::Type::RAW14: + case ImgFrame::Type::RAW12: + case ImgFrame::Type::RAW10: case ImgFrame::Type::GRAY8: case ImgFrame::Type::GRAYF16: default: From 561dcd174a82428684b9e12564c37e97b4a26935 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sun, 14 May 2023 10:31:51 +0200 Subject: [PATCH 322/385] Added some python bindings for Node Inputs (getParent and possibleDatatypes) --- src/pipeline/node/NodeBindings.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pipeline/node/NodeBindings.cpp b/src/pipeline/node/NodeBindings.cpp index 56c089e80..d1a758fe8 100644 --- a/src/pipeline/node/NodeBindings.cpp +++ b/src/pipeline/node/NodeBindings.cpp @@ -211,6 +211,9 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("group", &Node::Input::group, DOC(dai, Node, Input, group)) .def_readwrite("name", &Node::Input::name, DOC(dai, Node, Input, name)) .def_readwrite("type", &Node::Input::type, DOC(dai, Node, Input, type)) + .def_readwrite("possibleDatatypes", &Node::Input::possibleDatatypes, DOC(dai, Node, Input, possibleDatatypes)) + .def("getParent", static_cast(&Node::Input::getParent), py::return_value_policy::reference_internal, DOC(dai, Node, Input, getParent)) + .def("getParent", static_cast(&Node::Input::getParent), py::return_value_policy::reference_internal, DOC(dai, Node, Input, getParent)) .def("setBlocking", &Node::Input::setBlocking, py::arg("blocking"), DOC(dai, Node, Input, setBlocking)) .def("getBlocking", &Node::Input::getBlocking, DOC(dai, Node, Input, getBlocking)) .def("setQueueSize", &Node::Input::setQueueSize, py::arg("size"), DOC(dai, Node, Input, setQueueSize)) From 0f20d8d4a08e8422307f3611f91ba709ce5e8f24 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 15 May 2023 18:47:54 +0300 Subject: [PATCH 323/385] core/FW: UVC H.264, fix Device to forward pipeline DeviceConfig --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 7bf36cdef..4a452a9df 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 7bf36cdef7bee74c89b50515d0149d9468191c12 +Subproject commit 4a452a9df02d2991fdac29b8984313d0d89d76e8 From 5af35ba3ed4f972b86b85b4523f031e156b9a2ae Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 15 May 2023 19:01:17 +0300 Subject: [PATCH 324/385] rgb_uvc.py: Windows fixes: change SIGKILL->SIGTERM, set env var before depthai import --- examples/ColorCamera/rgb_uvc.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/examples/ColorCamera/rgb_uvc.py b/examples/ColorCamera/rgb_uvc.py index 42e510c95..78a27129b 100755 --- a/examples/ColorCamera/rgb_uvc.py +++ b/examples/ColorCamera/rgb_uvc.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -import depthai as dai import time import argparse @@ -10,6 +9,14 @@ parser.add_argument('-l', '--load-and-exit', default=False, action="store_true") args = parser.parse_args() +if args.load_and_exit: + import os + # Disabling device watchdog, so it doesn't need the host to ping periodically. + # Note: this is done before importing `depthai` + os.environ["DEPTHAI_WATCHDOG"] = "0" + +import depthai as dai + def getPipeline(): enable_4k = True # Will downscale 4K -> 1080p @@ -68,17 +75,14 @@ def flash(pipeline=None): quit() if args.load_and_exit: - import os - # Disabling device watchdog, so it doesn't need the host to ping periodically - os.environ["DEPTHAI_WATCHDOG"] = "0" device = dai.Device(getPipeline()) - print("\nDevice started, open a UVC viewer to check the camera stream.") - print("Attempting to force-quit this process...") - print("To reconnect with depthai, a device power-cycle may be required") - # We do not want the device to be closed, so kill the process. + print("\nDevice started. Attempting to force-terminate this process...") + print("Open an UVC viewer to check the camera stream.") + print("To reconnect with depthai, a device power-cycle may be required in some cases") + # We do not want the device to be closed, so terminate the process uncleanly. # (TODO add depthai API to be able to cleanly exit without closing device) import signal - os.kill(os.getpid(),signal.SIGKILL) + os.kill(os.getpid(), signal.SIGTERM) # Standard UVC load with depthai with dai.Device(getPipeline()) as device: From a2f73eb7b8751f76d786ae17f26f5c0f246f2efa Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Mon, 15 May 2023 20:02:04 +0200 Subject: [PATCH 325/385] Modified XLink to a temporary branch --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 4bb0f8345..cc8edba15 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 4bb0f8345de4f3a1c045e62f19a7c9139accc7dc +Subproject commit cc8edba15e6d69b8ddb1b6b8e303d8219244759c From 1ac989693e6c198cce1d64c65025d1feea743acd Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 16 May 2023 20:18:41 +0200 Subject: [PATCH 326/385] Updated XLink with fixed winusb mxid retrieval --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index cc8edba15..1defb6790 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit cc8edba15e6d69b8ddb1b6b8e303d8219244759c +Subproject commit 1defb67907dcb191335d98897754e0c67ef04075 From 78720c26bea88b1bdda95305d40973b68cb66949 Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Fri, 19 May 2023 00:05:55 +0300 Subject: [PATCH 327/385] FW: Stereo: handle queue blocking settings --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index a33f90b3f..75b93d617 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit a33f90b3fb627df08e30a4148002c74f804a50f2 +Subproject commit 75b93d617bf97b3fcd57504438592c294ab64478 From 72cddbb885eb6495ce74ffbe80e1492c612a5452 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Wed, 24 May 2023 18:43:01 +0200 Subject: [PATCH 328/385] [FW] Updated for some devices and ToF --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 75b93d617..2bd6419ba 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 75b93d617bf97b3fcd57504438592c294ab64478 +Subproject commit 2bd6419babb3a3c46f3ed67acd227b74fd43cb85 From a24b0c350b3ef8078c7674deb06af1d5e0b1b3bd Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 25 May 2023 21:28:33 +0200 Subject: [PATCH 329/385] Added system-site-packages to venv for architectures where pyqt5 isn't available --- docs/source/_static/install_depthai.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/source/_static/install_depthai.sh b/docs/source/_static/install_depthai.sh index 94556ce55..27aa3b75c 100755 --- a/docs/source/_static/install_depthai.sh +++ b/docs/source/_static/install_depthai.sh @@ -210,7 +210,7 @@ elif [[ $(uname -s) == "Linux" ]]; then # install python 3.10 if [ "$install_python" == "true" ]; then echo "installing python 3.10" - + sudo yes "" | sudo add-apt-repository ppa:deadsnakes/ppa sudo apt -y install python3.10 sudo apt -y install python3.10-venv @@ -219,13 +219,21 @@ elif [[ $(uname -s) == "Linux" ]]; then echo "Creating python virtual environment in $VENV_DIR" - "$python_executable" -m venv "$VENV_DIR" + machine=$(uname -m) + if [[ $machine != 'armv6l' && $machine != 'armv7l' && $machine != 'aarch64' && $machine != 'arm64' ]]; then + "$python_executable" -m venv "$VENV_DIR" + else + "$python_executable" -m venv "$VENV_DIR" --system-site-packages + fi source "$VENV_DIR/bin/activate" python -m pip install --upgrade pip pip install packaging - pip install pyqt5 + + if [[ $machine != 'armv6l' && $machine != 'armv7l' && $machine != 'aarch64' && $machine != 'arm64' ]]; then + pip install pyqt5 + fi else echo "Error: Host $(uname -s) not supported." exit 99 From ba178299d80bb6f60aa20c836ca8d683b577ca0f Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Mon, 29 May 2023 22:49:14 +0200 Subject: [PATCH 330/385] [FW] WIP for S2 PoE boards --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 2bd6419ba..14dd92bff 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 2bd6419babb3a3c46f3ed67acd227b74fd43cb85 +Subproject commit 14dd92bff859952e15d918bee7a2f784d7fb040f From 3f093046749ab51ccae41eb805932923a01fc452 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 30 May 2023 18:04:28 +0200 Subject: [PATCH 331/385] [FW] WIP for new SoMs --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 14dd92bff..be9c9d9fa 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 14dd92bff859952e15d918bee7a2f784d7fb040f +Subproject commit be9c9d9fa379fdf6f433a8bf758a194529a8b510 From 42af7451ddd3f75a1721ba10dcc21bfa0f3216b0 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Tue, 30 May 2023 19:08:47 +0200 Subject: [PATCH 332/385] Fixed build --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index be9c9d9fa..7c07d7c93 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit be9c9d9fa379fdf6f433a8bf758a194529a8b510 +Subproject commit 7c07d7c933a0e5993e23a5460aa5e0c9f5bb409b From ebd5478769e3bd0cb52b0871b7764d2a878c78f8 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Wed, 31 May 2023 05:35:34 +0300 Subject: [PATCH 333/385] RVC3 FW: set camera orientation, manual focus (needs OS update, 1.10 or newer) --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 841d763b6..4f5c4ffb9 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 841d763b6b5fa847c69b958de31ac68380ccaebe +Subproject commit 4f5c4ffb9f149fc870baa07443b9ee0e7d284860 From 281811d987d50f5a9ab9581df96dfcd1ce9c91fe Mon Sep 17 00:00:00 2001 From: jakasker Date: Thu, 1 Jun 2023 16:21:54 +0200 Subject: [PATCH 334/385] Added dap creation, emmc access --- .../samples/Script/script_emmc_access.rst | 64 ++++++++++++ docs/source/tutorials/code_samples.rst | 1 + docs/source/tutorials/standalone_mode.rst | 53 +++++++--- examples/Script/script_emmc_access.py | 98 +++++++++++++++++++ 4 files changed, 201 insertions(+), 15 deletions(-) create mode 100644 docs/source/samples/Script/script_emmc_access.rst create mode 100644 examples/Script/script_emmc_access.py diff --git a/docs/source/samples/Script/script_emmc_access.rst b/docs/source/samples/Script/script_emmc_access.rst new file mode 100644 index 000000000..c4db24eb0 --- /dev/null +++ b/docs/source/samples/Script/script_emmc_access.rst @@ -0,0 +1,64 @@ +Script EMMC access +================== + +.. note:: + + This example requires a device with onboard EMMC memory (e.g. OAK-1-POE). + To check whether your device has EMMC memory, run the bootloader version script at :ref:`Bootloader Version` and check whether the output contains ``Memory.EMMC``. + + + +This example shows how to use :ref:`Script` node to access EMMC memory of the device. Default location for EMMC memory is ``/media/mmcsd-0-0/``. The first script in the pipeline works by writing an image to EMMC memory. +The second script starts a webserver on ::code:`/media/mmcsd-0-0/` directory and serves the image from EMMC memory. + + +Setup +##### + +.. include:: /includes/install_from_pypi.rst + + +Prerequisites +############# + +We first need to enable the EMMC memory as storage on the device. To do so, we need to flash the device with an application that has EMMC enabled. + +Example application: + +.. code-block:: python + + import depthai as dai + + # Create pipeline + pipeline = dai.Pipeline() + + # Set board config + board = dai.BoardConfig() + board.emmc = True + config = dai.Device.Config() + config.board = board + pipeline.setBoardConfig(board) + + (f, bl) = dai.DeviceBootloader.getFirstAvailableDevice() + bootloader = dai.DeviceBootloader(bl) + progress = lambda p : print(f'Flashing progress: {p*100:.1f}%') + (r, errmsg) = bootloader.flash(progress, pipeline, memory=dai.DeviceBootloader.Memory.EMMC) + if r: print("Flash OK") + + +The above code will flash the device with the application that enables the script node to access EMMC memory. Now we should be able to access EMMC memory even when the device is in standard mode (connected to the host PC). + +Source code +########### + +.. tabs:: + + .. tab:: Python + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../examples/Script/script_emmc_access.py + :language: python + :linenos: + +.. include:: /includes/footer-short.rst diff --git a/docs/source/tutorials/code_samples.rst b/docs/source/tutorials/code_samples.rst index 66fc52ba4..8d1af2a57 100644 --- a/docs/source/tutorials/code_samples.rst +++ b/docs/source/tutorials/code_samples.rst @@ -128,6 +128,7 @@ are presented with code. - :ref:`Script MJPEG server` - Serve MJPEG video stream over HTTP response (only OAK-POE devices) - :ref:`Script NNData example` - Constructs :ref:`NNData` in Script node and sends it to the host - :ref:`Script UART communication` - UART communication with Script node +- :ref:`Script EMMC access` - Access EMMC memory from the Script node .. rubric:: SpatialDetection diff --git a/docs/source/tutorials/standalone_mode.rst b/docs/source/tutorials/standalone_mode.rst index c4eb4f189..d364a2907 100644 --- a/docs/source/tutorials/standalone_mode.rst +++ b/docs/source/tutorials/standalone_mode.rst @@ -67,26 +67,49 @@ can flash the pipeline to the device, along with its assests (eg. AI models). Yo After successfully flashing the pipeline, it will get started automatically when you power up the device. If you would like to change the flashed pipeline, simply re-flash it again. -.. - Clear flash - ########### +Alternatively, you can also flash the pipeline with the :ref:`Device Manager`. For this apporach, you will need a Depthai Applicaion Package (.dap), which you +can create with the following script: - Since pipeline will start when powering the device, this can lead to unnecesary heating. If you would like to clear - the flashed pipeline, use the code snippet below. - .. warning:: - Code below doesn't work yet. We will be adding "flashClear" helper function to the library. +.. code-block:: python - .. code-block:: python + import depthai as dai - import depthai as dai - (f, bl) = dai.DeviceBootloader.getFirstAvailableDevice() - if not f: - print('No devices found, exiting...') - exit(-1) + pipeline = dai.Pipeline() + + # Define standalone pipeline; add nodes and link them + # cam = pipeline.create(dai.node.ColorCamera) + # script = pipeline.create(dai.node.Script) + # ... + + # Create Depthai Application Package (.dap) + (f, bl) = dai.DeviceBootloader.getFirstAvailableDevice() + bootloader = dai.DeviceBootloader(bl) + bootloader.saveDepthaiApplicationPackage(pipeline=pipeline, path=) + + +Clear flash +########### + +Since pipeline will start when powering the device, this can lead to unnecesary heating. If you would like to clear +the flashed pipeline, use the code snippet below. + + +.. warning:: + Code below doesn't work yet. We will be adding "flashClear" helper function to the library. + + +.. code-block:: python + + import depthai as dai + (f, bl) = dai.DeviceBootloader.getFirstAvailableDevice() + if not f: + print('No devices found, exiting...') + exit(-1) + + with dai.DeviceBootloader(bl) as bootloader: + bootloader.flashClear() - with dai.DeviceBootloader(bl) as bootloader: - bootloader.flashClear() Factory reset ############# diff --git a/examples/Script/script_emmc_access.py b/examples/Script/script_emmc_access.py new file mode 100644 index 000000000..a93485976 --- /dev/null +++ b/examples/Script/script_emmc_access.py @@ -0,0 +1,98 @@ +import depthai as dai +import cv2 + +# Start defining a pipeline +pipeline = dai.Pipeline() + +board = dai.BoardConfig() +board.emmc = True +pipeline.setBoardConfig(board) + +# Define source and output +camRgb = pipeline.create(dai.node.ColorCamera) +jpegEncoder = pipeline.create(dai.node.VideoEncoder) + +# Properties +camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) +jpegEncoder.setDefaultProfilePreset(1, dai.VideoEncoderProperties.Profile.MJPEG) + +#Set a write script +script_write = pipeline.createScript() +script_write.setProcessor(dai.ProcessorType.LEON_CSS) +script_write.setScript(""" + + import os + index = 1000 + import time + while True: + # Find an unused file name first + while True: + path = '/media/mmcsd-0-0/' + str(index) + '.jpg' + if not os.path.exists(path): + break + index += 1 + frame = node.io['jpeg'].get() + node.warn(f'Saving to EMMC: {path}') + with open(path, 'wb') as f: + f.write(frame.getData()) + index += 1 + time.sleep(3) + +""") + +#Set a read script +script_read = pipeline.createScript() +script_read.setProcessor(dai.ProcessorType.LEON_CSS) +script_read.setScript(""" + + import http.server + import socketserver + import socket + import fcntl + import struct + import os + + def get_ip_address(ifname): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + return socket.inet_ntoa(fcntl.ioctl( + s.fileno(), + -1071617759, # SIOCGIFADDR + struct.pack('256s', ifname[:15].encode()) + )[20:24]) + + # Note: `chdir` here will prevent unmount, this should be improved! + os.chdir('/media/mmcsd-0-0') + + PORT = 80 + Handler = http.server.SimpleHTTPRequestHandler + + with socketserver.TCPServer(("", PORT), Handler) as httpd: + ip = get_ip_address('re0') + node.warn(f'===== HTTP file server accessible at: http://{ip}') + httpd.serve_forever() + +""") + +# Linking + +camRgb.video.link(jpegEncoder.input) +jpegEncoder.bitstream.link(script_write.inputs['jpeg']) +script_write.inputs['jpeg'].setBlocking(False) +xout = pipeline.create(dai.node.XLinkOut) +xout.setStreamName("rgb") +script_read.outputs['jpeg'].link(xout.input) + + +# Pipeline defined, now the device is connected to +with dai.Device(pipeline) as device: + # Output queue will be used to get the rgb frames from the output defined above + qRgb = device.getOutputQueue(name="rgb", maxSize=100, blocking=False) + + while True: + inRgb = qRgb.tryGet() + + if inRgb is not None: + cv2.imshow("rgb", inRgb.getCvFrame()) + + if cv2.waitKey(1) == ord('q'): + break \ No newline at end of file From 6d10f525c465bfc08bb5a54783b2ea1c3600d655 Mon Sep 17 00:00:00 2001 From: jakaskerl <53253318+jakaskerl@users.noreply.github.com> Date: Thu, 1 Jun 2023 16:28:22 +0200 Subject: [PATCH 335/385] Update standalone_mode.rst --- docs/source/tutorials/standalone_mode.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/tutorials/standalone_mode.rst b/docs/source/tutorials/standalone_mode.rst index d364a2907..95f4440a5 100644 --- a/docs/source/tutorials/standalone_mode.rst +++ b/docs/source/tutorials/standalone_mode.rst @@ -67,7 +67,7 @@ can flash the pipeline to the device, along with its assests (eg. AI models). Yo After successfully flashing the pipeline, it will get started automatically when you power up the device. If you would like to change the flashed pipeline, simply re-flash it again. -Alternatively, you can also flash the pipeline with the :ref:`Device Manager`. For this apporach, you will need a Depthai Applicaion Package (.dap), which you +Alternatively, you can also flash the pipeline with the :ref:`Device Manager`. For this approach, you will need a Depthai Application Package (.dap), which you can create with the following script: From a6e7193636aa7731e4a71b438b457410bf997374 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Fri, 2 Jun 2023 19:19:24 +0200 Subject: [PATCH 336/385] [FW] Fixed camera orientation --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 2bd6419ba..ceb954c16 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 2bd6419babb3a3c46f3ed67acd227b74fd43cb85 +Subproject commit ceb954c16f0d8214873242c0adeb9c3819390a96 From 4ebba138bf0a981a825100c8879ccc18bb02c5ab Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sat, 3 Jun 2023 21:57:25 +0200 Subject: [PATCH 337/385] Added docs about measuring operation times (using depthai level trace) --- docs/source/tutorials/low-latency.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/source/tutorials/low-latency.rst b/docs/source/tutorials/low-latency.rst index 4d27dec4a..25e7df418 100644 --- a/docs/source/tutorials/low-latency.rst +++ b/docs/source/tutorials/low-latency.rst @@ -155,6 +155,28 @@ A few options to reduce bandwidth: - Encode frames (H.264, H.265, MJPEG) on-device using :ref:`VideoEncoder node ` - Reduce FPS/resolution/number of streams +Measuring operation times +######################### + +If user sets depthai level to `trace` (see :ref:`DepthAI debugging level`), depthai will log operation times for each node/process, as shown below. + +.. code-block:: bash + :emphasize-lines: 1,2,5,6,7,8,9,10,13 + + [SpatialDetectionNetwork(1)] [trace] SpatialDetectionNetwork syncing took '70.39142' ms. + [StereoDepth(4)] [trace] Warp node took '2.2945' ms. + [system] [trace] EV:0,S:0,IDS:27,IDD:10,TSS:2,TSN:601935518 + [system] [trace] EV:0,S:1,IDS:27,IDD:10,TSS:2,TSN:602001382 + [StereoDepth(4)] [trace] Stereo took '12.27392' ms. + [StereoDepth(4)] [trace] 'Median+Disparity to depth' pipeline took '0.86295' ms. + [StereoDepth(4)] [trace] Stereo post processing (total) took '0.931422' ms. + [SpatialDetectionNetwork(1)] [trace] NeuralNetwork inference took '62.274784' ms. + [StereoDepth(4)] [trace] Stereo rectification took '2.686294' ms. + [MonoCamera(3)] [trace] Mono ISP took '1.726888' ms. + [system] [trace] EV:0,S:0,IDS:20,IDD:25,TSS:2,TSN:616446812 + [system] [trace] EV:0,S:1,IDS:20,IDD:25,TSS:2,TSN:616489715 + [SpatialDetectionNetwork(1)] [trace] DetectionParser took '3.464118' ms. + Reducing latency when running NN ################################ From d29eeeae26419472046b5bf452773c228a6eeb94 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sat, 3 Jun 2023 22:45:37 +0200 Subject: [PATCH 338/385] Updated docs about max number of objects ObjectTracker can track --- docs/source/components/nodes/object_tracker.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/components/nodes/object_tracker.rst b/docs/source/components/nodes/object_tracker.rst index 5fdb9e9cc..579db9321 100644 --- a/docs/source/components/nodes/object_tracker.rst +++ b/docs/source/components/nodes/object_tracker.rst @@ -73,7 +73,7 @@ A similar comparison of object trackers with more information can be found `here Maximum number of tracked objects ################################# -:code:`SHORT_TERM_KCF` can track up to 60 objects at once, while all other trackers can (theoretically) track up to 1000 objects at once. +**ObjectTracker** node can track up to 60 objects at once. At the moment the firmware crashes if there are more than 60 objects to track. Usage ##### From 88cad0e319c43818ae6c3b68212f6788bc43a7b8 Mon Sep 17 00:00:00 2001 From: JanLipovsek Date: Wed, 7 Jun 2023 18:00:55 +0200 Subject: [PATCH 339/385] added child run id to summary --- .github/workflows/main.yml | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 030265f29..469f0b65d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -540,14 +540,33 @@ jobs: event-type: depthai-python-release client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' + # notify_hil_workflow_linux_x86_64: + # needs: [build-linux-x86_64] + # runs-on: ubuntu-latest + # steps: + # - name: Repository Dispatch + # uses: peter-evans/repository-dispatch@v2 + # with: + # token: ${{ secrets.HIL_CORE_DISPATCH_TOKEN }} + # repository: luxonis/depthai-core-hil-tests + # event-type: python-hil-event + # client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' + notify_hil_workflow_linux_x86_64: needs: [build-linux-x86_64] runs-on: ubuntu-latest steps: - - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v2 - with: - token: ${{ secrets.HIL_CORE_DISPATCH_TOKEN }} - repository: luxonis/depthai-core-hil-tests - event-type: python-hil-event - client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' \ No newline at end of file + - name: Dispatch an action and get the run ID + uses: codex-/return-dispatch@v1 + id: return_dispatch + with: + token: ${{ secrets.HIL_CORE_DISPATCH_TOKEN }} # Note this is NOT GITHUB_TOKEN but a PAT + ref: main # or refs/heads/target_branch + repo: depthai-core-hil-tests + owner: luxonis + workflow: regression_test.yml + workflow_inputs: '{"commit": "${{ github.ref }}", "sha": "${{ github.sha }}", "parent_url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' + workflow_timeout_seconds: 120 # Default: 300 + + - name: Release + run: echo "https://github.com/luxonis/depthai-core-hil-tests/actions/runs/${{steps.return_dispatch.outputs.run_id}}" >> $GITHUB_STEP_SUMMARY \ No newline at end of file From fc53ec248b91b165d45c24bfd236fd351a19891d Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Wed, 7 Jun 2023 19:28:04 +0200 Subject: [PATCH 340/385] Bump version to v2.22.0.0 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 5f75ae74a..ae0379482 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 5f75ae74ade6ae12bd56c71c0149c0482f23c18f +Subproject commit ae03794826aeb34f00481238ae30c65f6ec8915c From 53feb9395f3e7eea60fbadcfd4b26bf61824770c Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 8 Jun 2023 12:10:35 +0200 Subject: [PATCH 341/385] Added filter by device name --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index ae0379482..830fa4cf6 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit ae03794826aeb34f00481238ae30c65f6ec8915c +Subproject commit 830fa4cf696b2fc7ab96bc305e14428a88bb5e6a From 552abdedc38dac0aa4712ed30da43f1eeac67825 Mon Sep 17 00:00:00 2001 From: TheMarpe Date: Thu, 8 Jun 2023 12:27:20 +0200 Subject: [PATCH 342/385] Merge pull request #836 from JanLipovsek/get_child_run_id added child run id to summary --- .github/workflows/main.yml | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 030265f29..469f0b65d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -540,14 +540,33 @@ jobs: event-type: depthai-python-release client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' + # notify_hil_workflow_linux_x86_64: + # needs: [build-linux-x86_64] + # runs-on: ubuntu-latest + # steps: + # - name: Repository Dispatch + # uses: peter-evans/repository-dispatch@v2 + # with: + # token: ${{ secrets.HIL_CORE_DISPATCH_TOKEN }} + # repository: luxonis/depthai-core-hil-tests + # event-type: python-hil-event + # client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' + notify_hil_workflow_linux_x86_64: needs: [build-linux-x86_64] runs-on: ubuntu-latest steps: - - name: Repository Dispatch - uses: peter-evans/repository-dispatch@v2 - with: - token: ${{ secrets.HIL_CORE_DISPATCH_TOKEN }} - repository: luxonis/depthai-core-hil-tests - event-type: python-hil-event - client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' \ No newline at end of file + - name: Dispatch an action and get the run ID + uses: codex-/return-dispatch@v1 + id: return_dispatch + with: + token: ${{ secrets.HIL_CORE_DISPATCH_TOKEN }} # Note this is NOT GITHUB_TOKEN but a PAT + ref: main # or refs/heads/target_branch + repo: depthai-core-hil-tests + owner: luxonis + workflow: regression_test.yml + workflow_inputs: '{"commit": "${{ github.ref }}", "sha": "${{ github.sha }}", "parent_url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' + workflow_timeout_seconds: 120 # Default: 300 + + - name: Release + run: echo "https://github.com/luxonis/depthai-core-hil-tests/actions/runs/${{steps.return_dispatch.outputs.run_id}}" >> $GITHUB_STEP_SUMMARY \ No newline at end of file From ec095966037dda2fa521b84e5f10fe8a28d0e4fd Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 12 Jun 2023 18:02:06 +0300 Subject: [PATCH 343/385] FW: update IMX296 tuning --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 830fa4cf6..0435b503c 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 830fa4cf696b2fc7ab96bc305e14428a88bb5e6a +Subproject commit 0435b503c65c7bdd280d2b641747118c0f72c865 From 21463ddc0e482a102180e52993466336a63c81d9 Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Tue, 13 Jun 2023 02:04:40 +0200 Subject: [PATCH 344/385] [FW] Fixed OAK-D-SR and OV9782 issues --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 0435b503c..2facab6fb 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 0435b503c65c7bdd280d2b641747118c0f72c865 +Subproject commit 2facab6fb48c8c31245e56bd64589b5b58862d93 From 02c69c908afc9dcec335d3a5ece42e86fec0adb5 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Thu, 15 Jun 2023 00:01:19 +0200 Subject: [PATCH 345/385] Updated install docs to include win11 to win10 instructions --- docs/source/install.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/source/install.rst b/docs/source/install.rst index c72fbee2b..2a42fa95a 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -20,9 +20,9 @@ Follow the steps below to just install depthai api library dependencies for your Please refer to :ref:`documentation below ` if any issues occur. - .. tab:: **Windows 10** + .. tab:: **Windows 10/11** - Windows 10 users can either **install DepthAI dependencies** via `Windows Installer `__, + Windows 10/11 users can either **install DepthAI dependencies** via `Windows Installer `__, or follow :ref:`instructions below `. .. tab:: **Linux** @@ -47,7 +47,7 @@ See documentation below for other platforms or additional information. * - Platform - Instructions - * - Windows 10 + * - Windows 10/11 - :ref:`Platform dependencies ` * - macOS - :ref:`Platform dependencies ` @@ -221,8 +221,7 @@ For openSUSE, available `in this official article `__ and use it to install DepthAI's dependencies do the following: From a80e4631e7055154850cea29c3416d3a743e16a6 Mon Sep 17 00:00:00 2001 From: saching13 Date: Thu, 15 Jun 2023 23:20:25 +0200 Subject: [PATCH 346/385] matched opencv version with depthai repo --- examples/install_requirements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/install_requirements.py b/examples/install_requirements.py index 1bcd555fa..d4a2b7acc 100755 --- a/examples/install_requirements.py +++ b/examples/install_requirements.py @@ -57,7 +57,7 @@ def hasWhitespace(string): if sys.version_info[0] == 3 and sys.version_info[1] == 9: DEPENDENCIES.append('opencv-python!=4.5.4.58') else: - DEPENDENCIES.append('opencv-python') + DEPENDENCIES.append('opencv-contrib-python==4.5.5.62') # same as in depthai requirementx.txt From e89b5518c0e5fd0cd451f9bb4c4de3df198d11e1 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 16 Jun 2023 17:43:56 +0200 Subject: [PATCH 347/385] Started updating bootloader docs --- docs/source/components/bootloader.rst | 73 ++++++++++++++++++--------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/docs/source/components/bootloader.rst b/docs/source/components/bootloader.rst index 44ffb43fe..81d888409 100644 --- a/docs/source/components/bootloader.rst +++ b/docs/source/components/bootloader.rst @@ -3,17 +3,10 @@ Bootloader ========== -DepthAI bootloader is a small program which **handles the booting process**, either by **booting the flashed application**, -or by **initializing the OAK PoE camera** so DepthAI API can connect to it. +DepthAI bootloader is a small program which handles the booting process, either by **booting the flashed application** (see :ref:`Standalone mode`), +or by **initializing the OAK PoE camera** so DepthAI API can connect to it. OAK PoE cameras already come with bootloader flashed at the factory. -To be able to run in :ref:`Standalone mode`, the Depthai bootloader must be first flashed to the devices flash. -This step is required only once. - -Once the device has the bootloader flashed, it will perform the same as before. Running pipelines with a host -connected doesn't require any changes. - -Suggested workflow is to perform as much of development as possible with the host connected as the -iteration cycle is greatly improved. +Bootloader is part of the ``depthai`` library, so to eg. flash the newest bootloader, you should use the newest ``depthai`` library. Device Manager ############## @@ -23,32 +16,66 @@ It can be found at `depthai-python/utilities 9.5MB + applicationName='myAppName' # Optional, so you know which app is flashed afterwards + ) + +Danger Zone +----------- + +.. warning:: + + This section can potentially soft-brick your device, so be careful when using it. + +To prevent soft-bricking, OAK devices (since 2023) have factory bootloader and user bootloader. If user flashes a corrupted user bootloader, it will fallback to using factory bootloader. When updating bootloader, +Device Manager will try to flash the user bootloader first, if flashed (factory) bootloader supports it. If it's not possible, it will flash the factory bootloader. + +* **Update Bootloader** button will flash the newest bootloader to the device. You can select AUTO, USB or NETWORK bootloader. + + * AUTO will select the connection type of bootloader with which the camera is currently connected to. If you are connected via USB (doing factory reset) to an OAK PoE camera, you shouldn't select AUTO, as it will flash USB bootloader. + * USB bootloader will try to boot the application that is stored on flash memory. If it can't find flashed application, it will just behave as normal USB OAK - so it will wait until a host computer initializes the application. + * NETWORK bootloader is used by the OAK PoE cameras, and is flashed at the factory. It handles network initialization so the OAK PoE cameras can be booted through the LAN. +* **Flash Factory Bootloader**: If you want to flash the factory bootloader, you can use this button. It will flash the factory bootloader, even if the user bootloader is already flashed. +* **Factory reset** will erase the whole flash content and re-flash it with only the USB or NETWORK bootloader. Flashed application (pipeline, assets) and bootloader configurations will be lost. +* **Boot into USB recovery mode** will force eg. OAK PoE camera to be available through the USB connector, even if its boot pins are set to PoE booting. It is mostly used by our firmware developers. Boot switches ############# From 36b4c85578da78fe9f22973230b804ab9751f785 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 16 Jun 2023 17:52:15 +0200 Subject: [PATCH 348/385] Updated images --- docs/source/components/bootloader.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/source/components/bootloader.rst b/docs/source/components/bootloader.rst index 81d888409..9a04ad6a9 100644 --- a/docs/source/components/bootloader.rst +++ b/docs/source/components/bootloader.rst @@ -14,7 +14,7 @@ Device Manager ``device_manager.py`` is a Python helper that interfaces with device :ref:`Bootloader` and bootloader configuration. It can be found at `depthai-python/utilities `__. -.. image:: https://user-images.githubusercontent.com/18037362/171629704-0f78f31a-1778-4338-8ac0-bdfb0d2d593f.png +.. image:: https://github.com/luxonis/depthai-python/assets/18037362/b2f067d8-8b4b-4158-9342-ea0dfbe0caf6 About Device ------------ @@ -37,6 +37,10 @@ After you select a device that has bootloader flashed, you can also configure bo After setting some values, you have to click on the ``Flash configuration`` button. You can also ``Clear configuration``, or ``View configuration`` (its JSON). +.. figure:: https://github.com/luxonis/depthai-python/assets/18037362/4bced0ab-92fa-4a73-986f-4a3ba8848940 + + When flashing static IP, make sure to also set the gateway/mask + Applications settings --------------------- From 936ed8577abff8e3aaa55b3e32035b9d0722d9c3 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Fri, 16 Jun 2023 18:51:29 +0200 Subject: [PATCH 349/385] Updated wording --- docs/source/components/bootloader.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/components/bootloader.rst b/docs/source/components/bootloader.rst index 9a04ad6a9..d7a10a9b9 100644 --- a/docs/source/components/bootloader.rst +++ b/docs/source/components/bootloader.rst @@ -11,7 +11,7 @@ Bootloader is part of the ``depthai`` library, so to eg. flash the newest bootlo Device Manager ############## -``device_manager.py`` is a Python helper that interfaces with device :ref:`Bootloader` and bootloader configuration. +``device_manager.py`` is a Python script that interfaces with device :ref:`Bootloader` and bootloader configuration. It can be found at `depthai-python/utilities `__. .. image:: https://github.com/luxonis/depthai-python/assets/18037362/b2f067d8-8b4b-4158-9342-ea0dfbe0caf6 From 23ecd7efb9a6f6fc728c166cc7741a849de927ae Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 20 Jun 2023 11:16:15 +0200 Subject: [PATCH 350/385] Added POE latency docs --- docs/source/tutorials/low-latency.rst | 45 +++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/source/tutorials/low-latency.rst b/docs/source/tutorials/low-latency.rst index 25e7df418..4fa18f94f 100644 --- a/docs/source/tutorials/low-latency.rst +++ b/docs/source/tutorials/low-latency.rst @@ -43,6 +43,51 @@ disabled for these tests (:code:`pipeline.setXLinkChunkSize(0)`). For an example - 246 Mbps - `link `__ +Below are the same tests, but with **OAK PoE** camera, which uses Gigabit ethernet link. The camera was connected directly to the computer, +without any switches or routers in between. Power was supplied via M8 connector. `oak_bandwidth_test.py `__ results: 797 mbps downlink, 264 mbps uplink. +`oak_latency_test.py `__ results: Average: 5.2 ms, Std: 6.2 + +.. list-table:: + :header-rows: 1 + + * - What + - Resolution + - FPS + - FPS set + - Time-to-Host [ms] + - Bandwidth + * - Color (isp) + - 1080P + - 25 + - 25 + - 51 + - 622 Mbps + * - Color (isp) + - 4K + - 8 + - 8 + - 148 + - 530 Mbps + * - Color (isp) + - 4K + - 8.5 + - 10 + - 530 + - 663 Mbps + * - Mono + - 400P + - 90 + - 90 + - Avrg: 12 (Std: 5.0) + - 184 Mbps + * - Mono + - 400P + - 110 + - 110 + - Avrg: 16 (Std: 9.4) + - 225 Mbps + + - **Time-to-Host** is measured time between frame timestamp (:code:`imgFrame.getTimestamp()`) and host timestamp when the frame is received (:code:`dai.Clock.now()`). - **Histogram** shows how much Time-to-Host varies frame to frame. Y axis represents number of frame that occurred at that time while the X axis represents microseconds. - **Bandwidth** is calculated bandwidth required to stream specified frames at specified FPS. From 567dd2f81bb67678a7af8153cfa029ec3ca2d062 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 20 Jun 2023 17:55:17 +0200 Subject: [PATCH 351/385] Update docs on spatial coordinate system (cherry picked from commit ce4f6a99977a39ca5d67d617885a2aeb312509fd) --- docs/source/includes/spatial-coords.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/includes/spatial-coords.rst b/docs/source/includes/spatial-coords.rst index e9c14db0b..01b375aa1 100644 --- a/docs/source/includes/spatial-coords.rst +++ b/docs/source/includes/spatial-coords.rst @@ -1,8 +1,8 @@ Spatial coordinate system ^^^^^^^^^^^^^^^^^^^^^^^^^ -OAK camera uses right-handed (Cartesian) coordinate system for all spatial coordiantes. +OAK camera uses left-handed (Cartesian) coordinate system for all spatial coordiantes. -.. image:: /_static/images/components/spatial-coordinates.png +.. image:: -More details (image source) can be found `here `__. \ No newline at end of file +From the image you can see that on the frame to up the Y is positive, and on the frame to the right the X is positive. \ No newline at end of file From f5115b1566430abfd71ae214f1ba7237adda3780 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Tue, 20 Jun 2023 17:57:49 +0200 Subject: [PATCH 352/385] Updated demo image --- docs/source/includes/spatial-coords.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/includes/spatial-coords.rst b/docs/source/includes/spatial-coords.rst index 01b375aa1..3e721d21d 100644 --- a/docs/source/includes/spatial-coords.rst +++ b/docs/source/includes/spatial-coords.rst @@ -3,6 +3,6 @@ Spatial coordinate system OAK camera uses left-handed (Cartesian) coordinate system for all spatial coordiantes. -.. image:: +.. image:: https://github.com/luxonis/depthai-python/assets/18037362/f9bfaa0c-0286-46c0-910c-77c1337493e1 -From the image you can see that on the frame to up the Y is positive, and on the frame to the right the X is positive. \ No newline at end of file +Middle of the frame is 0,0 in terms of X,Y coordinates. If you go up, Y will increase, and if you go right, X will increase. \ No newline at end of file From c8c5d8e913c4e2738a4e335a00088c05fc49f0f7 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Thu, 22 Jun 2023 11:48:42 +0200 Subject: [PATCH 353/385] Updated image for device manager in docs --- docs/source/components/bootloader.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/components/bootloader.rst b/docs/source/components/bootloader.rst index d7a10a9b9..d9660e713 100644 --- a/docs/source/components/bootloader.rst +++ b/docs/source/components/bootloader.rst @@ -14,7 +14,7 @@ Device Manager ``device_manager.py`` is a Python script that interfaces with device :ref:`Bootloader` and bootloader configuration. It can be found at `depthai-python/utilities `__. -.. image:: https://github.com/luxonis/depthai-python/assets/18037362/b2f067d8-8b4b-4158-9342-ea0dfbe0caf6 +.. image:: https://github.com/luxonis/depthai-python/assets/18037362/1de0d86f-58bf-4979-b7d0-5ca7723db599 About Device ------------ From 953671b805fb50f081f66b83a657582856012173 Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Fri, 23 Jun 2023 15:15:47 +0000 Subject: [PATCH 354/385] Updated Hunter --- CMakeLists.txt | 4 ++-- depthai-core | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ce0fa1b8..2e3c07c66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,8 +23,8 @@ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/generated/Hunter/config.cmake ${final_hun include("cmake/HunterGate.cmake") HunterGate( - URL "https://github.com/cpp-pm/hunter/archive/v0.23.322.tar.gz" - SHA1 "cb0ea1f74f4a2c49a807de34885743495fccccbe" + URL "https://github.com/cpp-pm/hunter/archive/v0.24.18.tar.gz" + SHA1 "1292e4d661e1770d6d6ca08c12c07cf34a0bf718" FILEPATH ${CMAKE_CURRENT_BINARY_DIR}/generated/Hunter/config.cmake # Combined config ) diff --git a/depthai-core b/depthai-core index 3ad3351ba..b6c9053dd 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 3ad3351ba2ad43992dbca4667e43126cfcc3fa04 +Subproject commit b6c9053dd674bddfaf90b4d51865eaabb866989f From 3ef814f8aa822c2520b46a97816829352649f37c Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 26 Jun 2023 23:19:34 +0300 Subject: [PATCH 355/385] RVC3 FW: Fsync improvements --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index b6c9053dd..861945280 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit b6c9053dd674bddfaf90b4d51865eaabb866989f +Subproject commit 861945280558042e3a5726e39fc047e8101cc47c From c35a1179d61789e1462818fde8946d13784861d9 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sat, 1 Jul 2023 11:27:18 +0200 Subject: [PATCH 356/385] Added ToF docs --- .../source/components/messages/tof_config.rst | 31 +++++ docs/source/components/nodes/tof.rst | 110 ++++++++++++++++++ docs/source/samples/ToF/tof_depth.rst | 45 +++++++ docs/source/tutorials/code_samples.rst | 5 + examples/ToF/tof_depth.py | 52 +++++++++ 5 files changed, 243 insertions(+) create mode 100644 docs/source/components/messages/tof_config.rst create mode 100644 docs/source/components/nodes/tof.rst create mode 100644 docs/source/samples/ToF/tof_depth.rst create mode 100644 examples/ToF/tof_depth.py diff --git a/docs/source/components/messages/tof_config.rst b/docs/source/components/messages/tof_config.rst new file mode 100644 index 000000000..1681c1df4 --- /dev/null +++ b/docs/source/components/messages/tof_config.rst @@ -0,0 +1,31 @@ +ToFConfig +========= + +This message is used to configure the :ref:`ToF` node. + +Examples of functionality +######################### + +- :ref:`ToF depth` + +Reference +######### + +.. tabs:: + + .. tab:: Python + + .. autoclass:: depthai.ToFConfig + :members: + :inherited-members: + :noindex: + + .. tab:: C++ + + .. doxygenclass:: dai::ToFConfig + :project: depthai-core + :members: + :private-members: + :undoc-members: + +.. include:: ../../includes/footer-short.rst diff --git a/docs/source/components/nodes/tof.rst b/docs/source/components/nodes/tof.rst new file mode 100644 index 000000000..149cb8c50 --- /dev/null +++ b/docs/source/components/nodes/tof.rst @@ -0,0 +1,110 @@ +ToF +=== + +**ToF node** is used for converting the raw data from the ToF sensor into a depth map. Currently, these 2 products contain a ToF sensor: + +- `OAK-D SR PoE `__ - integrated 33D ToF sensor, together with a stereo camera pair +- `OAK-FFC ToF 33D `__ - standalone FFC module with a 33D ToF sensor + +ToF's ``depth`` output can be used instead of :ref:`StereoDepth`'s - so you can link ToF.depth to :ref:`MobileNetSpatialDetectionNetwork`/:ref:`YoloSpatialDetectionNetwork` or +:ref:`SpatialLocationCalculator` directly. + +How to place it +############### + +.. tabs:: + + .. code-tab:: py + + pipeline = dai.Pipeline() + warp = pipeline.create(dai.node.ToF) + + .. code-tab:: c++ + + dai::Pipeline pipeline; + auto warp = pipeline.create(); + +Inputs and Outputs +################## + +.. code-block:: + + ┌───────────┐ depth + inputConfig | ├────────► + ───────────►│ | amplitude + input | ToF ├────────► + ───────────►│ │ error + │ ├────────► + └───────────┘ + +**Message types** + +- ``inputConfig`` - :ref:`ToFConfig` +- ``input`` - :ref:`ImgFrame` +- ``depth`` - :ref:`ImgFrame` +- ``amplitude`` - :ref:`ImgFrame` +- ``error`` - :ref:`ImgFrame` + +Usage +##### + +.. tabs:: + + .. code-tab:: py + + pipeline = dai.Pipeline() + + tof_cam = pipeline.create(dai.node.Camera) + # We assume the ToF camera sensor is on port CAM_A + tof_cam.setBoardSocket(dai.CameraBoardSocket.CAM_A) + + tof = pipeline.create(dai.node.ToF) + # ToF node converts raw sensor frames into depth + tof_cam.raw.link(tof.input) + + # Send ToF depth output to the host, or perhaps to SLC / Spatial Detection Network + tof.depth.link(xout.input) + + .. code-tab:: c++ + + dai::Pipeline pipeline; + + auto tofCam = pipeline.create(); + // We assume the ToF camera sensor is on port CAM_A + tofCam->setBoardSocket(dai::CameraBoardSocket::AUTO); + + auto tof = pipeline.create(); + // ToF node converts raw sensor frames into depth + tofCam->raw.link(tof->input); + + auto xout = pipeline.create(); + xout->setStreamName("depth"); + // Send ToF depth output to the host + tof->depth.link(xout->input); + +Examples of functionality +######################### + +- :ref:`ToF depth` + +Reference +######### + +.. tabs:: + + .. tab:: Python + + .. autoclass:: depthai.node.ToF + :members: + :inherited-members: + :noindex: + + .. tab:: C++ + + .. doxygenclass:: dai::node::ToF + :project: depthai-core + :members: + :private-members: + :undoc-members: + +.. include:: ../../includes/footer-short.rst diff --git a/docs/source/samples/ToF/tof_depth.rst b/docs/source/samples/ToF/tof_depth.rst new file mode 100644 index 000000000..ac86656ec --- /dev/null +++ b/docs/source/samples/ToF/tof_depth.rst @@ -0,0 +1,45 @@ +ToF depth +========= + +This is a sample code that showcases how to use the ToF sensor. The :ref:`ToF node ` converts raw data from the ToF sensor into a depth map. + +Demo +#### + +This demo was recorded using the `OAK-D SR PoE `__, that's why we selected CAM_A port +on the ToF sensor. + +.. raw:: html + +
+ +
+ +Setup +##### + +.. include:: /includes/install_from_pypi.rst + +Source code +########### + +.. tabs:: + + .. tab:: Python + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../examples/ToF/tof_depth.py + :language: python + :linenos: + + .. tab:: C++ + + .. + Also `available on GitHub `__ + + .. literalinclude:: ../../../../depthai-core/examples/ToF/tof_depth.cpp + :language: cpp + :linenos: + +.. include:: /includes/footer-short.rst diff --git a/docs/source/tutorials/code_samples.rst b/docs/source/tutorials/code_samples.rst index 8d1af2a57..cf099f596 100644 --- a/docs/source/tutorials/code_samples.rst +++ b/docs/source/tutorials/code_samples.rst @@ -23,6 +23,7 @@ Code Samples ../samples/SpatialDetection/* ../samples/StereoDepth/* ../samples/SystemLogger/* + ../samples/ToF/* ../samples/VideoEncoder/* ../samples/Warp/* ../samples/Yolo/* @@ -152,6 +153,10 @@ are presented with code. - :ref:`System information` - Displays device system information (memory/cpu usage, temperature) +.. rubtic:: ToF + +- :ref:`ToF depth` - Displays colorized ToF depth frames + .. rubric:: VideoEncoder - :ref:`Disparity encoding` - Encodes stereo disparity into :code:`.mjpeg` diff --git a/examples/ToF/tof_depth.py b/examples/ToF/tof_depth.py new file mode 100644 index 000000000..5a08b5d0c --- /dev/null +++ b/examples/ToF/tof_depth.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +import cv2 +import depthai as dai +import numpy as np + +pipeline = dai.Pipeline() + +cam_a = pipeline.create(dai.node.Camera) +# We assume the ToF camera sensor is on port CAM_A +cam_a.setBoardSocket(dai.CameraBoardSocket.CAM_A) + +tof = pipeline.create(dai.node.ToF) + +# Configure the ToF node +tofConfig = tof.initialConfig.get() +# tofConfig.depthParams.freqModUsed = dai.RawToFConfig.DepthParams.TypeFMod.MIN +tofConfig.depthParams.freqModUsed = dai.RawToFConfig.DepthParams.TypeFMod.MAX +tofConfig.depthParams.avgPhaseShuffle = False +tofConfig.depthParams.minimumAmplitude = 3.0 +tof.initialConfig.set(tofConfig) +# Link the ToF sensor to the ToF node +cam_a.raw.link(tof.input) + +xout = pipeline.create(dai.node.XLinkOut) +xout.setStreamName("depth") +tof.depth.link(xout.input) + +# Connect to device and start pipeline +with dai.Device(pipeline) as device: + print('Connected cameras:', device.getConnectedCameraFeatures()) + q = device.getOutputQueue(name="depth") + + while True: + imgFrame = q.get() # blocking call, will wait until a new data has arrived + depth_map = imgFrame.getFrame() + + # Colorize the depth frame to jet colormap + depth_downscaled = depth_map[::4] + non_zero_depth = depth_downscaled[depth_downscaled != 0] # Remove invalid depth values + if len(non_zero_depth) == 0: + min_depth, max_depth = 0, 0 + else: + min_depth = np.percentile(non_zero_depth, 3) + max_depth = np.percentile(non_zero_depth, 97) + depth_colorized = np.interp(depth_map, (min_depth, max_depth), (0, 255)).astype(np.uint8) + depth_colorized = cv2.applyColorMap(depth_colorized, cv2.COLORMAP_JET) + + cv2.imshow("Colorized depth", depth_colorized) + + if cv2.waitKey(1) == ord('q'): + break From d317f2077833021d7b01056ed6b93cab30ec7748 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Sat, 1 Jul 2023 14:50:52 +0200 Subject: [PATCH 357/385] Fix typo --- docs/source/tutorials/code_samples.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/tutorials/code_samples.rst b/docs/source/tutorials/code_samples.rst index cf099f596..a523682d1 100644 --- a/docs/source/tutorials/code_samples.rst +++ b/docs/source/tutorials/code_samples.rst @@ -153,7 +153,7 @@ are presented with code. - :ref:`System information` - Displays device system information (memory/cpu usage, temperature) -.. rubtic:: ToF +.. rubric:: ToF - :ref:`ToF depth` - Displays colorized ToF depth frames From 3e0375083258b255da47d32cdf4ac4663192799e Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 3 Jul 2023 12:20:40 +0200 Subject: [PATCH 358/385] Alternate between dot projector and illumination led docs --- .../MonoCamera/mono_preview_alternate_pro.rst | 48 +++++++++++++++++++ docs/source/tutorials/code_samples.rst | 1 + 2 files changed, 49 insertions(+) create mode 100644 docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst diff --git a/docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst b/docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst new file mode 100644 index 000000000..3e11fe44e --- /dev/null +++ b/docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst @@ -0,0 +1,48 @@ +Mono preview - Alternate between dot projector and illumination LED +=================================================================== + +This example will alternate between the IR illumination LED and IR dot projector. By default, example script will run +both left and right monochrome camera sensors at 30FPS, and it will switch between the IR LED and dot projector +every frame - meaning you will get LED-illuminated frames at 15FPS, and dot projector-illuminated frames at 15FPS. + +LED-illuminated frames can be used for your `AI vision tasks `__ +and CV algorithms (eg. :ref:`Feature Tracker`) in low-light environments. Dot projector-illuminated frames are used for `active stereo depth `__. + +Demo +#### + +.. raw:: html + +
+ +
+ +Setup +##### + +.. include:: /includes/install_from_pypi.rst + +Source code +########### + +.. tabs:: + + .. tab:: Python + + Also `available on GitHub `__ + + .. literalinclude:: ../../../../examples/MonoCamera/mono_preview_alternate_pro.py + :language: python + :linenos: + + .. tab:: C++ + + Not yet implemented. + .. + Also `available on GitHub `__ + + .. literalinclude:: ../../../../depthai-core/examples/MonoCamera/mono_preview_alternate_pro.cpp + :language: cpp + :linenos: + +.. include:: /includes/footer-short.rst diff --git a/docs/source/tutorials/code_samples.rst b/docs/source/tutorials/code_samples.rst index a523682d1..193a04370 100644 --- a/docs/source/tutorials/code_samples.rst +++ b/docs/source/tutorials/code_samples.rst @@ -104,6 +104,7 @@ are presented with code. - :ref:`Mono Preview` - Displays right/left mono cameras - :ref:`Mono Camera Control` - Demonstrates how to control the mono camera (crop, exposure, sensitivity) from the host +- :ref:`Mono preview - Alternate between dot projector and illumination LED` on OAK Pro devices - :ref:`Mono Full Resolution Saver` - Saves mono (720P) images to the host (:code:`.png`) .. rubric:: NeuralNetwork From 1b2f9e89f97bfe4d1d55a770e931b05a8d947ef2 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 3 Jul 2023 12:21:46 +0200 Subject: [PATCH 359/385] Update docs --- docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst b/docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst index 3e11fe44e..86fbec04e 100644 --- a/docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst +++ b/docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst @@ -17,6 +17,9 @@ Demo +On the video, we disabled both projector and LED for about a second, just to demonstrate how the scene looks +in almost-complete darkness. + Setup ##### From 5a6ec6db0518c3a5c69dc9d2e350e35ce72b570e Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 3 Jul 2023 12:57:20 +0200 Subject: [PATCH 360/385] Fix docs building --- docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst b/docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst index 86fbec04e..7212d0476 100644 --- a/docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst +++ b/docs/source/samples/MonoCamera/mono_preview_alternate_pro.rst @@ -41,7 +41,8 @@ Source code .. tab:: C++ Not yet implemented. - .. + +.. Also `available on GitHub `__ .. literalinclude:: ../../../../depthai-core/examples/MonoCamera/mono_preview_alternate_pro.cpp From be8427e9a247cde350512ea7ba1870d68ae86c0b Mon Sep 17 00:00:00 2001 From: Erol444 Date: Mon, 3 Jul 2023 14:43:57 +0200 Subject: [PATCH 361/385] Max IMU frequencies docs --- docs/source/components/nodes/imu.rst | 48 +++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/docs/source/components/nodes/imu.rst b/docs/source/components/nodes/imu.rst index 83f356777..3859ecd67 100644 --- a/docs/source/components/nodes/imu.rst +++ b/docs/source/components/nodes/imu.rst @@ -48,7 +48,6 @@ Limitations ########### - For BNO086, gyroscope frequency above 400Hz can produce some jitter from time to time due to sensor HW limitation. -- **Maximum frequencies**: 500 Hz raw accelerometer, 1000 Hz raw gyroscope values individually, and 500Hz combined (synced) output. You can obtain the combined synced 500Hz output with :code:`imu.enableIMUSensor([dai.IMUSensor.ACCELEROMETER_RAW, dai.IMUSensor.GYROSCOPE_RAW], 500)`. IMU sensor frequencies ###################### @@ -58,14 +57,53 @@ for BNO086, maximum frequency for gyroscope is 1000Hz, but up to 400Hz is stable **BNO086:** -- Accelerometer: 100Hz, 200Hz, 400Hz -- Gyroscope: 125Hz, 250Hz, 400Hz +Note that BNO IMU "rounds up" the input frequency to the next available frequency. For example, if you set the frequency to 101 it will round it to 200Hz. + +- Accelerometer: 15Hz, 31Hz, 62Hz, 125Hz, 250Hz 500Hz +- Gyroscope: 25Hz, 33Hz, 50Hz, 100Hz, 200Hz, 400Hz - Magnetometer: 100Hz +**BNO086 max frequency:** + +.. list-table:: + :header-rows: 1 + + * - BNO086 Sensor + - Max Frequency + * - ``ACCELEROMETER_RAW`` + - 512 Hz + * - ``ACCELEROMETER`` + - 512 Hz + * - ``LINEAR_ACCELERATION`` + - 400 Hz + * - ``GRAVITY`` + - 400 Hz + * - ``GYROSCOPE_RAW`` + - 1000 Hz + * - ``GYROSCOPE_CALIBRATED`` / ``GYROSCOPE_UNCALIBRATED`` + - 100 Hz + * - ``MAGNETOMETER_RAW`` + - 100 Hz + * - ``MAGNETOMETER_CALIBRATED`` / ``MAGNETOMETER_UNCALIBRATED`` + - 100 Hz + * - ``ROTATION_VECTOR`` + - 400 Hz + * - ``GAME_ROTATION_VECTOR`` + - 400 Hz + * - ``GEOMAGNETIC_ROTATION_VECTOR`` + - 100 Hz + * - ``ARVR_STABILIZED_ROTATION_VECTOR`` + - 100 Hz + * - ``ARVR_STABILIZED_GAME_ROTATION_VECTOR`` + - 100 Hz + **BMI270:** -- Accelerometer: 25Hz, 50Hz, 100Hz, 200Hz, 400Hz -- Gyroscope: 25Hz, 50Hz, 100Hz, 200Hz, 400Hz +Note that BMI279 "rounds down" the input frequency to the next available frequency. For example, if you set the frequency to 99 it will round it to 50Hz. +Additionally, the current max frequency of ~250 Hz is set when the input is >400Hz. + +- Accelerometer: 25Hz, 50Hz, 100Hz, 200Hz, 250Hz +- Gyroscope: 25Hz, 50Hz, 100Hz, 200Hz, 250Hz Usage ##### From bdca5b1f3762ab0111962b13e0789d2ab14b57d8 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Tue, 4 Jul 2023 17:47:29 +0000 Subject: [PATCH 362/385] Update core for clean exit --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 861945280..ce9b8a2f5 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 861945280558042e3a5726e39fc047e8101cc47c +Subproject commit ce9b8a2f5165589072026d0d84ee02206da62428 From 841c7392635fe4127e46019e6ce95693682793f6 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Tue, 4 Jul 2023 18:08:16 +0000 Subject: [PATCH 363/385] Update FW --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index ce9b8a2f5..f91ea0e68 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit ce9b8a2f5165589072026d0d84ee02206da62428 +Subproject commit f91ea0e68eafba31b993bc50c3b3e8c320828d81 From 9c0c8b47fbc90ae5a88223922a40df64d501ead2 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Wed, 5 Jul 2023 08:52:05 +0000 Subject: [PATCH 364/385] Update FW --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index f91ea0e68..ea15fea48 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit f91ea0e68eafba31b993bc50c3b3e8c320828d81 +Subproject commit ea15fea481f2586f17ed662db9e57dc7fdff6b94 From 55c7bcdd7a33e9c223dbd6ace7f0d4435c734643 Mon Sep 17 00:00:00 2001 From: Florin Buica Date: Thu, 6 Jul 2023 17:11:06 +0300 Subject: [PATCH 365/385] update rvc3 fw --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index ea15fea48..04102e8be 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit ea15fea481f2586f17ed662db9e57dc7fdff6b94 +Subproject commit 04102e8bec601d4c2b7a3900bb1155661dbc037a From e815fb9ad789b15cef84650c630ababfa284834c Mon Sep 17 00:00:00 2001 From: SzabolcsGergely Date: Mon, 10 Jul 2023 16:23:28 +0300 Subject: [PATCH 366/385] FW: timesync between device and host --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 04102e8be..ba8b3739a 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 04102e8bec601d4c2b7a3900bb1155661dbc037a +Subproject commit ba8b3739a45d5d91561883f3d8212d70a73c21fc From 11a40ed426e44610d40f1a328eb6a054d9af313f Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Mon, 10 Jul 2023 16:20:50 +0000 Subject: [PATCH 367/385] [RVC3 FW] Fix a detection parser bug --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index ba8b3739a..0ee1ebf76 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit ba8b3739a45d5d91561883f3d8212d70a73c21fc +Subproject commit 0ee1ebf76bfd8181b701dfcf781fb6402b26f0ff From 1d19a6cab66e824b4de2b08d443a74c7e8cbd461 Mon Sep 17 00:00:00 2001 From: Erol444 Date: Thu, 13 Jul 2023 12:26:14 +0200 Subject: [PATCH 368/385] Added camera exposure limit docs --- docs/source/tutorials/image_quality.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/tutorials/image_quality.rst b/docs/source/tutorials/image_quality.rst index 71065016f..5542badda 100644 --- a/docs/source/tutorials/image_quality.rst +++ b/docs/source/tutorials/image_quality.rst @@ -73,6 +73,7 @@ To tune your own camera sensors, one would need Intel's software, for which a li - **Mono tuning for low-light environments** `here `__. This allows auto-exposure to go up to 200ms (otherwise limited with default tuning to 33ms). For 200ms auto-exposure, you also need to limit the FPS (:code:`monoRight.setFps(5)`) - **Color tuning for low-light environments** `here `__. Comparison below. This allows auto-exposure to go up to 100ms (otherwise limited with default tuning to 33ms). For 200ms auto-exposure, you also need to limit the FPS (:code:`rgbCam.setFps(10)`). *Known limitation*: flicker can be seen with auto-exposure over 33ms, it is caused by auto-focus working in continuous mode. A workaround is to change from CONTINUOUS_VIDEO (default) to AUTO (focusing only once at init, and on further focus trigger commands): :code:`camRgb.initialControl.setAutoFocusMode(dai.CameraControl.AutoFocusMode.AUTO)` - **OV9782 Wide FOV color tuning for sunlight environments** `here `__. Fixes lens color filtering on direct sunglight, see `blog post here `__. It also improves LSC (Lens Shading Correction). Currently doesn't work for OV9282, so when used on eg. Series 2 OAK with Wide FOV cams, mono cameras shouldn't be enabled. +- **Camera exposure limit**: `max 500us `__, `max 8300us `__. These tuning blobs will limit the maximum exposure time, and instead start increasing ISO (sensitivity) after max exposure time is reached. This is a useful approach to reduce the :ref:`Motion blur`. .. image:: https://user-images.githubusercontent.com/18037362/149826169-3b92901d-3367-460b-afbf-c33d8dc9d118.jpeg @@ -91,7 +92,7 @@ cause blurry images (`docs here Date: Mon, 17 Jul 2023 18:10:58 +0000 Subject: [PATCH 369/385] [RVC3 FW] Update firmware --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 0ee1ebf76..61fb919a9 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 0ee1ebf76bfd8181b701dfcf781fb6402b26f0ff +Subproject commit 61fb919a9dd01cdce0f293e51a82f1c4f93f4469 From 486394d20b71da9d7fb833c63a88d1f0c3435f46 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Fri, 21 Jul 2023 15:14:38 +0000 Subject: [PATCH 370/385] Update core --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 61fb919a9..a27ce0671 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 61fb919a9dd01cdce0f293e51a82f1c4f93f4469 +Subproject commit a27ce067177492b7f32437ffcc8b034ec3aeb0d6 From 2b78b83040a7f7e3bb4e4dc0ebb4b63d791329ba Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Fri, 21 Jul 2023 17:58:14 +0000 Subject: [PATCH 371/385] Update core --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index e1854cb58..4ee3ead3c 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit e1854cb580c7a505d2bf44440f789a9f0767fc76 +Subproject commit 4ee3ead3ce213e38a7a37f07d711c6b617898cd8 From 02d2b69a65decea4fe06409d30245ec5f81328e3 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Fri, 21 Jul 2023 21:45:01 +0000 Subject: [PATCH 372/385] Fix duplicated bindings --- src/DatatypeBindings.cpp | 2 -- src/pipeline/node/ToFBindings.cpp | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/DatatypeBindings.cpp b/src/DatatypeBindings.cpp index 601e5eed5..50cb4e0c5 100644 --- a/src/DatatypeBindings.cpp +++ b/src/DatatypeBindings.cpp @@ -23,7 +23,6 @@ void bind_stereodepthconfig(pybind11::module& m, void* pCallstack); void bind_systeminformation(pybind11::module& m, void* pCallstack); void bind_systeminformationS3(pybind11::module& m, void* pCallstack); void bind_trackedfeatures(pybind11::module& m, void* pCallstack); -void bind_tofconfig(pybind11::module& m, void* pCallstack); void bind_tracklets(pybind11::module& m, void* pCallstack); void bind_benchmarkreport(pybind11::module& m, void* pCallstack); @@ -52,7 +51,6 @@ void DatatypeBindings::addToCallstack(std::deque& callstack) { callstack.push_front(bind_systeminformation); callstack.push_front(bind_systeminformationS3); callstack.push_front(bind_trackedfeatures); - callstack.push_front(bind_tofconfig); callstack.push_front(bind_tracklets); callstack.push_front(bind_benchmarkreport); } diff --git a/src/pipeline/node/ToFBindings.cpp b/src/pipeline/node/ToFBindings.cpp index 97047af01..94497bcde 100644 --- a/src/pipeline/node/ToFBindings.cpp +++ b/src/pipeline/node/ToFBindings.cpp @@ -5,6 +5,7 @@ #include "depthai/pipeline/Node.hpp" #include "depthai/pipeline/node/ToF.hpp" + void bind_tof(pybind11::module& m, void* pCallstack){ using namespace dai; From 61d79385e4018cbfdf3a87108f66cf941a068c0f Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Sat, 22 Jul 2023 00:15:33 +0200 Subject: [PATCH 373/385] Add missing ToF bindings --- depthai-core | 2 +- src/pipeline/datatype/ToFConfigBindings.cpp | 30 ++++++++++++++++++--- src/pipeline/node/ToFBindings.cpp | 18 +++++++------ 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/depthai-core b/depthai-core index 4ee3ead3c..184f5484e 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 4ee3ead3ce213e38a7a37f07d711c6b617898cd8 +Subproject commit 184f5484e4a41ffa8ee93f7fd03e76c66e218392 diff --git a/src/pipeline/datatype/ToFConfigBindings.cpp b/src/pipeline/datatype/ToFConfigBindings.cpp index 177d50f4e..a51fae8ac 100644 --- a/src/pipeline/datatype/ToFConfigBindings.cpp +++ b/src/pipeline/datatype/ToFConfigBindings.cpp @@ -17,7 +17,9 @@ void bind_tofconfig(pybind11::module& m, void* pCallstack){ using namespace dai; py::class_> rawToFConfig(m, "RawToFConfig", DOC(dai, RawToFConfig)); - py::class_> tofConfig(m, "ToFConfig", DOC(dai, ToFConfig)); + py::class_ depthParams(rawToFConfig, "DepthParams", DOC(dai, RawToFConfig, DepthParams)); + py::enum_ depthParamsTypeFMod(depthParams, "TypeFMod", DOC(dai, RawToFConfig, DepthParams, TypeFMod)); + py::class_> toFConfig(m, "ToFConfig", DOC(dai, ToFConfig)); /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// @@ -35,16 +37,38 @@ void bind_tofconfig(pybind11::module& m, void* pCallstack){ // Metadata / raw rawToFConfig .def(py::init<>()) - .def_readwrite("dummy", &RawToFConfig::dummy, DOC(dai, RawToFConfig, dummy)) + .def_readwrite("depthParams", &RawToFConfig::depthParams, DOC(dai, RawToFConfig, depthParams)) + ; + + depthParamsTypeFMod + .value("ALL", RawToFConfig::DepthParams::TypeFMod::F_MOD_ALL) + .value("MIN", RawToFConfig::DepthParams::TypeFMod::F_MOD_MIN) + .value("MAX", RawToFConfig::DepthParams::TypeFMod::F_MOD_MAX) + ; + + depthParams + .def(py::init<>()) + .def_readwrite("enable", &RawToFConfig::DepthParams::enable, DOC(dai, RawToFConfig, DepthParams, enable)) + .def_readwrite("freqModUsed", &RawToFConfig::DepthParams::freqModUsed, DOC(dai, RawToFConfig, DepthParams, freqModUsed)) + .def_readwrite("avgPhaseShuffle", &RawToFConfig::DepthParams::avgPhaseShuffle, DOC(dai, RawToFConfig, DepthParams, avgPhaseShuffle)) + .def_readwrite("minimumAmplitude", &RawToFConfig::DepthParams::minimumAmplitude, DOC(dai, RawToFConfig, DepthParams, minimumAmplitude)) ; // Message - tofConfig + toFConfig .def(py::init<>()) .def(py::init>()) + + .def("setDepthParams", static_cast(&ToFConfig::setDepthParams), py::arg("config"), DOC(dai, ToFConfig, setDepthParams)) + .def("setFreqModUsed", static_cast(&ToFConfig::setFreqModUsed), DOC(dai, ToFConfig, setFreqModUsed)) + .def("setAvgPhaseShuffle", &ToFConfig::setAvgPhaseShuffle, DOC(dai, ToFConfig, setAvgPhaseShuffle)) + .def("setMinAmplitude", &ToFConfig::setMinAmplitude, DOC(dai, ToFConfig, setMinAmplitude)) .def("set", &ToFConfig::set, py::arg("config"), DOC(dai, ToFConfig, set)) .def("get", &ToFConfig::get, DOC(dai, ToFConfig, get)) ; + // add aliases + m.attr("ToFConfig").attr("DepthParams") = m.attr("RawToFConfig").attr("DepthParams"); + } diff --git a/src/pipeline/node/ToFBindings.cpp b/src/pipeline/node/ToFBindings.cpp index 94497bcde..e641f9e98 100644 --- a/src/pipeline/node/ToFBindings.cpp +++ b/src/pipeline/node/ToFBindings.cpp @@ -28,19 +28,21 @@ void bind_tof(pybind11::module& m, void* pCallstack){ /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// - // ToF properties + // Properties tofProperties .def_readwrite("initialConfig", &ToFProperties::initialConfig, DOC(dai, ToFProperties, initialConfig)) ; - // ToF Node + // Node tof - .def_readonly("inputConfig", &ToF::inputConfig, DOC(dai, node, ToF, inputConfig)) - .def_readonly("inputRaw", &ToF::inputRaw, DOC(dai, node, ToF, inputRaw)) - .def_readonly("depth", &ToF::depth, DOC(dai, node, ToF, depth)) - .def_readonly("passthroughInputRaw", &ToF::passthroughInputRaw, DOC(dai, node, ToF, passthroughInputRaw)) - .def_readonly("initialConfig", &ToF::initialConfig, DOC(dai, node, ToF, initialConfig)) - ; + .def_readonly("inputConfig", &ToF::inputConfig, DOC(dai, node, ToF, inputConfig), DOC(dai, node, ToF, inputConfig)) + .def_readonly("input", &ToF::input, DOC(dai, node, ToF, input), DOC(dai, node, ToF, input)) + .def_readonly("depth", &ToF::depth, DOC(dai, node, ToF, depth), DOC(dai, node, ToF, depth)) + .def_readonly("amplitude", &ToF::amplitude, DOC(dai, node, ToF, amplitude), DOC(dai, node, ToF, amplitude)) + .def_readonly("error", &ToF::error, DOC(dai, node, ToF, error), DOC(dai, node, ToF, error)) + .def_readonly("initialConfig", &ToF::initialConfig, DOC(dai, node, ToF, initialConfig), DOC(dai, node, ToF, initialConfig)) + ; + // ALIAS daiNodeModule.attr("ToF").attr("Properties") = tofProperties; } From aeed4c184ab6257c99600ddffa72ccb505a5b003 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Mon, 24 Jul 2023 20:13:18 +0200 Subject: [PATCH 374/385] Add missing API to detection parser --- depthai-core | 2 +- src/pipeline/node/DetectionParserBindings.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/depthai-core b/depthai-core index 184f5484e..4fc7b0814 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 184f5484e4a41ffa8ee93f7fd03e76c66e218392 +Subproject commit 4fc7b08146cdcfc93fa659dc70f61e2e28dc5e03 diff --git a/src/pipeline/node/DetectionParserBindings.cpp b/src/pipeline/node/DetectionParserBindings.cpp index 8d6dfb0b4..95e377495 100644 --- a/src/pipeline/node/DetectionParserBindings.cpp +++ b/src/pipeline/node/DetectionParserBindings.cpp @@ -36,9 +36,11 @@ void bind_detectionparser(pybind11::module& m, void* pCallstack){ detectionParser .def_readonly("input", &DetectionParser::input, DOC(dai, node, DetectionParser, input)) .def_readonly("out", &DetectionParser::out, DOC(dai, node, DetectionParser, out)) + .def("setBlobPath", &DetectionParser::setBlobPath, py::arg("path"), DOC(dai, node, DetectionParser, setBlobPath)) .def("setNumFramesPool", &DetectionParser::setNumFramesPool, py::arg("numFramesPool"), DOC(dai, node, DetectionParser, setNumFramesPool)) .def("getNumFramesPool", &DetectionParser::getNumFramesPool, DOC(dai, node, DetectionParser, getNumFramesPool)) - .def("setBlob", &DetectionParser::setBlob, py::arg("blob"), DOC(dai, node, DetectionParser, setBlob)) + .def("setBlob", py::overload_cast(&DetectionParser::setBlob), py::arg("blob"), DOC(dai, node, DetectionParser, setBlob)) + .def("setBlob", py::overload_cast(&DetectionParser::setBlob), py::arg("path"), DOC(dai, node, DetectionParser, setBlob, 2)) .def("setInputImageSize", static_cast(&DetectionParser::setInputImageSize), py::arg("width"), py::arg("height"), DOC(dai, node, DetectionParser, setInputImageSize)) .def("setInputImageSize", static_cast)>(&DetectionParser::setInputImageSize), py::arg("size"), DOC(dai, node, DetectionParser, setInputImageSize, 2)) .def("setNNFamily", &DetectionParser::setNNFamily, py::arg("type"), DOC(dai, node, DetectionParser, setNNFamily)) From 80c0213f3cf04402dbd3592c863935ec4d04a2b5 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Mon, 24 Jul 2023 22:06:32 +0200 Subject: [PATCH 375/385] Update core --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 4fc7b0814..018cfda21 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 4fc7b08146cdcfc93fa659dc70f61e2e28dc5e03 +Subproject commit 018cfda21d3aa314dbfa69ab3aefbd9076df03e3 From 3d753199fa580627451a47cab0ecc6b77cc6ab78 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Mon, 24 Jul 2023 22:32:00 +0200 Subject: [PATCH 376/385] Fix the build --- docs/requirements_mkdoc.txt | 1 + pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/requirements_mkdoc.txt b/docs/requirements_mkdoc.txt index 700047e08..212071f2c 100644 --- a/docs/requirements_mkdoc.txt +++ b/docs/requirements_mkdoc.txt @@ -1 +1,2 @@ git+https://github.com/luxonis/pybind11_mkdoc.git@59746f8d1645c9f00ebfb534186334d0154b5bd6 +numpy # Needed because of xtensor-python \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index c679e23fb..5626df1b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,2 @@ [build-system] -requires = ["setuptools", "wheel", "mypy", "cmake==3.25"] +requires = ["setuptools", "wheel", "mypy", "numpy", "cmake==3.25"] From 41581ca7ee9405f8eab9596062b0a09e00cc1ff6 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Mon, 24 Jul 2023 23:27:23 +0200 Subject: [PATCH 377/385] Fix device discovery on RVC3 --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 018cfda21..0f9201886 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 018cfda21d3aa314dbfa69ab3aefbd9076df03e3 +Subproject commit 0f92018863e233bba071156a97dcf0619433b132 From acc20f7b948c5da3e657e813e78a2b1e4b31a65a Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Tue, 25 Jul 2023 01:00:36 +0200 Subject: [PATCH 378/385] Update RVC3 FW --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 0f9201886..3ffb44a6e 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 0f92018863e233bba071156a97dcf0619433b132 +Subproject commit 3ffb44a6e5382e532d84979af27a900b38d2414f From 66f79e821e3395c798fe9cf853539cd9990ccd33 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Fri, 28 Jul 2023 15:28:58 +0300 Subject: [PATCH 379/385] Update core with clang-format --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 3ffb44a6e..0fca3b449 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 3ffb44a6e5382e532d84979af27a900b38d2414f +Subproject commit 0fca3b4498111f8c77049be2e88267ad76e27232 From 88cdaecc728c95279dceee29eef670f41056d070 Mon Sep 17 00:00:00 2001 From: moratom <47612463+moratom@users.noreply.github.com> Date: Sat, 29 Jul 2023 20:02:05 +0300 Subject: [PATCH 380/385] Update pyproject.toml Fix arm builds --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5626df1b4..bfedcbc90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,2 @@ [build-system] -requires = ["setuptools", "wheel", "mypy", "numpy", "cmake==3.25"] +requires = ["setuptools", "wheel", "mypy<=1.3.0", "cmake==3.25"] From 689cab3f5d451299769a6a736af1d8d730fdffa3 Mon Sep 17 00:00:00 2001 From: moratom <47612463+moratom@users.noreply.github.com> Date: Sat, 29 Jul 2023 20:33:41 +0300 Subject: [PATCH 381/385] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bfedcbc90..fc198666c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,2 @@ [build-system] -requires = ["setuptools", "wheel", "mypy<=1.3.0", "cmake==3.25"] +requires = ["setuptools", "wheel", "mypy<=1.3.0", "numpy", "cmake==3.25"] From 3861e61dd0b7bc2c2e7a2be6f695135a41626c7b Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Mon, 31 Jul 2023 14:02:37 +0000 Subject: [PATCH 382/385] Revert cam test to before merge --- utilities/cam_test.py | 167 +++++++++++++----------------------------- 1 file changed, 50 insertions(+), 117 deletions(-) diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 27dd39a0b..3b7b94dca 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -30,8 +30,6 @@ For the 'Select control: ...' options, use these keys to modify the value: '-' or '_' to decrease '+' or '=' to increase - -'/' to toggle printing camera settings: exposure, ISO, lens position, color temperature """ import os @@ -40,28 +38,22 @@ import cv2 import numpy as np import argparse +import depthai as dai import collections import time from itertools import cycle -from pathlib import Path -import sys -import signal - def socket_type_pair(arg): socket, type = arg.split(',') if not (socket in ['rgb', 'left', 'right', 'camd', 'came', 'camf']): raise ValueError("") if not (type in ['m', 'mono', 'c', 'color']): raise ValueError("") is_color = True if type in ['c', 'color'] else False - is_tof = True if type in ['t', 'tof'] else False - return [socket, is_color, is_tof] - + return [socket, is_color] parser = argparse.ArgumentParser() parser.add_argument('-cams', '--cameras', type=socket_type_pair, nargs='+', - default=[['rgb', True, False], ['left', False, False], - ['right', False, False], ['camd', True, False]], - help="Which camera sockets to enable, and type: c[olor] / m[ono] / t[of]. " + default=[['rgb', True], ['left', False], ['right', False], ['camd', True]], + help="Which camera sockets to enable, and type: c[olor] / m[ono]. " "E.g: -cams rgb,m right,c . Default: rgb,c left,m right,m camd,c") parser.add_argument('-mres', '--mono-resolution', type=int, default=800, choices={480, 400, 720, 800}, help="Select mono camera resolution (height). Default: %(default)s") @@ -71,8 +63,6 @@ def socket_type_pair(arg): help="Which cameras to rotate 180 degrees. All if not filtered") parser.add_argument('-fps', '--fps', type=float, default=30, help="FPS to set for all cameras") -parser.add_argument('-isp3afps', '--isp3afps', type=int, default=0, - help="3A FPS to set for all cameras") parser.add_argument('-ds', '--isp-downscale', default=1, type=int, help="Downscale the ISP output by this factor") parser.add_argument('-rs', '--resizable-windows', action='store_true', @@ -81,24 +71,13 @@ def socket_type_pair(arg): help='Enable the RAW camera streams') args = parser.parse_args() -# Set timeouts before importing depthai -os.environ["DEPTHAI_CONNECTION_TIMEOUT"] = str(args.connection_timeout) -os.environ["DEPTHAI_BOOT_TIMEOUT"] = str(args.boot_timeout) -import depthai as dai - -if len(sys.argv) == 1: - import cam_test_gui - cam_test_gui.main() - cam_list = [] cam_type_color = {} -cam_type_tof = {} print("Enabled cameras:") -for socket, is_color, is_tof in args.cameras: +for socket, is_color in args.cameras: cam_list.append(socket) cam_type_color[socket] = is_color - cam_type_tof[socket] = is_tof - print(socket.rjust(7), ':', 'tof' if is_tof else 'color' if is_color else 'mono') + print(socket.rjust(7), ':', 'color' if is_color else 'mono') print("DepthAI version:", dai.__version__) print("DepthAI path:", dai.__file__) @@ -107,9 +86,6 @@ def socket_type_pair(arg): 'rgb' : dai.CameraBoardSocket.CAM_A, 'left' : dai.CameraBoardSocket.CAM_B, 'right': dai.CameraBoardSocket.CAM_C, - 'cama' : dai.CameraBoardSocket.CAM_A, - 'camb' : dai.CameraBoardSocket.CAM_B, - 'camc' : dai.CameraBoardSocket.CAM_C, 'camd' : dai.CameraBoardSocket.CAM_D, 'came' : dai.CameraBoardSocket.CAM_E, 'camf' : dai.CameraBoardSocket.CAM_F, @@ -119,14 +95,17 @@ def socket_type_pair(arg): 'RGB' : 'rgb', 'LEFT' : 'left', 'RIGHT': 'right', + 'CAM_A': 'rgb', + 'CAM_B': 'left', + 'CAM_C': 'right', 'CAM_D': 'camd', 'CAM_E': 'came', 'CAM_F': 'camf', } rotate = { - 'rgb': args.rotate in ['all', 'rgb'], - 'left': args.rotate in ['all', 'mono'], + 'rgb' : args.rotate in ['all', 'rgb'], + 'left' : args.rotate in ['all', 'mono'], 'right': args.rotate in ['all', 'mono'], 'camd' : args.rotate in ['all', 'rgb'], 'came' : args.rotate in ['all', 'rgb'], @@ -153,7 +132,6 @@ def socket_type_pair(arg): '4k': dai.ColorCameraProperties.SensorResolution.THE_4_K, '5mp': dai.ColorCameraProperties.SensorResolution.THE_5_MP, '12mp': dai.ColorCameraProperties.SensorResolution.THE_12_MP, - '13mp': dai.ColorCameraProperties.SensorResolution.THE_13_MP, '48mp': dai.ColorCameraProperties.SensorResolution.THE_48_MP, } @@ -167,11 +145,9 @@ def __init__(self, window_size=30): self.fps = 0 def update(self, timestamp=None): - if timestamp == None: - timestamp = time.monotonic() + if timestamp == None: timestamp = time.monotonic() count = len(self.dq) - if count > 0: - self.fps = count / (timestamp - self.dq[0]) + if count > 0: self.fps = count / (timestamp - self.dq[0]) self.dq.append(timestamp) def get(self): @@ -180,21 +156,16 @@ def get(self): # Start defining a pipeline pipeline = dai.Pipeline() # Uncomment to get better throughput -# pipeline.setXLinkChunkSize(0) +#pipeline.setXLinkChunkSize(0) control = pipeline.createXLinkIn() control.setStreamName('control') -xinTofConfig = pipeline.createXLinkIn() -xinTofConfig.setStreamName('tofConfig') - cam = {} -tof = {} xout = {} xout_raw = {} streams = [] for c in cam_list: - tofEnableRaw = False xout[c] = pipeline.createXLinkOut() xout[c].setStreamName(c) streams.append(c) @@ -202,22 +173,19 @@ def get(self): cam[c] = pipeline.createColorCamera() cam[c].setResolution(color_res_opts[args.color_resolution]) cam[c].setIspScale(1, args.isp_downscale) - # cam[c].initialControl.setManualFocus(85) # TODO - if args.rgb_preview: - cam[c].preview.link(xout[c].input) - else: - cam[c].isp.link(xout[c].input) + #cam[c].initialControl.setManualFocus(85) # TODO + cam[c].isp.link(xout[c].input) else: cam[c] = pipeline.createMonoCamera() cam[c].setResolution(mono_res_opts[args.mono_resolution]) cam[c].out.link(xout[c].input) cam[c].setBoardSocket(cam_socket_opts[c]) # Num frames to capture on trigger, with first to be discarded (due to degraded quality) - # cam[c].initialControl.setExternalTrigger(2, 1) - # cam[c].initialControl.setStrobeExternal(48, 1) - # cam[c].initialControl.setFrameSyncMode(dai.CameraControl.FrameSyncMode.INPUT) + #cam[c].initialControl.setExternalTrigger(2, 1) + #cam[c].initialControl.setStrobeExternal(48, 1) + #cam[c].initialControl.setFrameSyncMode(dai.CameraControl.FrameSyncMode.INPUT) - # cam[c].initialControl.setManualExposure(15000, 400) # exposure [us], iso + #cam[c].initialControl.setManualExposure(15000, 400) # exposure [us], iso # When set, takes effect after the first 2 frames #cam[c].initialControl.setManualWhiteBalance(4000) # light temperature in K, 1000..12000 # cam[c].initialControl.setMisc("stride-align", 1) @@ -234,6 +202,11 @@ def get(self): streams.append(raw_name) cam[c].raw.link(xout_raw[c].input) +if 0: + print("=== Using custom camera tuning, and limiting RGB FPS to 10") + pipeline.setCameraTuningBlobPath("/home/user/Downloads/tuning_color_low_light.bin") + # TODO: change sensor driver to make FPS automatic (based on requested exposure time) + cam['rgb'].setFps(10) # Pipeline is defined, now we can connect to the device with dai.Device() as device: @@ -241,8 +214,7 @@ def get(self): print('Connected cameras:', device.getConnectedCameraFeatures()) cam_name = {} for p in device.getConnectedCameraFeatures(): - print( - f' -socket {p.socket.name:6}: {p.sensorName:6} {p.width:4} x {p.height:4} focus:', end='') + print(f' -socket {p.socket.name:6}: {p.sensorName:6} {p.width:4} x {p.height:4} focus:', end='') print('auto ' if p.hasAutofocus else 'fixed', '- ', end='') print(*[type.name for type in p.supportedTypes]) socket_name = cam_socket_to_name[p.socket.name] @@ -269,7 +241,6 @@ def get(self): fps_capt[c] = FPS() controlQueue = device.getInputQueue('control') - tofCfgQueue = device.getInputQueue('tofConfig') # Manual exposure/focus set step EXP_STEP = 500 # us @@ -296,12 +267,9 @@ def get(self): dotIntensity = 0 floodIntensity = 0 - awb_mode = cycle([item for name, item in vars( - dai.CameraControl.AutoWhiteBalanceMode).items() if name.isupper()]) - anti_banding_mode = cycle([item for name, item in vars( - dai.CameraControl.AntiBandingMode).items() if name.isupper()]) - effect_mode = cycle([item for name, item in vars( - dai.CameraControl.EffectMode).items() if name.isupper()]) + awb_mode = cycle([item for name, item in vars(dai.CameraControl.AutoWhiteBalanceMode).items() if name.isupper()]) + anti_banding_mode = cycle([item for name, item in vars(dai.CameraControl.AntiBandingMode).items() if name.isupper()]) + effect_mode = cycle([item for name, item in vars(dai.CameraControl.EffectMode).items() if name.isupper()]) ae_comp = 0 ae_lock = False @@ -313,13 +281,8 @@ def get(self): luma_denoise = 0 chroma_denoise = 0 control = 'none' - show = False - - jet_custom = cv2.applyColorMap(np.arange(256, dtype=np.uint8), cv2.COLORMAP_JET) - jet_custom[0] = [0, 0, 0] - print("Cam:", *[' ' + c.ljust(8) - for c in cam_list], "[host | capture timestamp]") + print("Cam:", *[' ' + c.ljust(8) for c in cam_list], "[host | capture timestamp]") capture_list = [] while True: @@ -379,10 +342,6 @@ def get(self): key = cv2.waitKey(1) if key == ord('q'): break - elif key == ord('/'): - show = not show - # Print empty string as FPS status new-line separator - print("" if show else "Printing camera settings: OFF") elif key == ord('c'): capture_list = streams.copy() capture_time = time.strftime('%Y%m%d_%H%M%S') @@ -404,8 +363,7 @@ def get(self): elif key == ord('f'): print("Autofocus enable, continuous") ctrl = dai.CameraControl() - ctrl.setAutoFocusMode( - dai.CameraControl.AutoFocusMode.CONTINUOUS_VIDEO) + ctrl.setAutoFocusMode(dai.CameraControl.AutoFocusMode.CONTINUOUS_VIDEO) controlQueue.send(ctrl) elif key == ord('e'): print("Autoexposure enable") @@ -413,24 +371,18 @@ def get(self): ctrl.setAutoExposureEnable() controlQueue.send(ctrl) elif key in [ord(','), ord('.')]: - if key == ord(','): - lensPos -= LENS_STEP - if key == ord('.'): - lensPos += LENS_STEP + if key == ord(','): lensPos -= LENS_STEP + if key == ord('.'): lensPos += LENS_STEP lensPos = clamp(lensPos, lensMin, lensMax) print("Setting manual focus, lens position: ", lensPos) ctrl = dai.CameraControl() ctrl.setManualFocus(lensPos) controlQueue.send(ctrl) elif key in [ord('i'), ord('o'), ord('k'), ord('l')]: - if key == ord('i'): - expTime -= EXP_STEP - if key == ord('o'): - expTime += EXP_STEP - if key == ord('k'): - sensIso -= ISO_STEP - if key == ord('l'): - sensIso += ISO_STEP + if key == ord('i'): expTime -= EXP_STEP + if key == ord('o'): expTime += EXP_STEP + if key == ord('k'): sensIso -= ISO_STEP + if key == ord('l'): sensIso += ISO_STEP expTime = clamp(expTime, expMin, expMax) sensIso = clamp(sensIso, sensMin, sensMax) print("Setting manual exposure, time: ", expTime, "iso: ", sensIso) @@ -469,36 +421,22 @@ def get(self): if floodIntensity < 0: floodIntensity = 0 device.setIrFloodLightBrightness(floodIntensity) - elif key >= 0 and chr(key) in '34567890[]p': - if key == ord('3'): - control = 'awb_mode' - elif key == ord('4'): - control = 'ae_comp' - elif key == ord('5'): - control = 'anti_banding_mode' - elif key == ord('6'): - control = 'effect_mode' - elif key == ord('7'): - control = 'brightness' - elif key == ord('8'): - control = 'contrast' - elif key == ord('9'): - control = 'saturation' - elif key == ord('0'): - control = 'sharpness' - elif key == ord('['): - control = 'luma_denoise' - elif key == ord(']'): - control = 'chroma_denoise' - elif key == ord('p'): - control = 'tof_amplitude_min' + elif key >= 0 and chr(key) in '34567890[]': + if key == ord('3'): control = 'awb_mode' + elif key == ord('4'): control = 'ae_comp' + elif key == ord('5'): control = 'anti_banding_mode' + elif key == ord('6'): control = 'effect_mode' + elif key == ord('7'): control = 'brightness' + elif key == ord('8'): control = 'contrast' + elif key == ord('9'): control = 'saturation' + elif key == ord('0'): control = 'sharpness' + elif key == ord('['): control = 'luma_denoise' + elif key == ord(']'): control = 'chroma_denoise' print("Selected control:", control) elif key in [ord('-'), ord('_'), ord('+'), ord('=')]: change = 0 - if key in [ord('-'), ord('_')]: - change = -1 - if key in [ord('+'), ord('=')]: - change = 1 + if key in [ord('-'), ord('_')]: change = -1 + if key in [ord('+'), ord('=')]: change = 1 ctrl = dai.CameraControl() if control == 'none': print("Please select a control first using keys 3..9 0 [ ]") @@ -542,9 +480,4 @@ def get(self): chroma_denoise = clamp(chroma_denoise + change, 0, 4) print("Chroma denoise:", chroma_denoise) ctrl.setChromaDenoise(chroma_denoise) - elif control == 'tof_amplitude_min' and tof: - amp_min = clamp(tofConfig.depthParams.minimumAmplitude + change, 0, 50) - print("Setting min amplitude(confidence) to:", amp_min) - tofConfig.depthParams.minimumAmplitude = amp_min - tofCfgQueue.send(tofConfig) controlQueue.send(ctrl) From 868e8dc6818f2841b211f0c3d77f1c066ee7e5b8 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Thu, 10 Aug 2023 17:13:34 +0300 Subject: [PATCH 383/385] RVC3 MonoCamera: new resolutions for 12mp, 13mp --- depthai-core | 2 +- src/pipeline/node/MonoCameraBindings.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 0fca3b449..e8f119c5c 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 0fca3b4498111f8c77049be2e88267ad76e27232 +Subproject commit e8f119c5cd177490563c4f2f548628e0ecb0aa54 diff --git a/src/pipeline/node/MonoCameraBindings.cpp b/src/pipeline/node/MonoCameraBindings.cpp index 51365fda0..4e6765eb4 100644 --- a/src/pipeline/node/MonoCameraBindings.cpp +++ b/src/pipeline/node/MonoCameraBindings.cpp @@ -36,6 +36,8 @@ void bind_monocamera(pybind11::module& m, void* pCallstack){ .value("THE_400_P", MonoCameraProperties::SensorResolution::THE_400_P) .value("THE_480_P", MonoCameraProperties::SensorResolution::THE_480_P) .value("THE_1200_P", MonoCameraProperties::SensorResolution::THE_1200_P) + .value("THE_4000X3000", MonoCameraProperties::SensorResolution::THE_4000X3000) + .value("THE_4224X3136", MonoCameraProperties::SensorResolution::THE_4224X3136) ; monoCameraProperties From 99dbc24572123726428e5917574e44f03c524290 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Thu, 10 Aug 2023 17:24:34 +0300 Subject: [PATCH 384/385] RVC3 FW: Monocamera new resolutions for 12mp, 13mp. Add to cam_test.py --- depthai-core | 2 +- utilities/cam_test.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/depthai-core b/depthai-core index e8f119c5c..1ce848d7f 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit e8f119c5cd177490563c4f2f548628e0ecb0aa54 +Subproject commit 1ce848d7fa06ff2fd235cf7f2e5c4c1fa2509eee diff --git a/utilities/cam_test.py b/utilities/cam_test.py index 3b7b94dca..bae88c9f6 100755 --- a/utilities/cam_test.py +++ b/utilities/cam_test.py @@ -55,9 +55,9 @@ def socket_type_pair(arg): default=[['rgb', True], ['left', False], ['right', False], ['camd', True]], help="Which camera sockets to enable, and type: c[olor] / m[ono]. " "E.g: -cams rgb,m right,c . Default: rgb,c left,m right,m camd,c") -parser.add_argument('-mres', '--mono-resolution', type=int, default=800, choices={480, 400, 720, 800}, +parser.add_argument('-mres', '--mono-resolution', type=int, default=800, choices={480, 400, 720, 800, 1200, 4000, 4224}, help="Select mono camera resolution (height). Default: %(default)s") -parser.add_argument('-cres', '--color-resolution', default='1080', choices={'720', '800', '1080', '1200', '1500', '1520', '1560', '4000', '4k', '5mp', '12mp', '48mp'}, +parser.add_argument('-cres', '--color-resolution', default='1080', choices={'720', '800', '1080', '1200', '1500', '1520', '1560', '4000', '4k', '5mp', '12mp', '13mp', '48mp'}, help="Select color camera resolution / height. Default: %(default)s") parser.add_argument('-rot', '--rotate', const='all', choices={'all', 'rgb', 'mono'}, nargs="?", help="Which cameras to rotate 180 degrees. All if not filtered") @@ -118,6 +118,8 @@ def socket_type_pair(arg): 720: dai.MonoCameraProperties.SensorResolution.THE_720_P, 800: dai.MonoCameraProperties.SensorResolution.THE_800_P, 1200: dai.MonoCameraProperties.SensorResolution.THE_1200_P, + 4000: dai.MonoCameraProperties.SensorResolution.THE_4000X3000, + 4224: dai.MonoCameraProperties.SensorResolution.THE_4224X3136, } color_res_opts = { @@ -132,6 +134,7 @@ def socket_type_pair(arg): '4k': dai.ColorCameraProperties.SensorResolution.THE_4_K, '5mp': dai.ColorCameraProperties.SensorResolution.THE_5_MP, '12mp': dai.ColorCameraProperties.SensorResolution.THE_12_MP, + '13mp': dai.ColorCameraProperties.SensorResolution.THE_13_MP, '48mp': dai.ColorCameraProperties.SensorResolution.THE_48_MP, } From c45828924dc2f5ac1c5da61aa6d03537fe25bf5c Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Tue, 15 Aug 2023 18:59:53 +0000 Subject: [PATCH 385/385] [RVC3 FW] Add camera resolutions and types to device discovery --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 1ce848d7f..6c8a0381d 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 1ce848d7fa06ff2fd235cf7f2e5c4c1fa2509eee +Subproject commit 6c8a0381daf1f4c1937da28329b7864980ecdfd3