diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 42997bd37..0354f12b7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -177,6 +177,7 @@ jobs: matrix: python-version: [3.6, 3.7, 3.8, 3.9, '3.10'] python-architecture: [x64, x86] + fail-fast: false steps: - name: Cache .hunter folder uses: actions/cache@v2 @@ -230,6 +231,7 @@ jobs: strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9, '3.10'] + fail-fast: false steps: - name: Cache .hunter folder uses: actions/cache@v2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 988bdfd8f..a02a1c2db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,12 @@ if(NOT WIN32) set(HUNTER_CONFIGURATION_TYPES "Release" CACHE STRING "Hunter dependencies list of build configurations") endif() +# Specify path separator +set(SYS_PATH_SEPARATOR ";") +if(UNIX) + set(SYS_PATH_SEPARATOR ":") +endif() + # Generate combined Hunter config file(READ depthai-core/cmake/Hunter/config.cmake depthai_core_hunter_config) file(READ cmake/Hunter/config.cmake hunter_config) @@ -29,7 +35,7 @@ endif() # Pybindings project set(TARGET_NAME depthai) -project(depthai VERSION "1") # revision of bindings [depthai-core].[rev] +project(depthai VERSION "0") # revision of bindings [depthai-core].[rev] # Set default build type depending on context set(default_build_type "Release") @@ -97,6 +103,23 @@ pybind11_add_module(${TARGET_NAME} src/log/LogBindings.cpp ) +if(WIN32) + # Copy dlls to target directory - Windows only + # TARGET_RUNTIME_DLLS generator expression available since CMake 3.21 + if(CMAKE_VERSION VERSION_LESS "3.21") + file(GLOB depthai_dll_libraries "${HUNTER_INSTALL_PREFIX}/bin/*.dll") + else() + set(depthai_dll_libraries "$") + endif() + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND + ${CMAKE_COMMAND} -E copy ${depthai_dll_libraries} $ + COMMAND_EXPAND_LISTS + ) + + # Disable "d" postfix, so python can import the library as is + set_target_properties(${TARGET_NAME} PROPERTIES DEBUG_POSTFIX "") +endif() + # Add stubs (pyi) generation step after building bindings execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" "from mypy import api" RESULT_VARIABLE error OUTPUT_QUIET ERROR_QUIET) if(error) @@ -108,7 +131,12 @@ else() endif() message(STATUS "Mypy available, creating and checking stubs. Running with generate_stubs.py ${TARGET_NAME} ${bindings_directory}") add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND - ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_LIST_DIR}/generate_stubs.py" "${TARGET_NAME}" "${bindings_directory}" + ${CMAKE_COMMAND} -E env + # PATH (dlls) + "PATH=${HUNTER_INSTALL_PREFIX}/bin${SYS_PATH_SEPARATOR}$ENV{PATH}" + # Python path (to find compiled module) + "PYTHONPATH=$${SYS_PATH_SEPARATOR}$ENV{PYTHONPATH}" + ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_LIST_DIR}/generate_stubs.py" "${TARGET_NAME}" "$" DEPENDS "${CMAKE_CURRENT_LIST_DIR}/generate_stubs.py" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" ) diff --git a/ci/Dockerfile b/ci/Dockerfile index 3046aa9e0..e97711d0f 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -4,12 +4,6 @@ RUN apt-get update && apt-get install -y wget build-essential cmake pkg-config l ADD ci/docker_dependencies.sh . RUN ./docker_dependencies.sh -RUN wget https://github.com/libusb/libusb/releases/download/v1.0.24/libusb-1.0.24.tar.bz2 -RUN tar xf libusb-1.0.24.tar.bz2 -RUN cd libusb-1.0.24 && \ - ./configure --disable-udev && \ - make -j && make install - RUN pip install -U pip && pip install --extra-index-url https://www.piwheels.org/simple/ --prefer-binary opencv-python diff --git a/depthai-core b/depthai-core index cedff3f64..ce8b527be 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit cedff3f6419e07dca5fd9c5a087432f665613db8 +Subproject commit ce8b527be57485a5d252a87f8284f55d2a914e9e diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 6e4d2d9c6..6bc5a25ac 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -40,6 +40,8 @@ else() add_custom_target(sphinx ALL ${CMAKE_COMMAND} -E env # Environment variables + # PATH (dlls) + "PATH=${HUNTER_INSTALL_PREFIX}/bin${SYS_PATH_SEPARATOR}$ENV{PATH}" # Python path (to find compiled module) "PYTHONPATH=$${SYS_PATH_SEPARATOR}$ENV{PYTHONPATH}" # ASAN in case of sanitizers diff --git a/docs/source/components/bootloader.rst b/docs/source/components/bootloader.rst index 87927e32a..97abd163b 100644 --- a/docs/source/components/bootloader.rst +++ b/docs/source/components/bootloader.rst @@ -21,20 +21,23 @@ 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/170479657-faacd06d-5f7e-4215-a821-005d58a5f379.png +.. image:: https://user-images.githubusercontent.com/18037362/171629704-0f78f31a-1778-4338-8ac0-bdfb0d2d593f.png Device Manager Usage -------------------- **About device tab** - Select a camera to see its metadata - like MxID, flashed bootloader version, device state etc. -* First, we need to select the device using the dropdown. You can click ``Search`` to search for all available cameras, either via USB port or on LAN (PoE OAKs). +* First we have to select the device we want to connect (boot) to, you can select that using: + + * **Dropdown** which contains found device MX Ids. Dropdown will only get updated when starting the app. + * **Specify IP** button if your OAK PoE camera isn't in the same LAN. + * **Search** feature - a new window will show that has a table with all available cameras (either via USB port or on LAN - OAK PoEs), their MxId, name, and status. Clicking on a table row will select that device and boot to it. * ``Flash newest 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. - * ``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. diff --git a/docs/source/components/device.rst b/docs/source/components/device.rst index b538633a4..8bfdfd104 100644 --- a/docs/source/components/device.rst +++ b/docs/source/components/device.rst @@ -40,6 +40,20 @@ When you create the device in the code, firmware is uploaded together with the p cfg = depthai.ImageManipConfig() input_q.send(cfg) +Connect to specified device +########################### + +If you have multiple devices and only want to connect to a specific one, or if your OAK PoE camera is outside of your +subnet, you can specify the device (either with MxID, IP, or USB port name) you want to connect to. + +.. code-block:: python + + # Specify MXID, IP Address or USB path + device_info = depthai.DeviceInfo("14442C108144F1D000") # MXID + #device_info = depthai.DeviceInfo("192.168.1.44") # IP Address + #device_info = depthai.DeviceInfo("3.3.3") # USB port name + with depthai.Device(pipeline, device_info) as device: + # ... Multiple devices ################ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 6d92ec22e..7937142b8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -33,6 +33,8 @@ function(add_python_example example_name python_script_path) add_custom_target(${example_name} ${CMAKE_COMMAND} -E env # Environment variables + # PATH (dlls) + "PATH=${HUNTER_INSTALL_PREFIX}/bin${SYS_PATH_SEPARATOR}$ENV{PATH}" # Python path (to find compiled module) "PYTHONPATH=$${SYS_PATH_SEPARATOR}$ENV{PYTHONPATH}" # ASAN in case of sanitizers @@ -49,6 +51,8 @@ function(add_python_example example_name python_script_path) # Adds test with 5 seconds timeout and bumps all python warnings to errors add_test(NAME ${example_name} COMMAND ${CMAKE_COMMAND} -E env + # PATH (dlls) + "PATH=${HUNTER_INSTALL_PREFIX}/bin${SYS_PATH_SEPARATOR}$ENV{PATH}" # Python path (to find compiled module) "PYTHONPATH=$${SYS_PATH_SEPARATOR}$ENV{PYTHONPATH}" # ASAN in case of sanitizers @@ -70,6 +74,8 @@ if(DEPTHAI_PYTHON_TEST_EXAMPLES) # Adds install requirements test with 5 minute timeout add_test(NAME install_requirements COMMAND ${CMAKE_COMMAND} -E env + # PATH (dlls) + "PATH=${HUNTER_INSTALL_PREFIX}/bin${SYS_PATH_SEPARATOR}$ENV{PATH}" # Python path (to find compiled module) "PYTHONPATH=$${SYS_PATH_SEPARATOR}$ENV{PYTHONPATH}" # ASAN in case of sanitizers diff --git a/examples/IMU/imu_firmware_update.py b/examples/IMU/imu_firmware_update.py new file mode 100755 index 000000000..1dbf071bb --- /dev/null +++ b/examples/IMU/imu_firmware_update.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 + +import cv2 +import depthai as dai +import time +import math + +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: ") +if input() != 'y': + 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 dai.Device(pipeline) as device: + + def timeDeltaToMilliS(delta) -> float: + return delta.total_seconds()*1000 + + # 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.timestamp.get() + gyroTs = gyroValues.timestamp.get() + 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'): + break diff --git a/examples/IMU/imu_rotation_vector.py b/examples/IMU/imu_rotation_vector.py index ae76c8c25..1485c096f 100755 --- a/examples/IMU/imu_rotation_vector.py +++ b/examples/IMU/imu_rotation_vector.py @@ -16,6 +16,7 @@ # enable ROTATION_VECTOR at 400 hz rate imu.enableIMUSensor(dai.IMUSensor.ROTATION_VECTOR, 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 diff --git a/examples/MobileNet/rgb_mobilenet.py b/examples/MobileNet/rgb_mobilenet.py index e595cde17..94d734128 100755 --- a/examples/MobileNet/rgb_mobilenet.py +++ b/examples/MobileNet/rgb_mobilenet.py @@ -29,9 +29,11 @@ nn = pipeline.create(dai.node.MobileNetDetectionNetwork) xoutRgb = pipeline.create(dai.node.XLinkOut) nnOut = pipeline.create(dai.node.XLinkOut) +nnNetworkOut = pipeline.create(dai.node.XLinkOut) xoutRgb.setStreamName("rgb") nnOut.setStreamName("nn") +nnNetworkOut.setStreamName("nnNetwork"); # Properties camRgb.setPreviewSize(300, 300) @@ -51,6 +53,7 @@ camRgb.preview.link(nn.input) nn.out.link(nnOut.input) +nn.outNetwork.link(nnNetworkOut.input); # Connect to device and start pipeline with dai.Device(pipeline) as device: @@ -58,6 +61,7 @@ # Output queues will be used to get the rgb frames and nn data from the outputs defined above qRgb = device.getOutputQueue(name="rgb", maxSize=4, blocking=False) qDet = device.getOutputQueue(name="nn", maxSize=4, blocking=False) + qNN = device.getOutputQueue(name="nnNetwork", maxSize=4, blocking=False); frame = None detections = [] @@ -81,15 +85,19 @@ def displayFrame(name, frame): # Show the frame cv2.imshow(name, frame) + printOutputLayersOnce = True + while True: if args.sync: # Use blocking get() call to catch frame and inference result synced inRgb = qRgb.get() inDet = qDet.get() + inNN = qNN.get() else: # Instead of get (blocking), we use tryGet (non-blocking) which will return the available data or None otherwise inRgb = qRgb.tryGet() inDet = qDet.tryGet() + inNN = qNN.tryGet() if inRgb is not None: frame = inRgb.getCvFrame() @@ -100,6 +108,13 @@ def displayFrame(name, frame): detections = inDet.detections counter += 1 + if printOutputLayersOnce and inNN is not None: + toPrint = 'Output layer names:' + for ten in inNN.getAllLayerNames(): + toPrint = f'{toPrint} {ten},' + print(toPrint) + printOutputLayersOnce = False; + # If the frame is available, draw bounding boxes on it and show the frame if frame is not None: displayFrame("rgb", frame) diff --git a/examples/NeuralNetwork/detection_parser.py b/examples/NeuralNetwork/detection_parser.py new file mode 100755 index 000000000..340fb6040 --- /dev/null +++ b/examples/NeuralNetwork/detection_parser.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 + +from pathlib import Path +import cv2 +import depthai as dai +import numpy as np +import time +import argparse + +nnPathDefault = str((Path(__file__).parent / Path('../models/mobilenet-ssd_openvino_2021.4_6shave.blob')).resolve().absolute()) +parser = argparse.ArgumentParser() +parser.add_argument('nnPath', nargs='?', help="Path to mobilenet detection network blob", default=nnPathDefault) +parser.add_argument('-s', '--sync', action="store_true", help="Sync RGB output with NN output", default=False) +args = parser.parse_args() + +if not Path(nnPathDefault).exists(): + import sys + raise FileNotFoundError(f'Required file/s not found, please run "{sys.executable} install_requirements.py"') + +# MobilenetSSD label texts +labelMap = ["background", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", + "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"] + +# Create pipeline +pipeline = dai.Pipeline() + +# Define sources and outputs +camRgb = pipeline.create(dai.node.ColorCamera) +nn = pipeline.create(dai.node.NeuralNetwork) +det = pipeline.create(dai.node.DetectionParser) +xoutRgb = pipeline.create(dai.node.XLinkOut) +nnOut = pipeline.create(dai.node.XLinkOut) + +xoutRgb.setStreamName("rgb") +nnOut.setStreamName("nn") + +# Properties +camRgb.setPreviewSize(300, 300) +camRgb.setInterleaved(False) +camRgb.setFps(40) +# Define a neural network that will make predictions based on the source frames +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); + +# Linking +if args.sync: + nn.passthrough.link(xoutRgb.input) +else: + camRgb.preview.link(xoutRgb.input) + +camRgb.preview.link(nn.input) +nn.out.link(det.input) +det.out.link(nnOut.input) + +# Connect to device and start pipeline +with dai.Device(pipeline) as device: + + # Output queues will be used to get the rgb frames and nn data from the outputs defined above + qRgb = device.getOutputQueue(name="rgb", maxSize=4, blocking=False) + qDet = device.getOutputQueue(name="nn", maxSize=4, blocking=False) + + frame = None + detections = [] + startTime = time.monotonic() + counter = 0 + color2 = (255, 255, 255) + + # nn data (bounding box locations) are in <0..1> range - they need to be normalized with frame width/height + def frameNorm(frame, bbox): + normVals = np.full(len(bbox), frame.shape[0]) + normVals[::2] = frame.shape[1] + return (np.clip(np.array(bbox), 0, 1) * normVals).astype(int) + + def displayFrame(name, frame): + color = (255, 0, 0) + for detection in detections: + bbox = frameNorm(frame, (detection.xmin, detection.ymin, detection.xmax, detection.ymax)) + cv2.putText(frame, labelMap[detection.label], (bbox[0] + 10, bbox[1] + 20), cv2.FONT_HERSHEY_TRIPLEX, 0.5, color) + cv2.putText(frame, f"{int(detection.confidence * 100)}%", (bbox[0] + 10, bbox[1] + 40), cv2.FONT_HERSHEY_TRIPLEX, 0.5, color) + cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), color, 2) + # Show the frame + cv2.imshow(name, frame) + + while True: + if args.sync: + # Use blocking get() call to catch frame and inference result synced + inRgb = qRgb.get() + inDet = qDet.get() + else: + # Instead of get (blocking), we use tryGet (non-blocking) which will return the available data or None otherwise + inRgb = qRgb.tryGet() + inDet = qDet.tryGet() + + if inRgb is not None: + frame = inRgb.getCvFrame() + cv2.putText(frame, "NN fps: {:.2f}".format(counter / (time.monotonic() - startTime)), + (2, frame.shape[0] - 4), cv2.FONT_HERSHEY_TRIPLEX, 0.4, color2) + + if inDet is not None: + detections = inDet.detections + counter += 1 + + # If the frame is available, draw bounding boxes on it and show the frame + if frame is not None: + displayFrame("rgb", frame) + + if cv2.waitKey(1) == ord('q'): + break diff --git a/examples/SpatialDetection/spatial_tiny_yolo.py b/examples/SpatialDetection/spatial_tiny_yolo.py index 3aaa6981d..094f4d087 100755 --- a/examples/SpatialDetection/spatial_tiny_yolo.py +++ b/examples/SpatialDetection/spatial_tiny_yolo.py @@ -57,6 +57,7 @@ monoLeft = pipeline.create(dai.node.MonoCamera) monoRight = pipeline.create(dai.node.MonoCamera) stereo = pipeline.create(dai.node.StereoDepth) +nnNetworkOut = pipeline.create(dai.node.XLinkOut) xoutRgb = pipeline.create(dai.node.XLinkOut) xoutNN = pipeline.create(dai.node.XLinkOut) @@ -67,6 +68,7 @@ xoutNN.setStreamName("detections") xoutBoundingBoxDepthMapping.setStreamName("boundingBoxDepthMapping") xoutDepth.setStreamName("depth") +nnNetworkOut.setStreamName("nnNetwork") # Properties camRgb.setPreviewSize(416, 416) @@ -114,6 +116,7 @@ stereo.depth.link(spatialDetectionNetwork.inputDepth) spatialDetectionNetwork.passthroughDepth.link(xoutDepth.input) +spatialDetectionNetwork.outNetwork.link(nnNetworkOut.input); # Connect to device and start pipeline with dai.Device(pipeline) as device: @@ -123,16 +126,26 @@ detectionNNQueue = device.getOutputQueue(name="detections", maxSize=4, blocking=False) xoutBoundingBoxDepthMappingQueue = device.getOutputQueue(name="boundingBoxDepthMapping", maxSize=4, blocking=False) depthQueue = device.getOutputQueue(name="depth", maxSize=4, blocking=False) + networkQueue = device.getOutputQueue(name="nnNetwork", maxSize=4, blocking=False); startTime = time.monotonic() counter = 0 fps = 0 color = (255, 255, 255) + printOutputLayersOnce = True while True: inPreview = previewQueue.get() inDet = detectionNNQueue.get() depth = depthQueue.get() + inNN = networkQueue.get() + + if printOutputLayersOnce: + toPrint = 'Output layer names:' + for ten in inNN.getAllLayerNames(): + toPrint = f'{toPrint} {ten},' + print(toPrint) + printOutputLayersOnce = False; frame = inPreview.getCvFrame() depthFrame = depth.getFrame() # depthFrame values are in millimeters diff --git a/examples/bootloader/bootloader_config.py b/examples/bootloader/bootloader_config.py index 0967094ab..b6de9b1ac 100755 --- a/examples/bootloader/bootloader_config.py +++ b/examples/bootloader/bootloader_config.py @@ -31,7 +31,7 @@ (res, info) = dai.DeviceBootloader.getFirstAvailableDevice() if res: - print(f'Found device with name: {info.desc.name}'); + print(f'Found device with name: {info.name}'); with dai.DeviceBootloader(info) as bl: if read: print('Current flashed configuration') diff --git a/examples/bootloader/bootloader_version.py b/examples/bootloader/bootloader_version.py index 6c370469d..87f254823 100755 --- a/examples/bootloader/bootloader_version.py +++ b/examples/bootloader/bootloader_version.py @@ -5,7 +5,7 @@ (res, info) = dai.DeviceBootloader.getFirstAvailableDevice() if res == True: - print(f'Found device with name: {info.desc.name}') + print(f'Found device with name: {info.name}') bl = dai.DeviceBootloader(info) print(f'Version: {bl.getVersion()}') else: diff --git a/examples/bootloader/flash_bootloader.py b/examples/bootloader/flash_bootloader.py index 191f4dad1..50fc077da 100755 --- a/examples/bootloader/flash_bootloader.py +++ b/examples/bootloader/flash_bootloader.py @@ -20,7 +20,7 @@ exit(-1) else: for i, di in enumerate(deviceInfos): - print(f'[{i}] {di.getMxId()} [{di.desc.protocol.name}]', end='') + print(f'[{i}] {di.getMxId()} [{di.protocol.name}]', end='') if di.state == dai.XLinkDeviceState.X_LINK_BOOTLOADER: with dai.DeviceBootloader(di) as bl: print(f' current bootloader: {bl.getVersion()}', end='') diff --git a/examples/bootloader/poe_set_ip.py b/examples/bootloader/poe_set_ip.py index 029569c92..7314dacd6 100644 --- a/examples/bootloader/poe_set_ip.py +++ b/examples/bootloader/poe_set_ip.py @@ -12,7 +12,7 @@ def check_str(s: str): return s if found: - print(f'Found device with name: {info.desc.name}') + print(f'Found device with name: {info.name}') print('-------------------------------------') print('"1" to set a static IPv4 address') print('"2" to set a dynamic IPv4 address') diff --git a/generate_stubs.py b/generate_stubs.py index 78d3da721..3c21e1287 100644 --- a/generate_stubs.py +++ b/generate_stubs.py @@ -21,6 +21,13 @@ # CWD to to extdir where the built module can be found to extract the types env = os.environ env['PYTHONPATH'] = f'{DIRECTORY}{os.pathsep}{env.get("PYTHONPATH", "")}' + + # Test importing depthai after PYTHONPATH is specified + try: + import depthai + except Exception as ex: + print(f'Could not import depthai: {ex}') + print(f'PYTHONPATH set to {env["PYTHONPATH"]}') subprocess.check_call(['stubgen', '-p', MODULE_NAME, '-o', f'{DIRECTORY}'], cwd=DIRECTORY, env=env) diff --git a/setup.py b/setup.py index 0e9bdcbbc..6a2db6abb 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ from setuptools import setup, Extension from setuptools.command.build_ext import build_ext from distutils.version import LooseVersion +from pathlib import Path ### NAME MODULE_NAME = 'depthai' @@ -95,9 +96,16 @@ def build_extension(self, ext): # initialize cmake_args and build_args cmake_args = [] build_args = [] + env = os.environ.copy() # Specify output directory and python executable cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, '-DPYTHON_EXECUTABLE=' + sys.executable] + # Specify dir of python executable (pybind11) + if platform.system() == "Windows": + # Windows - remove case insensitive variants + env = {key:env[key] for key in env if key.upper() != 'pythonLocation'.upper()} + env['pythonLocation'] = str(Path(sys.executable).parent.absolute()) + # Pass a commit hash if buildCommitHash != None : @@ -127,8 +135,6 @@ def build_extension(self, ext): raise except: freeMemory = 4000 - # Memcheck (guard if it fails) - # Configure and build # Windows @@ -149,8 +155,8 @@ def build_extension(self, ext): # if macos add some additional env vars if sys.platform == 'darwin': from distutils import util - os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.9' - os.environ['_PYTHON_HOST_PLATFORM'] = re.sub(r'macosx-[0-9]+\.[0-9]+-(.+)', r'macosx-10.9-\1', util.get_platform()) + env['MACOSX_DEPLOYMENT_TARGET'] = '10.9' + env['_PYTHON_HOST_PLATFORM'] = re.sub(r'macosx-[0-9]+\.[0-9]+-(.+)', r'macosx-10.9-\1', util.get_platform()) # Specify how many threads to use when building, depending on available memory max_threads = multiprocessing.cpu_count() @@ -161,7 +167,6 @@ def build_extension(self, ext): build_args += ['--', '-j' + str(num_threads)] cmake_args += ['-DHUNTER_JOBS_NUMBER=' + str(num_threads)] - env = os.environ.copy() env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''), self.distribution.get_version()) # Add additional cmake args from environment @@ -170,8 +175,10 @@ def build_extension(self, ext): if not os.path.exists(self.build_temp): os.makedirs(self.build_temp) + + # Configure and build subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env) - subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) + subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp, env=env) setup( name=MODULE_NAME, diff --git a/src/DatatypeBindings.cpp b/src/DatatypeBindings.cpp index 776058bf4..c9577d77c 100644 --- a/src/DatatypeBindings.cpp +++ b/src/DatatypeBindings.cpp @@ -1385,6 +1385,7 @@ void DatatypeBindings::bind(pybind11::module& m, void* pCallstack){ .def("setLeftRightCheck", &StereoDepthConfig::setLeftRightCheck, py::arg("enable"), DOC(dai, StereoDepthConfig, setLeftRightCheck)) .def("setExtendedDisparity", &StereoDepthConfig::setExtendedDisparity, py::arg("enable"), DOC(dai, StereoDepthConfig, setExtendedDisparity)) .def("setSubpixel", &StereoDepthConfig::setSubpixel, py::arg("enable"), DOC(dai, StereoDepthConfig, setSubpixel)) + .def("setSubpixelFractionalBits", &StereoDepthConfig::setSubpixelFractionalBits, py::arg("subpixelFractionalBits"), DOC(dai, StereoDepthConfig, setSubpixelFractionalBits)) .def("getMaxDisparity", &StereoDepthConfig::getMaxDisparity, DOC(dai, StereoDepthConfig, getMaxDisparity)) .def("setDepthUnit", &StereoDepthConfig::setDepthUnit, DOC(dai, StereoDepthConfig, setDepthUnit)) .def("getDepthUnit", &StereoDepthConfig::getDepthUnit, DOC(dai, StereoDepthConfig, getDepthUnit)) diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 8b90b67d4..40d1b4dec 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -160,46 +160,14 @@ class_ bind_map_patched(handle scope, const std::string &name, template static auto deviceSearchHelper(Args&&... args){ - auto startTime = std::chrono::steady_clock::now(); - bool found = false; - bool invalidDeviceFound = false; - dai::DeviceInfo deviceInfo = {}; - dai::DeviceInfo invalidDeviceInfo = {}; - do { - { - // releases python GIL - py::gil_scoped_release release; - std::tie(found, deviceInfo) = DEVICE::getFirstAvailableDevice(false); - - if(strcmp("", deviceInfo.desc.name) == 0){ - invalidDeviceFound = true; - invalidDeviceInfo = deviceInfo; - found = false; - } - - // Check if found - if(found){ - break; - } else { - // block for 100ms - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - } - // reacquires python GIL for PyErr_CheckSignals call - // check if interrupt triggered in between + bool found; + dai::DeviceInfo deviceInfo; + // releases python GIL + py::gil_scoped_release release; + std::tie(found, deviceInfo) = DEVICE::getAnyAvailableDevice(DEVICE::getDefaultSearchTime(), [](){ + py::gil_scoped_acquire acquire; if (PyErr_CheckSignals() != 0) throw py::error_already_set(); - } while(std::chrono::steady_clock::now() - startTime < DEVICE::getDefaultSearchTime()); - - // Check if its an invalid device - if(invalidDeviceFound){ - // Warn - // spdlog::warn("skipping {} device having name \"{}\"", XLinkDeviceStateToStr(invalidDeviceInfo.state), invalidDeviceInfo.desc.name); - // TODO(themarpe) - move device search into C++ and expose a callback - DEVICE::getFirstAvailableDevice(true); - } - - // If neither UNBOOTED nor BOOTLOADER were found (after 'DEFAULT_SEARCH_TIME'), try BOOTED - if(!found) std::tie(found, deviceInfo) = dai::XLinkConnection::getFirstDevice(X_LINK_BOOTED); + }); // if no devices found, then throw if(!found) throw std::runtime_error("No available devices"); @@ -475,7 +443,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ //dai::Device methods //static - .def_static("getAnyAvailableDevice", [](std::chrono::microseconds us){ return Device::getAnyAvailableDevice(us); }, py::arg("timeout"), DOC(dai, DeviceBase, getAnyAvailableDevice)) + .def_static("getAnyAvailableDevice", [](std::chrono::milliseconds ms){ return DeviceBase::getAnyAvailableDevice(ms); }, py::arg("timeout"), DOC(dai, DeviceBase, getAnyAvailableDevice)) .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)) diff --git a/src/XLinkBindings.cpp b/src/XLinkBindings.cpp index 8ab924791..7e8caaedc 100644 --- a/src/XLinkBindings.cpp +++ b/src/XLinkBindings.cpp @@ -1,10 +1,13 @@ #include "XLinkBindings.hpp" +// std +#include +#include + +// depthai #include "depthai/xlink/XLinkConnection.hpp" #include "depthai/xlink/XLinkStream.hpp" -#include -#include void XLinkBindings::bind(pybind11::module &m, void *pCallstack) { @@ -47,22 +50,55 @@ void XLinkBindings::bind(pybind11::module &m, void *pCallstack) // Bindings deviceInfo - .def(py::init<>()) - .def_readwrite("desc", &DeviceInfo::desc) - .def_readwrite("state", &DeviceInfo::state) + .def(py::init<>(), DOC(dai, DeviceInfo, DeviceInfo)) + .def(py::init(), py::arg("name"), py::arg("mxid"), py::arg("state"), py::arg("protocol"), py::arg("platform"), py::arg("status"), DOC(dai, DeviceInfo, DeviceInfo, 2)) + .def(py::init(), py::arg("mxidOrName"), DOC(dai, DeviceInfo, DeviceInfo, 3)) + .def(py::init(), DOC(dai, DeviceInfo, DeviceInfo, 4)) .def("getMxId", &DeviceInfo::getMxId) + .def("getXLinkDeviceDesc", &DeviceInfo::getXLinkDeviceDesc) + .def_readwrite("name", &DeviceInfo::name) + .def_readwrite("mxid", &DeviceInfo::mxid) + .def_readwrite("state", &DeviceInfo::state) + .def_readwrite("protocol", &DeviceInfo::protocol) + .def_readwrite("platform", &DeviceInfo::platform) + .def_readwrite("status", &DeviceInfo::status) + .def("__repr__", &DeviceInfo::toString) + // deprecated + .def_property("desc", [](py::object& self) { + // Issue an deprecation warning + PyErr_WarnEx(PyExc_DeprecationWarning, "desc field is deprecated, use name/mxid and others instead.", 1); + return self; + },[](DeviceInfo& i, DeviceInfo di){ + // Issue an deprecation warning + PyErr_WarnEx(PyExc_DeprecationWarning, "desc field is deprecated, use name/mxid and others instead.", 1); + i = di; + }) ; deviceDesc .def(py::init<>()) .def_readwrite("protocol", &deviceDesc_t::protocol) .def_readwrite("platform", &deviceDesc_t::platform) + .def_readwrite("state", &deviceDesc_t::state) + .def_readwrite("status", &deviceDesc_t::status) .def_property( "name", [](deviceDesc_t &o) { return std::string(o.name); }, [](deviceDesc_t &o, std::string n) - { std::strncpy(o.name, n.c_str(), std::min(XLINK_MAX_NAME_SIZE, (int)n.size())); }) + { + memset(o.name, 0, sizeof(o.name)); + std::strncpy(o.name, n.c_str(), sizeof(o.name)); + }) + .def_property( + "mxid", + [](deviceDesc_t &o) + { return std::string(o.mxid); }, + [](deviceDesc_t &o, std::string n) + { + memset(o.mxid, 0, sizeof(o.mxid)); + std::strncpy(o.mxid, n.c_str(), sizeof(o.mxid)); + }) ; xLinkDeviceState diff --git a/src/openvino/OpenVINOBindings.cpp b/src/openvino/OpenVINOBindings.cpp index 29cb6da94..e2b9e1451 100644 --- a/src/openvino/OpenVINOBindings.cpp +++ b/src/openvino/OpenVINOBindings.cpp @@ -51,8 +51,11 @@ void OpenVINOBindings::bind(pybind11::module& m, void* pCallstack){ .value("VERSION_2021_2", OpenVINO::Version::VERSION_2021_2) .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) .export_values() ; + // DEFAULT_VERSION binding + openvino.attr("DEFAULT_VERSION") = dai::OpenVINO::DEFAULT_VERSION; // Bind OpenVINO::Blob openvinoBlob diff --git a/src/pipeline/CommonBindings.cpp b/src/pipeline/CommonBindings.cpp index 1ff9d2e38..70e39dd06 100644 --- a/src/pipeline/CommonBindings.cpp +++ b/src/pipeline/CommonBindings.cpp @@ -14,6 +14,7 @@ #include "depthai-shared/common/Size2f.hpp" #include "depthai-shared/common/UsbSpeed.hpp" #include "depthai-shared/common/DetectionNetworkType.hpp" +#include "depthai-shared/common/DetectionParserOptions.hpp" void CommonBindings::bind(pybind11::module& m, void* pCallstack){ @@ -37,6 +38,7 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ py::enum_ processorType(m, "ProcessorType"); py::enum_ detectionNetworkType(m, "DetectionNetworkType"); py::enum_ serializationType(m, "SerializationType"); + py::class_ detectionParserOptions(m, "DetectionParserOptions", DOC(dai, DetectionParserOptions)); /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// @@ -205,4 +207,14 @@ void CommonBindings::bind(pybind11::module& m, void* pCallstack){ .value("JSON_MSGPACK", SerializationType::JSON_MSGPACK) ; + detectionParserOptions + .def_readwrite("nnFamily", &DetectionParserOptions::nnFamily) + .def_readwrite("confidenceThreshold", &DetectionParserOptions::confidenceThreshold) + .def_readwrite("classes", &DetectionParserOptions::classes) + .def_readwrite("coordinates", &DetectionParserOptions::coordinates) + .def_readwrite("anchors", &DetectionParserOptions::anchors) + .def_readwrite("anchorMasks", &DetectionParserOptions::anchorMasks) + .def_readwrite("iouThreshold", &DetectionParserOptions::iouThreshold) + ; + } diff --git a/src/pipeline/NodeBindings.cpp b/src/pipeline/NodeBindings.cpp index 0f69ee360..580b397b6 100644 --- a/src/pipeline/NodeBindings.cpp +++ b/src/pipeline/NodeBindings.cpp @@ -22,6 +22,7 @@ #include "depthai/pipeline/node/EdgeDetector.hpp" #include "depthai/pipeline/node/FeatureTracker.hpp" #include "depthai/pipeline/node/AprilTag.hpp" +#include "depthai/pipeline/node/DetectionParser.hpp" // Libraries #include "hedley/hedley.h" @@ -182,6 +183,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ py::enum_ nodeOutputType(pyOutput, "Type"); py::class_ scriptProperties(m, "ScriptProperties", DOC(dai, ScriptProperties)); py::class_> pyProperties(m, "Properties", DOC(dai, Properties)); + py::class_ detectionParserProperties(m, "DetectionParserProperties", DOC(dai, DetectionParserProperties)); // Node::Id bindings @@ -216,6 +218,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ auto edgeDetector = ADD_NODE(EdgeDetector); auto featureTracker = ADD_NODE(FeatureTracker); auto aprilTag = ADD_NODE(AprilTag); + auto detectionParser = ADD_NODE(DetectionParser); py::enum_ stereoDepthPresetMode(stereoDepth, "PresetMode", DOC(dai, node, StereoDepth, PresetMode)); @@ -335,6 +338,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("profile", &VideoEncoderProperties::profile) .def_readwrite("quality", &VideoEncoderProperties::quality) .def_readwrite("rateCtrlMode", &VideoEncoderProperties::rateCtrlMode) + .def_readwrite("outputFrameSize", &VideoEncoderProperties::outputFrameSize) ; @@ -353,13 +357,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ detectionNetworkProperties - .def_readwrite("nnFamily", &DetectionNetworkProperties::nnFamily) - .def_readwrite("confidenceThreshold", &DetectionNetworkProperties::confidenceThreshold) - .def_readwrite("classes", &DetectionNetworkProperties::classes) - .def_readwrite("coordinates", &DetectionNetworkProperties::coordinates) - .def_readwrite("anchors", &DetectionNetworkProperties::anchors) - .def_readwrite("anchorMasks", &DetectionNetworkProperties::anchorMasks) - .def_readwrite("iouThreshold", &DetectionNetworkProperties::iouThreshold) + .def_readwrite("parser", &DetectionNetworkProperties::parser) ; @@ -426,6 +424,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("imuSensors", &IMUProperties::imuSensors, DOC(dai, IMUProperties, imuSensors)) .def_readwrite("batchReportThreshold", &IMUProperties::batchReportThreshold, DOC(dai, IMUProperties, batchReportThreshold)) .def_readwrite("maxBatchReports", &IMUProperties::maxBatchReports, DOC(dai, IMUProperties, maxBatchReports)) + .def_readwrite("enableFirmwareUpdate", &IMUProperties::enableFirmwareUpdate, DOC(dai, IMUProperties, enableFirmwareUpdate)) ; // EdgeDetector node properties @@ -470,6 +469,11 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("numMemorySlices", &FeatureTrackerProperties::numMemorySlices, DOC(dai, FeatureTrackerProperties, numMemorySlices)) ; + // DetectionParser node properties + detectionParserProperties + .def_readwrite("parser", &DetectionParserProperties::parser, DOC(dai, DetectionParserProperties, parser)) + ; + //////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////// @@ -684,6 +688,8 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def("setNumNCEPerInferenceThread", &NeuralNetwork::setNumNCEPerInferenceThread, py::arg("numNCEPerThread"), DOC(dai, node, NeuralNetwork, setNumNCEPerInferenceThread)) .def("getNumInferenceThreads", &NeuralNetwork::getNumInferenceThreads, DOC(dai, node, NeuralNetwork, getNumInferenceThreads)) + .def("setBlob", &NeuralNetwork::setBlob, DOC(dai, node, NeuralNetwork, setBlob)) + .def_readonly("inputs", &NeuralNetwork::inputs, DOC(dai, node, NeuralNetwork, inputs)) .def_readonly("passthroughs", &NeuralNetwork::passthroughs, DOC(dai, node, NeuralNetwork, passthroughs)) @@ -855,6 +861,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def("setRectification", &StereoDepth::setRectification, py::arg("enable"), DOC(dai, node, StereoDepth, setRectification)) .def("setLeftRightCheck", &StereoDepth::setLeftRightCheck, py::arg("enable"), DOC(dai, node, StereoDepth, setLeftRightCheck)) .def("setSubpixel", &StereoDepth::setSubpixel, py::arg("enable"), DOC(dai, node, StereoDepth, setSubpixel)) + .def("setSubpixelFractionalBits", &StereoDepth::setSubpixelFractionalBits, py::arg("subpixelFractionalBits"), DOC(dai, node, StereoDepth, setSubpixelFractionalBits)) .def("setExtendedDisparity", &StereoDepth::setExtendedDisparity, py::arg("enable"), DOC(dai, node, StereoDepth, setExtendedDisparity)) .def("setRectifyEdgeFillColor", &StereoDepth::setRectifyEdgeFillColor, py::arg("color"), DOC(dai, node, StereoDepth, setRectifyEdgeFillColor)) .def("setRectifyMirrorFrame", [](StereoDepth& s, bool enable) { @@ -968,6 +975,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def("setBitrate", &VideoEncoder::setBitrate, py::arg("bitrate"), DOC(dai, node, VideoEncoder, setBitrate)) .def("setBitrateKbps", &VideoEncoder::setBitrateKbps, py::arg("bitrateKbps"), DOC(dai, node, VideoEncoder, setBitrateKbps)) .def("setKeyframeFrequency", &VideoEncoder::setKeyframeFrequency, py::arg("freq"), DOC(dai, node, VideoEncoder, setKeyframeFrequency)) + .def("setMaxOutputFrameSize", &VideoEncoder::setMaxOutputFrameSize, py::arg("maxFrameSize"), DOC(dai, node, VideoEncoder, setMaxOutputFrameSize)) //.def("setMaxBitrate", &VideoEncoder::setMaxBitrate) .def("setNumBFrames", &VideoEncoder::setNumBFrames, py::arg("numBFrames"), DOC(dai, node, VideoEncoder, setNumBFrames)) .def("setQuality", &VideoEncoder::setQuality, py::arg("quality"), DOC(dai, node, VideoEncoder, setQuality)) @@ -1004,6 +1012,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ }, DOC(dai, node, VideoEncoder, getSize)) .def("getFrameRate", &VideoEncoder::getFrameRate, DOC(dai, node, VideoEncoder, getFrameRate)) .def("getLossless", &VideoEncoder::getLossless, DOC(dai, node, VideoEncoder, getLossless)) + .def("getMaxOutputFrameSize", &VideoEncoder::getMaxOutputFrameSize, DOC(dai, node, VideoEncoder, getMaxOutputFrameSize)) ; // ALIAS daiNodeModule.attr("VideoEncoder").attr("Properties") = videoEncoderProperties; @@ -1037,6 +1046,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ detectionNetwork .def_readonly("input", &DetectionNetwork::input, DOC(dai, node, NeuralNetwork, input)) .def_readonly("out", &DetectionNetwork::out, DOC(dai, node, DetectionNetwork, out)) + .def_readonly("outNetwork", &DetectionNetwork::outNetwork, DOC(dai, node, DetectionNetwork, outNetwork)) .def_readonly("passthrough", &DetectionNetwork::passthrough, DOC(dai, node, NeuralNetwork, passthrough)) .def("setConfidenceThreshold", &DetectionNetwork::setConfidenceThreshold, py::arg("thresh"), DOC(dai, node, DetectionNetwork, setConfidenceThreshold)) .def("getConfidenceThreshold", &DetectionNetwork::getConfidenceThreshold, DOC(dai, node, DetectionNetwork, getConfidenceThreshold)) @@ -1175,6 +1185,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def("getBatchReportThreshold", &IMU::getBatchReportThreshold, DOC(dai, node, IMU, getBatchReportThreshold)) .def("setMaxBatchReports", &IMU::setMaxBatchReports, py::arg("maxBatchReports"), DOC(dai, node, IMU, setMaxBatchReports)) .def("getMaxBatchReports", &IMU::getMaxBatchReports, DOC(dai, node, IMU, getMaxBatchReports)) + .def("enableFirmwareUpdate", &IMU::enableFirmwareUpdate, DOC(dai, node, IMU, enableFirmwareUpdate)) ; daiNodeModule.attr("IMU").attr("Properties") = imuProperties; @@ -1249,6 +1260,31 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ ; daiNodeModule.attr("FeatureTracker").attr("Properties") = featureTrackerProperties; + // FeatureTracker node + detectionParser + .def_readonly("input", &DetectionParser::input, DOC(dai, node, DetectionParser, input)) + .def_readonly("out", &DetectionParser::out, DOC(dai, node, DetectionParser, out)) + .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("setNNFamily", &DetectionParser::setNNFamily, py::arg("type"), DOC(dai, node, DetectionParser, setNNFamily)) + .def("getNNFamily", &DetectionParser::getNNFamily, DOC(dai, node, DetectionParser, getNNFamily)) + .def("setConfidenceThreshold", &DetectionParser::setConfidenceThreshold, py::arg("thresh"), DOC(dai, node, DetectionParser, setConfidenceThreshold)) + .def("getConfidenceThreshold", &DetectionParser::getConfidenceThreshold, DOC(dai, node, DetectionParser, getConfidenceThreshold)) + .def("setNumClasses", &DetectionParser::setNumClasses, py::arg("numClasses"), DOC(dai, node, DetectionParser, setNumClasses)) + .def("setCoordinateSize", &DetectionParser::setCoordinateSize, py::arg("coordinates"), DOC(dai, node, DetectionParser, setCoordinateSize)) + .def("setAnchors", &DetectionParser::setAnchors, py::arg("anchors"), DOC(dai, node, DetectionParser, setAnchors)) + .def("setAnchorMasks", &DetectionParser::setAnchorMasks, py::arg("anchorMasks"), DOC(dai, node, DetectionParser, setAnchorMasks)) + .def("setIouThreshold", &DetectionParser::setIouThreshold, py::arg("thresh"), DOC(dai, node, DetectionParser, setIouThreshold)) + .def("getNumClasses", &DetectionParser::getNumClasses, DOC(dai, node, DetectionParser, getNumClasses)) + .def("getCoordinateSize", &DetectionParser::getCoordinateSize, DOC(dai, node, DetectionParser, getCoordinateSize)) + .def("getAnchors", &DetectionParser::getAnchors, DOC(dai, node, DetectionParser, getAnchors)) + .def("getAnchorMasks", &DetectionParser::getAnchorMasks, DOC(dai, node, DetectionParser, getAnchorMasks)) + .def("getIouThreshold", &DetectionParser::getIouThreshold, DOC(dai, node, DetectionParser, getIouThreshold)) + + ; + daiNodeModule.attr("DetectionParser").attr("Properties") = detectionParserProperties; + } diff --git a/src/pipeline/PipelineBindings.cpp b/src/pipeline/PipelineBindings.cpp index bc90c6373..168516b18 100644 --- a/src/pipeline/PipelineBindings.cpp +++ b/src/pipeline/PipelineBindings.cpp @@ -26,6 +26,7 @@ #include "depthai/pipeline/node/EdgeDetector.hpp" #include "depthai/pipeline/node/FeatureTracker.hpp" #include "depthai/pipeline/node/AprilTag.hpp" +#include "depthai/pipeline/node/DetectionParser.hpp" // depthai-shared #include "depthai-shared/properties/GlobalProperties.hpp" @@ -134,6 +135,7 @@ void PipelineBindings::bind(pybind11::module& m, void* pCallstack){ .def("createEdgeDetector", &Pipeline::create) .def("createFeatureTracker", &Pipeline::create) .def("createAprilTag", &Pipeline::create) + .def("createDetectionParser", &Pipeline::create) ; diff --git a/src/py_bindings.cpp b/src/py_bindings.cpp index 1a3927ab9..0376c7e2a 100644 --- a/src/py_bindings.cpp +++ b/src/py_bindings.cpp @@ -79,6 +79,10 @@ PYBIND11_MODULE(depthai, m) } // Call dai::initialize on 'import depthai' to initialize asap with additional information to print - dai::initialize(std::string("Python bindings - version: ") + DEPTHAI_PYTHON_VERSION + " from " + DEPTHAI_PYTHON_COMMIT_DATETIME + " build: " + DEPTHAI_PYTHON_BUILD_DATETIME, installSignalHandler); + try { + dai::initialize(std::string("Python bindings - version: ") + DEPTHAI_PYTHON_VERSION + " from " + DEPTHAI_PYTHON_COMMIT_DATETIME + " build: " + DEPTHAI_PYTHON_BUILD_DATETIME, installSignalHandler); + } catch (const std::exception&) { + // ignore, will be initialized later on if possible + } } diff --git a/src/pybind11_common.hpp b/src/pybind11_common.hpp index 192206246..6dbc0967b 100644 --- a/src/pybind11_common.hpp +++ b/src/pybind11_common.hpp @@ -45,7 +45,7 @@ namespace pybind11 { namespace detail { bool isPath = false; try{ isPath = isinstance(src, module::import("pathlib").attr("PurePath")); - } catch (const std::exception& ex) { + } catch (const std::exception&) { //ignore } if(!isinstance(src) && !isPath) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3c0eec650..5fe352e5e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,6 +21,8 @@ pybind11_add_module(${TARGET_TEST_MODULE} THIN_LTO ${TARGET_TEST_MODULE}.cpp ${P add_custom_target( pytest COMMAND ${CMAKE_COMMAND} -E env + # PATH (dlls) + "PATH=${HUNTER_INSTALL_PREFIX}/bin${SYS_PATH_SEPARATOR}$ENV{PATH}" # Python path (to find compiled modules) "PYTHONPATH=$${SYS_PATH_SEPARATOR}$${SYS_PATH_SEPARATOR}$ENV{PYTHONPATH}" # ASAN in case of sanitizers diff --git a/utilities/README.md b/utilities/README.md index e2c12c0b7..02856eb4c 100644 --- a/utilities/README.md +++ b/utilities/README.md @@ -4,6 +4,6 @@ This folder contains DepthAI utility tools. ## Device Manager -![Device Manager](https://user-images.githubusercontent.com/18037362/170479657-faacd06d-5f7e-4215-a821-005d58a5f379.png) +![Device Manager](https://user-images.githubusercontent.com/18037362/171629704-0f78f31a-1778-4338-8ac0-bdfb0d2d593f.png) ``device_manager.py`` helps interfacing with the device [Bootloader](https://docs.luxonis.com/projects/api/en/latest/components/bootloader) and bootloader configuration. See [Device Manager Usage](https://docs.luxonis.com/projects/api/en/latest/components/bootloader/#device-manager-usage) to see how to use this utility. diff --git a/utilities/device_manager.py b/utilities/device_manager.py index 6f0f410e3..6cf91e6ba 100644 --- a/utilities/device_manager.py +++ b/utilities/device_manager.py @@ -4,7 +4,7 @@ import depthai as dai import tempfile import PySimpleGUI as sg - +import sys CONF_TEXT_POE = ['ipTypeText', 'ipText', 'maskText', 'gatewayText', 'dnsText', 'dnsAltText', 'networkTimeoutText', 'macText'] CONF_INPUT_POE = ['staticBut', 'dynamicBut', 'ip', 'mask', 'gateway', 'dns', 'dnsAlt', 'networkTimeout', 'mac'] @@ -12,6 +12,15 @@ CONF_INPUT_USB = ['usbTimeout', 'usbSpeed'] USB_SPEEDS = ["UNKNOWN", "LOW", "FULL", "HIGH", "SUPER", "SUPER_PLUS"] +devices = dict() + +def PrintException(): + exc_type, exc_obj, tb = sys.exc_info() + f = tb.tb_frame + lineno = tb.tb_lineno + filename = f.f_code.co_filename + print('Exception in {}, line {}; {}'.format(filename, lineno, exc_obj)) + def check_ip(s: str): if s == "": return False @@ -69,6 +78,77 @@ def wait(self): type = getattr(dai.DeviceBootloader.Type, values['bootType']) return (str(event) == "Submit", type) +class SelectIP: + def __init__(self): + self.ok = False + layout = [ + [sg.Text("Specify the custom IP of the OAK PoE\ncamera you want to connect to")], + [ + sg.Text("IPv4:", font=('Arial', 10, 'bold'), text_color="black"), + sg.InputText(key="ip", size=(16, 2)), + ], + [sg.Submit(), sg.Cancel()], + ] + self.window = sg.Window("Specify IP", layout, size=(300,110), modal=True, finalize=True) + 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"]): + return False, "" + return True, values["ip"] + +class SearchDevice: + def __init__(self): + self.infos = [] + layout = [ + [sg.Text("Select an OAK camera you would like to connect to.", font=('Arial', 10, 'bold'))], + [sg.Table(values=[], headings=["MxId", "Name", "State"], + # col_widths=[25, 17, 17], + # def_col_width=25, + # max_col_width=200, + # background_color='light blue', + display_row_numbers=True, + justification='right', + num_rows=8, + alternating_row_color='lightyellow', + key='table', + row_height=35, + # size=(500, 300) + expand_x = True, + enable_events = True, + enable_click_events = True, + ) + ], + [sg.Button('Search', size=(15, 2), font=('Arial', 10, 'bold'))], + ] + self.window = sg.Window("Select Device", layout, size=(550,375), modal=True, finalize=True) + self.search_devices() + + def search_devices(self): + self.infos = dai.XLinkConnection.getAllConnectedDevices() + if not self.infos: + sg.Popup("No devices found.") + else: + rows = [] + for info in self.infos: + rows.append([info.getMxId(), info.name, deviceStateTxt(info.state)]) + self.window['table'].update(values=rows) + + def wait(self) -> dai.DeviceInfo: + while True: + event, values = self.window.Read() + if event is None: + self.window.close() + return None + elif str(event) == 'Search': + self.search_devices() + elif len(event) == 3 and event[0] == "table" and event[1] == "+CLICKED+": + # User selected a device + deviceIndex = event[2][0] + deviceSelected = self.infos[deviceIndex] + self.window.close() + return deviceSelected + def unlockConfig(window, devType): if devType == "POE": for el in CONF_INPUT_POE: @@ -88,7 +168,6 @@ def unlockConfig(window, devType): window['Flash DAP'].update(disabled=False) window['recoveryMode'].update(disabled=False) - def lockConfig(window): for conf in [CONF_INPUT_POE, CONF_INPUT_USB]: for el in conf: @@ -113,8 +192,7 @@ def lockConfig(window): window.Element('commit').update("-version-") window.Element('devState').update("-state-") - -def getDevices(window, devices): +def getDevices(window): try: listedDevices = [] devices.clear() @@ -124,16 +202,15 @@ def getDevices(window, devices): sg.Popup("No devices found.") else: for deviceInfo in deviceInfos: - # print(deviceInfo.state) - listedDevices.append(deviceInfo.desc.name) - devices[deviceInfo.desc.name] = deviceInfo + deviceTxt = deviceInfo.getMxId() + listedDevices.append(deviceTxt) + devices[deviceTxt] = deviceInfo window.Element('devices').update("Select device", values=listedDevices) except Exception as ex: - print(f'Exception: {ex}') + PrintException() sg.Popup(f'{ex}') - -def getConfigs(window, bl, devType, device): +def getConfigs(window, bl: dai.DeviceBootloader, devType, device: dai.DeviceInfo): try: conf = bl.readConfig() if conf is not None: @@ -153,8 +230,8 @@ def getConfigs(window, bl, devType, device): window.Element('usbTimeout').update(int(conf.getUsbTimeout().total_seconds() * 1000)) window.Element('usbSpeed').update(str(conf.getUsbMaxSpeed()).split('.')[1]) - window.Element('devName').update(device.desc.name) - window.Element('devNameConf').update(device.desc.name) + window.Element('devName').update(device.name) + window.Element('devNameConf').update(device.getMxId()) window.Element('newBoot').update(dai.DeviceBootloader.getEmbeddedBootloaderVersion()) # The "isEmbeddedVersion" tells you whether BL had to be booted, @@ -166,13 +243,12 @@ def getConfigs(window, bl, devType, device): window.Element('version').update(dai.__version__) window.Element('commit').update(dai.__commit__) - window.Element('devState').update(str(devices[device.desc.name].state).split(".")[1]) + window.Element('devState').update(deviceStateTxt(device.state)) except Exception as ex: - print(f'Exception: {ex}') + PrintException() sg.Popup(f'{ex}') - -def flashBootloader(window, device): +def flashBootloader(window, device: dai.DeviceInfo): # FIXME - to flash bootloader, boot the same device again (from saved device info) but with allowFlashingBootloader = True try: sel = SelectBootloader(['AUTO', 'USB', 'NETWORK'], "Select bootloader type to flash.") @@ -189,11 +265,10 @@ def flashBootloader(window, device): window.Element('currBoot').update(bl.getVersion()) pr.finish("Flashed newest bootloader version.") except Exception as ex: - print(f'Exception: {ex}') + PrintException() sg.Popup(f'{ex}') - -def flashConfig(values, device, devType, staticIp): +def flashConfig(values, device: dai.DeviceInfo, devType: str, staticIp: bool): try: bl = dai.DeviceBootloader(device, True) conf = dai.DeviceBootloader.Config() @@ -229,11 +304,10 @@ def flashConfig(values, device, devType, staticIp): else: sg.Popup("Flashing successful.") except Exception as ex: - print(f'Exception: {ex}') + PrintException() sg.Popup(f'{ex}') - -def factoryReset(device): +def factoryReset(device: dai.DeviceInfo): sel = SelectBootloader(['USB', 'NETWORK'], "Select bootloader type used for factory reset.") ok, type = sel.wait() if not ok: @@ -256,20 +330,20 @@ def factoryReset(device): pr.finish(msg) tmpBlFw.close() except Exception as ex: - print(f'Exception: {ex}') + PrintException() sg.Popup(f'{ex}') -def getDeviceType(bl): +def getDeviceType(bl: dai.DeviceBootloader) -> str: try: if bl.getType() == dai.DeviceBootloader.Type.NETWORK: return "POE" else: return "USB" except Exception as ex: - print(f'Exception: {ex}') + PrintException() sg.Popup(f'{ex}') -def flashFromFile(file, device): +def flashFromFile(file, device: dai.DeviceInfo): try: bl = dai.DeviceBootloader(device, True) if str(file)[-3:] == "dap": @@ -277,40 +351,31 @@ def flashFromFile(file, device): else: sg.Popup("Selected file is not .dap!") except Exception as ex: - print(f'Exception: {ex}') + PrintException() sg.Popup(f'{ex}') - -def flashFromUsb(device): +def flashFromUsb(device: dai.DeviceInfo): try: bl = dai.DeviceBootloader(device, True) bl.bootUsbRomBootloader() except Exception as ex: - print(f'Exception: {ex}') + PrintException() sg.Popup(f'{ex}') - -def connectToDevice(device): +def connectToDevice(device: dai.DeviceInfo) -> dai.DeviceBootloader: try: bl = dai.DeviceBootloader(device, False) return bl except Exception as ex: - print(f'Exception: {ex}') + PrintException() sg.Popup(f'{ex}') -sg.theme('LightGrey2') +def deviceStateTxt(state: dai.XLinkDeviceState) -> str: + return str(state).replace("XLinkDeviceState.X_LINK_", "") -# first device search -allDevices = [] -devices = dict() -tmp = "Search for devices" -deviceInfos = dai.XLinkConnection.getAllConnectedDevices() +# Start defying layout -if deviceInfos: - tmp = "Select device" - for deviceInfo in deviceInfos: - allDevices.append(deviceInfo.desc.name) - devices[deviceInfo.desc.name] = deviceInfo +sg.theme('LightGrey2') # layout for device tab aboutDeviceLayout = [ @@ -323,8 +388,9 @@ def connectToDevice(device): [sg.HSeparator()], [ sg.Text("Select device: ", size=(15, 1), font=('Arial', 10, 'bold'), text_color="black"), - sg.Combo(allDevices, tmp, size=(30, 5), key="devices", enable_events=True), - sg.Button("Search") + sg.Combo([], "Select device", size=(30, 5), key="devices", enable_events=True), + sg.Button("Search", font=('Arial', 10, 'bold')), + sg.Button("Specify IP") ], [ sg.Text("Name of connected device:", size=(30, 1), font=('Arial', 10, 'bold'), text_color="black"), @@ -438,36 +504,68 @@ def connectToDevice(device): devType = "" bl = None -window = sg.Window(title="Device Manager", icon="assets/icon.png", layout=layout, size=(645, 380)) + +window = sg.Window(title="Device Manager", + icon="assets/icon.png", + layout=layout, + size=(645, 380), + finalize=True # So we can do First search for devices + ) + +# First device search +getDevices(window) while True: event, values = window.read() if event == sg.WIN_CLOSED: break dev = values['devices'] + if event == "devices": if dev != "Select device": # "allow flashing bootloader" boots latest bootloader first # which makes the information of current bootloader, etc.. not correct (can be checked by "isEmbeddedVersion") # So leave it to false, uncomment the isEmbeddedVersion below and only boot into latest bootlaoder upon the request to flash new bootloader # bl = dai.DeviceBootloader(devices[values['devices']], False) - if str(devices[values['devices']].state).replace("XLinkDeviceState.X_LINK_", "") == "BOOTED": + device = devices[values['devices']] + if deviceStateTxt(device.state) == "BOOTED": # device is already booted somewhere else sg.Popup("Device is already booted somewhere else!") else: - bl = connectToDevice(devices[values['devices']]) + bl = connectToDevice(device) devType = getDeviceType(bl) - getConfigs(window, bl, devType, devices[values['devices']]) + getConfigs(window, bl, devType, device) unlockConfig(window, devType) else: window.Element('progress').update("No device selected.") - if event == "Search": - getDevices(window, devices) + elif event == "Search": + getDevices(window) # Re-search devices for dropdown lockConfig(window) - if event == "Flash newest Bootloader": + selDev = SearchDevice() + di = selDev.wait() + if di is not None: + window.Element('devices').update(di.getMxId()) + bl = connectToDevice(di) + devType = getDeviceType(bl) + getConfigs(window, bl, devType, di) + unlockConfig(window, devType) + elif event == "Specify IP": + select = SelectIP() + ok, ip = select.wait() + if ok: + di = dai.DeviceInfo(ip) + di.state = dai.XLinkDeviceState.X_LINK_BOOTLOADER + di.protocol = dai.XLinkProtocol.X_LINK_TCP_IP + devices[ip] = di # Add to devices dict + window.Element('devices').update(ip) # Show to user + bl = connectToDevice(di) + devType = getDeviceType(bl) + getConfigs(window, bl, devType, di) + unlockConfig(window, devType) + elif event == "Flash newest Bootloader": bl.close() flashBootloader(window, devices[values['devices']]) - if event == "Flash configuration": + elif event == "Flash configuration": bl.close() flashConfig(values, devices[values['devices']], devType, values['staticBut']) bl = connectToDevice(devices[values['devices']]) @@ -478,20 +576,20 @@ def connectToDevice(device): else: devices.clear() window.Element('devices').update("Search for devices", values=[]) - if event == "Factory reset": + elif event == "Factory reset": bl.close() factoryReset(devices[values['devices']]) - if event == "Flash DAP": + elif event == "Flash DAP": file = sg.popup_get_file("Select .dap file", file_types=(('DepthAI Application Package', '*.dap'), ('All Files', '*.* *'))) bl = None flashFromFile(file, devices[values['devices']]) - if event == "configReal": + elif event == "configReal": window['-COL1-'].update(visible=False) window['-COL2-'].update(visible=True) - if event == "aboutReal": + elif event == "aboutReal": window['-COL2-'].update(visible=False) window['-COL1-'].update(visible=True) - if event == "recoveryMode": + elif event == "recoveryMode": bl = None flashFromUsb(devices[values['devices']]) window.close()