diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4d294de5a..ce844bd22 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -108,7 +108,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, '3.10'] python-architecture: [x64, x86] steps: - name: Cache .hunter folder @@ -159,7 +159,7 @@ jobs: runs-on: macos-latest strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9, '3.10'] steps: - name: Cache .hunter folder uses: actions/cache@v2 @@ -259,7 +259,7 @@ jobs: /opt/python/cp38-cp38/bin/python3.8 setup.py sdist --formats=gztar mv dist/* wheelhouse/audited/ - name: Build wheels - run: for PYBIN in /opt/python/cp3*/bin; do "${PYBIN}/pip" wheel . -w ./wheelhouse/ --verbose; done + run: for PYBIN in /opt/python/cp3{6..10}*/bin; do "${PYBIN}/pip" wheel . -w ./wheelhouse/ --verbose; done - name: Audit wheels run: for whl in wheelhouse/*.whl; do auditwheel repair "$whl" --plat $PLAT -w wheelhouse/audited/; done - name: Archive wheel artifacts @@ -316,7 +316,7 @@ jobs: if: startsWith(github.ref, 'refs/tags/v') != true run: echo "BUILD_COMMIT_HASH=${{github.sha}}" >> $GITHUB_ENV - name: Building wheels - run: for PYBIN in /opt/python/cp3*/bin; do "${PYBIN}/pip" wheel . -w ./wheelhouse/ --verbose; done + run: for PYBIN in /opt/python/cp3{6..10}*/bin; do "${PYBIN}/pip" wheel . -w ./wheelhouse/ --verbose; done - name: Auditing wheels run: for whl in wheelhouse/*.whl; do auditwheel repair "$whl" --plat $PLAT -w wheelhouse/audited/; done - name: Archive wheel artifacts diff --git a/CMakeLists.txt b/CMakeLists.txt index ad1730e14..a94ad9e06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,7 +83,7 @@ find_package(pybind11 CONFIG REQUIRED) # Add files for python module pybind11_add_module(${TARGET_NAME} src/py_bindings.cpp - src/XLinkConnectionBindings.cpp + src/XLinkBindings.cpp src/DeviceBindings.cpp src/CalibrationHandlerBindings.cpp src/DeviceBootloaderBindings.cpp @@ -157,6 +157,7 @@ target_link_libraries(${TARGET_NAME} pybind11::pybind11 depthai::core # Use non-opencv target as we use opencv-python in bindings hedley + pybind11_json ) # Find Git diff --git a/ci/build-wheels.sh b/ci/build-wheels.sh index 9081a3ce3..9f53a546d 100755 --- a/ci/build-wheels.sh +++ b/ci/build-wheels.sh @@ -7,7 +7,7 @@ yum install -y libusb1-devel #yum remove -y libusb1 # Compile wheels -for PYBIN in /opt/python/cp3*/bin; do +for PYBIN in /opt/python/cp3{6..10}*/bin; do #"${PYBIN}/pip" install -r /io/requirements.txt "${PYBIN}/pip" wheel /io/ -w wheelhouse/ done diff --git a/depthai-core b/depthai-core index 57bb84ad2..cb400ab5c 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 57bb84ad209825f181744f2308b8ac6f52a37604 +Subproject commit cb400ab5cfbe979a8da4d15420467ef7e202cdb3 diff --git a/docs/source/components/nodes/script.rst b/docs/source/components/nodes/script.rst index 1e8bc1f0a..a5b5123e2 100644 --- a/docs/source/components/nodes/script.rst +++ b/docs/source/components/nodes/script.rst @@ -91,15 +91,34 @@ Usage Interfacing with GPIOs ###################### -In the script node you can interface with GPIOs of the VPU. Currently supported functions are: +In the script node you can interface with GPIOs of the VPU using module GPIO. Currently supported functions are: .. code-block:: python - import GPIO # module - GPIO.read(pin) - GPIO.write(pin, value) - GPIO.setPwm(pin, highCount, lowCount, repeat=0) # repeat == 0 means indefinite - GPIO.enablePwm(pin, enable) + # Module + import GPIO + + # General + GPIO.setup(gpio, dir, pud, exclusive) + GPIO.release(gpio) + GPIO.write(gpio, value) + GPIO.read(gpio) + + # Interrupts + GPIO.waitInterruptEvent(gpio = -1) # blocks until any interrupt or interrupt by specified gpio is fired. Interrupts with callbacks are ignored here + GPIO.hasInterruptEvent(gpio = -1) # returns whether interrupt happened on any or specfied gpio. Interrupts with callbacks are ignored here + GPIO.setInterrupt(gpio, edge, priority, callback = None) # adds interrupt to specified pin + GPIO.clearInterrupt(gpio) # clears interrupt of specified pin + + # PWM + GPIO.setPwm(gpio, highCount, lowCount, repeat=0) # repeat == 0 means indefinite + GPIO.enablePwm(gpio, enable) + + # Enumerations + GPIO.Direction: GPIO.IN, GPIO.OUT + GPIO.State: GPIO.LOW, GPIO.HIGH + GPIO.PullDownUp: GPIO.PULL_NONE, GPIO.PULL_DOWN, GPIO.PULL_UP + GPIO.Edge: GPIO.RISING, GPIO.FALLING, GPIO.LEVEL_HIGH, GPIO.LEVEL_LOW Using DepthAI :ref:`Messages ` ################################################### diff --git a/examples/bootloader_config.py b/examples/bootloader_config.py new file mode 100755 index 000000000..17c7d0d24 --- /dev/null +++ b/examples/bootloader_config.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +import depthai as dai +import sys + +usage = False +read = True +clear = False +path = '' +if len(sys.argv) >= 2: + op = sys.argv[1] + if op == 'read': + read = True + elif op == 'flash': + read = False + if len(sys.argv) >= 3: + path = sys.argv[2] + elif op == 'clear': + clear = True + read = False + else: + usage = True +else: + usage = True + +if usage: + print(f'Usage: {sys.argv[0]} [read/flash/clear] [flash: path/to/config/json]') + exit(-1) + +(res, info) = dai.DeviceBootloader.getFirstAvailableDevice() + +if res: + print(f'Found device with name: {info.desc.name}'); + with dai.DeviceBootloader(info) as bl: + if read: + print('Current flashed configuration') + print(f'{bl.readConfigData()}') + else: + success = None + error = None + if clear: + (success, error) = bl.flashConfigClear() + else: + if path == '': + (success, error) = bl.flashConfig(dai.DeviceBootloader.Config()) + else: + (success, error) = bl.flashConfigFile(path) + if success: + print('Successfully flashed bootloader configuration') + else: + print(f'Error flashing bootloader configuration: {error}') +else: + print('No devices found') diff --git a/examples/bootloader_version.py b/examples/bootloader_version.py index e5ae3c480..6c370469d 100755 --- a/examples/bootloader_version.py +++ b/examples/bootloader_version.py @@ -5,8 +5,8 @@ (res, info) = dai.DeviceBootloader.getFirstAvailableDevice() if res == True: - print(f'Found device with name: {info.desc.name}'); + print(f'Found device with name: {info.desc.name}') bl = dai.DeviceBootloader(info) - print(f'Version: {bl.getVersion()}'); + print(f'Version: {bl.getVersion()}') else: print('No devices found') diff --git a/examples/depth_preview.py b/examples/depth_preview.py index 1461e963f..0dc6e4365 100755 --- a/examples/depth_preview.py +++ b/examples/depth_preview.py @@ -51,7 +51,7 @@ inDepth = q.get() # blocking call, will wait until a new data has arrived frame = inDepth.getFrame() # Normalization for better visualization - frame = (frame * (255 / depth.getMaxDisparity())).astype(np.uint8) + frame = (frame * (255 / depth.initialConfig.getMaxDisparity())).astype(np.uint8) cv2.imshow("disparity", frame) diff --git a/examples/flash_bootloader.py b/examples/flash_bootloader.py new file mode 100755 index 000000000..7b0e5cd55 --- /dev/null +++ b/examples/flash_bootloader.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +import depthai as dai +import sys +import time + +blType = dai.DeviceBootloader.Type.AUTO +if len(sys.argv) > 1: + if sys.argv[1] == 'usb': + blType = dai.DeviceBootloader.Type.USB + elif sys.argv[1] == 'network': + blType = dai.DeviceBootloader.Type.NETWORK + else: + print("Specify either 'usb' or 'network' bootloader type") + exit() + +(found, info) = dai.DeviceBootloader.getFirstAvailableDevice() +if not found: + print("No device found to flash. Exiting.") + exit(-1) + +hasBootloader = (info.state == dai.XLinkDeviceState.X_LINK_BOOTLOADER) +if hasBootloader: + print("Warning! Flashing bootloader can potentially soft brick your device and should be done with caution.") + print("Do not unplug your device while the bootloader is flashing.") + print("Type 'y' and press enter to proceed, otherwise exits: ") + if input() != 'y': + print("Prompt declined, exiting...") + exit(-1) + +# Open DeviceBootloader and allow flashing bootloader +print(f"Booting latest bootloader first, will take a tad longer...") +with dai.DeviceBootloader(info, allowFlashingBootloader=True) as bl: + currentBlType = bl.getType() + + if blType == dai.DeviceBootloader.Type.AUTO: + blType = currentBlType + + # Check if bootloader type is the same, if already booted by bootloader (not in USB recovery mode) + if currentBlType != blType and hasBootloader: + print(f"Are you sure you want to flash '{blType.name}' bootloader over current '{currentBlType.name}' bootloader?") + print(f"Type 'y' and press enter to proceed, otherwise exits: ") + if input() != 'y': + print("Prompt declined, exiting...") + exit(-1) + + # Create a progress callback lambda + progress = lambda p : print(f'Flashing progress: {p*100:.1f}%') + + print(f"Flashing {blType.name} bootloader...") + startTime = time.monotonic() + (res, message) = bl.flashBootloader(dai.DeviceBootloader.Memory.FLASH, blType, progress) + if res: + print("Flashing successful. Took", time.monotonic() - startTime, "seconds") + else: + print("Flashing failed:", message) diff --git a/examples/mono_depth_mobilenetssd.py b/examples/mono_depth_mobilenetssd.py index 2b1df8c1c..613dbe889 100755 --- a/examples/mono_depth_mobilenetssd.py +++ b/examples/mono_depth_mobilenetssd.py @@ -19,8 +19,6 @@ labelMap = ["background", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"] -flipRectified = True - # Create pipeline pipeline = dai.Pipeline() @@ -97,7 +95,7 @@ def show(name, frame): # Show the frame cv2.imshow(name, frame) - disparityMultiplier = 255 / stereo.getMaxDisparity() + disparityMultiplier = 255 / stereo.initialConfig.getMaxDisparity() while True: # Instead of get (blocking), we use tryGet (nonblocking) which will return the available data or None otherwise @@ -107,16 +105,6 @@ def show(name, frame): if inRight is not None: rightFrame = inRight.getCvFrame() - if flipRectified: - rightFrame = cv2.flip(rightFrame, 1) - - if inDet is not None: - detections = inDet.detections - if flipRectified: - for detection in detections: - swap = detection.xmin - detection.xmin = 1 - detection.xmax - detection.xmax = 1 - swap if inDisparity is not None: # Frame is transformed, normalized, and color map will be applied to highlight the depth info diff --git a/examples/rgb_depth_aligned.py b/examples/rgb_depth_aligned.py index 2e2c7f3e7..012cc6e8b 100755 --- a/examples/rgb_depth_aligned.py +++ b/examples/rgb_depth_aligned.py @@ -82,7 +82,7 @@ if latestPacket["depth"] is not None: frameDepth = latestPacket["depth"].getFrame() - maxDisparity = stereo.getMaxDisparity() + maxDisparity = stereo.initialConfig.getMaxDisparity() # Optional, extend range 0..95 -> 0..255, for a better visualisation if 1: frameDepth = (frameDepth * 255. / maxDisparity).astype(np.uint8) # Optional, apply false colorization diff --git a/examples/rgb_encoding_mono_mobilenet_depth.py b/examples/rgb_encoding_mono_mobilenet_depth.py index d75147b40..d900ad807 100755 --- a/examples/rgb_encoding_mono_mobilenet_depth.py +++ b/examples/rgb_encoding_mono_mobilenet_depth.py @@ -19,8 +19,6 @@ labelMap = ["background", "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"] -flipRectified = True - # Create pipeline pipeline = dai.Pipeline() @@ -54,9 +52,7 @@ monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) videoEncoder.setDefaultProfilePreset(1920, 1080, 30, dai.VideoEncoderProperties.Profile.H265_MAIN) -# Note: the rectified streams are horizontally mirrored by default depth.initialConfig.setConfidenceThreshold(255) -depth.setRectifyMirrorFrame(False) depth.setRectifyEdgeFillColor(0) # Black, to better see the cutout nn.setConfidenceThreshold(0.5) @@ -81,7 +77,7 @@ nn.out.link(nnOut.input) # Disparity range is used for normalization -disparityMultiplier = 255 / depth.getMaxDisparity() +disparityMultiplier = 255 / depth.initialConfig.getMaxDisparity() # Connect to device and start pipeline with dai.Device(pipeline) as device: @@ -126,10 +122,8 @@ def frameNorm(frame, bbox): frameManip = inManip.getCvFrame() if inDisparity is not None: - # Flip disparity frame, normalize it and apply color map for better visualization + # Apply color map for better visualization frameDisparity = inDisparity.getCvFrame() - if flipRectified: - frameDisparity = cv2.flip(frameDisparity, 1) frameDisparity = (frameDisparity*disparityMultiplier).astype(np.uint8) frameDisparity = cv2.applyColorMap(frameDisparity, cv2.COLORMAP_JET) diff --git a/examples/rgb_preview.py b/examples/rgb_preview.py index 45eea1a10..8484547a9 100755 --- a/examples/rgb_preview.py +++ b/examples/rgb_preview.py @@ -20,9 +20,9 @@ # Linking camRgb.preview.link(xoutRgb.input) -# Connect to device and start pipeline -with dai.Device(pipeline) as device: - +# Connect to the device +with dai.Device(pipeline, dai.UsbSpeed.SUPER) as device: + # Print out available cameras print('Connected cameras: ', device.getConnectedCameras()) # Print out usb speed print('Usb speed: ', device.getUsbSpeed().name) @@ -31,7 +31,7 @@ qRgb = device.getOutputQueue(name="rgb", maxSize=4, blocking=False) while True: - inRgb = qRgb.get() # blocking call, will wait until a new data has arrived + inRgb = qRgb.get() # blocking call, will wait until a new data has arrived # Retrieve 'bgr' (opencv format) frame cv2.imshow("rgb", inRgb.getCvFrame()) diff --git a/examples/spatial_location_calculator.py b/examples/spatial_location_calculator.py index 26825d95b..9f672e972 100755 --- a/examples/spatial_location_calculator.py +++ b/examples/spatial_location_calculator.py @@ -125,6 +125,7 @@ if newConfig: config.roi = dai.Rect(topLeft, bottomRight) + config.calculationAlgorithm = dai.SpatialLocationCalculatorAlgorithm.AVERAGE cfg = dai.SpatialLocationCalculatorConfig() cfg.addROI(config) spatialCalcConfigInQueue.send(cfg) diff --git a/examples/spatial_mobilenet_mono.py b/examples/spatial_mobilenet_mono.py index 8dc1c03ec..38eda03fe 100755 --- a/examples/spatial_mobilenet_mono.py +++ b/examples/spatial_mobilenet_mono.py @@ -28,7 +28,6 @@ "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"] syncNN = True -flipRectified = True # Create pipeline pipeline = dai.Pipeline() @@ -118,8 +117,6 @@ startTime = currentTime rectifiedRight = inRectified.getCvFrame() - if flipRectified: - rectifiedRight = cv2.flip(rectifiedRight, 1) depthFrame = inDepth.getFrame() depthFrameColor = cv2.normalize(depthFrame, None, 255, 0, cv2.NORM_INF, cv2.CV_8UC1) @@ -146,10 +143,6 @@ height = rectifiedRight.shape[0] width = rectifiedRight.shape[1] for detection in detections: - if flipRectified: - swap = detection.xmin - detection.xmin = 1 - detection.xmax - detection.xmax = 1 - swap # Denormalize bounding box x1 = int(detection.xmin * width) x2 = int(detection.xmax * width) diff --git a/examples/stereo_depth_from_host.py b/examples/stereo_depth_from_host.py index 70f4a88ee..acacc4609 100755 --- a/examples/stereo_depth_from_host.py +++ b/examples/stereo_depth_from_host.py @@ -7,203 +7,312 @@ import datetime import argparse from pathlib import Path +import math datasetDefault = str((Path(__file__).parent / Path('models/dataset')).resolve().absolute()) parser = argparse.ArgumentParser() parser.add_argument('-dataset', nargs='?', help="Path to recorded frames", default=datasetDefault) +parser.add_argument('-debug', "--debug", action="store_true", help="Enable debug outputs.") +parser.add_argument('-dumpdispcost', "--dumpdisparitycostvalues", action="store_true", help="Dumps the disparity cost values for each disparity range. 96 byte for each pixel.") args = parser.parse_args() +if args.debug and args.dumpdisparitycostvalues: + print("-debug and --dumpdisparitycostvalues are mutually exclusive!") + exit(1) + if not Path(datasetDefault).exists(): import sys raise FileNotFoundError(f'Required file/s not found, please run "{sys.executable} install_requirements.py"') -class trackbar: - def __init__(self, trackbarName, windowName, minValue, maxValue, defaultValue, handler): - cv2.createTrackbar(trackbarName, windowName, minValue, maxValue, handler) - cv2.setTrackbarPos(trackbarName, windowName, defaultValue) - -class depthHandler: - depthStream = "depth" - _send_new_config = False - currentConfig = dai.StereoDepthConfig() - - def on_trackbar_change_sigma(self, value): - self._sigma = value - self._send_new_config = True - - def on_trackbar_change_confidence(self, value): - self._confidence = value - self._send_new_config = True - - def on_trackbar_change_lr_threshold(self, value): - self._lrCheckThreshold = value - self._send_new_config = True - def handleKeypress(self, key, stereoDepthConfigInQueue): +class StereoConfigHandler: + + class Trackbar: + def __init__(self, trackbarName, windowName, minValue, maxValue, defaultValue, handler): + self.min = minValue + self.max = maxValue + self.windowName = windowName + self.trackbarName = trackbarName + cv2.createTrackbar(trackbarName, windowName, minValue, maxValue, handler) + cv2.setTrackbarPos(trackbarName, windowName, defaultValue) + + def set(self, value): + if value < self.min: + value = self.min + print(f'{self.trackbarName} min value is {self.min}') + if value > self.max: + value = self.max + print(f'{self.trackbarName} max value is {self.max}') + cv2.setTrackbarPos(self.trackbarName, self.windowName, value) + + newConfig = False + config = None + trSigma = list() + trConfidence = list() + trLrCheck = list() + trFractionalBits = list() + trLineqAlpha = list() + trLineqBeta = list() + trLineqThreshold = list() + + def trackbarSigma(value): + StereoConfigHandler.config.postProcessing.bilateralSigmaValue = value + StereoConfigHandler.newConfig = True + for tr in StereoConfigHandler.trSigma: + tr.set(value) + + def trackbarConfidence(value): + StereoConfigHandler.config.costMatching.confidenceThreshold = value + StereoConfigHandler.newConfig = True + for tr in StereoConfigHandler.trConfidence: + tr.set(value) + + def trackbarLrCheckThreshold(value): + StereoConfigHandler.config.algorithmControl.leftRightCheckThreshold = value + StereoConfigHandler.newConfig = True + for tr in StereoConfigHandler.trLrCheck: + tr.set(value) + + def trackbarFractionalBits(value): + StereoConfigHandler.config.algorithmControl.subpixelFractionalBits = value + for tr in StereoConfigHandler.trFractionalBits: + tr.set(value) + + def trackbarLineqAlpha(value): + StereoConfigHandler.config.costMatching.linearEquationParameters.alpha = value + StereoConfigHandler.newConfig = True + for tr in StereoConfigHandler.trLineqAlpha: + tr.set(value) + + def trackbarLineqBeta(value): + StereoConfigHandler.config.costMatching.linearEquationParameters.beta = value + StereoConfigHandler.newConfig = True + for tr in StereoConfigHandler.trLineqBeta: + tr.set(value) + + def trackbarLineqThreshold(value): + StereoConfigHandler.config.costMatching.linearEquationParameters.threshold = value + StereoConfigHandler.newConfig = True + for tr in StereoConfigHandler.trLineqThreshold: + tr.set(value) + + def handleKeypress(key, stereoDepthConfigInQueue): if key == ord('m'): - self._send_new_config = True + StereoConfigHandler.newConfig = True medianSettings = [dai.MedianFilter.MEDIAN_OFF, dai.MedianFilter.KERNEL_3x3, dai.MedianFilter.KERNEL_5x5, dai.MedianFilter.KERNEL_7x7] - currentMedian = self.currentConfig.getMedianFilter() - # circle through median settins + currentMedian = StereoConfigHandler.config.postProcessing.median nextMedian = medianSettings[(medianSettings.index(currentMedian)+1) % len(medianSettings)] - self.currentConfig.setMedianFilter(nextMedian) print(f"Changing median to {nextMedian.name} from {currentMedian.name}") - self.sendConfig(stereoDepthConfigInQueue) - - def __init__(self, _confidence, _sigma, _lrCheckThreshold): + StereoConfigHandler.config.postProcessing.median = nextMedian + elif key == ord('c'): + StereoConfigHandler.newConfig = True + censusSettings = [dai.RawStereoDepthConfig.CensusTransform.KernelSize.AUTO, dai.RawStereoDepthConfig.CensusTransform.KernelSize.KERNEL_5x5, dai.RawStereoDepthConfig.CensusTransform.KernelSize.KERNEL_7x7, dai.RawStereoDepthConfig.CensusTransform.KernelSize.KERNEL_7x9] + currentCensus = StereoConfigHandler.config.censusTransform.kernelSize + nextCensus = censusSettings[(censusSettings.index(currentCensus)+1) % len(censusSettings)] + print(f"Changing census transform to {nextCensus.name} from {currentCensus.name}") + StereoConfigHandler.config.censusTransform.kernelSize = nextCensus + elif key == ord('d'): + StereoConfigHandler.newConfig = True + dispRangeSettings = [dai.RawStereoDepthConfig.CostMatching.DisparityWidth.DISPARITY_64, dai.RawStereoDepthConfig.CostMatching.DisparityWidth.DISPARITY_96] + currentDispRange = StereoConfigHandler.config.costMatching.disparityWidth + nextDispRange = dispRangeSettings[(dispRangeSettings.index(currentDispRange)+1) % len(dispRangeSettings)] + print(f"Changing disparity range to {nextDispRange.name} from {currentDispRange.name}") + StereoConfigHandler.config.costMatching.disparityWidth = nextDispRange + elif key == ord('f'): + StereoConfigHandler.newConfig = True + StereoConfigHandler.config.costMatching.enableCompanding = not StereoConfigHandler.config.costMatching.enableCompanding + state = "on" if StereoConfigHandler.config.costMatching.enableCompanding else "off" + print(f"Companding {state}") + elif key == ord('v'): + StereoConfigHandler.newConfig = True + StereoConfigHandler.config.censusTransform.enableMeanMode = not StereoConfigHandler.config.censusTransform.enableMeanMode + state = "on" if StereoConfigHandler.config.censusTransform.enableMeanMode else "off" + print(f"Census transform mean mode {state}") + elif key == ord('1'): + StereoConfigHandler.newConfig = True + StereoConfigHandler.config.algorithmControl.enableLeftRightCheck = not StereoConfigHandler.config.algorithmControl.enableLeftRightCheck + state = "on" if StereoConfigHandler.config.algorithmControl.enableLeftRightCheck else "off" + print(f"LR-check {state}") + elif key == ord('2'): + StereoConfigHandler.newConfig = True + StereoConfigHandler.config.algorithmControl.enableSubpixel = not StereoConfigHandler.config.algorithmControl.enableSubpixel + state = "on" if StereoConfigHandler.config.algorithmControl.enableSubpixel else "off" + print(f"Subpixel {state}") + + StereoConfigHandler.sendConfig(stereoDepthConfigInQueue) + + def sendConfig(stereoDepthConfigInQueue): + if StereoConfigHandler.newConfig: + StereoConfigHandler.newConfig = False + configMessage = dai.StereoDepthConfig() + configMessage.set(StereoConfigHandler.config) + stereoDepthConfigInQueue.send(configMessage) + + def registerWindow(stream): + cv2.namedWindow(stream) + StereoConfigHandler.trConfidence.append(StereoConfigHandler.Trackbar('Disparity confidence', stream, 0, 255, StereoConfigHandler.config.costMatching.confidenceThreshold, StereoConfigHandler.trackbarConfidence)) + StereoConfigHandler.trSigma.append(StereoConfigHandler.Trackbar('Bilateral sigma', stream, 0, 100, StereoConfigHandler.config.postProcessing.bilateralSigmaValue, StereoConfigHandler.trackbarSigma)) + 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.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)) + + def __init__(self, config): print("Control median filter using the 'm' key.") - print("Use slider to adjust disparity confidence.") - print("Use slider to adjust bilateral filter intensity.") - print("Use slider to adjust left-right check threshold.") - - self._confidence = _confidence - self._sigma = _sigma - self._lrCheckThreshold = _lrCheckThreshold - cv2.namedWindow(self.depthStream) - self.lambdaTrackbar = trackbar('Disparity confidence', self.depthStream, 0, 255, _confidence, self.on_trackbar_change_confidence) - self.sigmaTrackbar = trackbar('Bilateral sigma', self.depthStream, 0, 250, _sigma, self.on_trackbar_change_sigma) - self.lrcheckTrackbar = trackbar('LR-check threshold', self.depthStream, 0, 10, _lrCheckThreshold, self.on_trackbar_change_lr_threshold) - - def imshow(self, frame): - cv2.imshow(self.depthStream, frame) - - def sendConfig(self, stereoDepthConfigInQueue): - if self._send_new_config: - self._send_new_config = False - self.currentConfig.setConfidenceThreshold(self._confidence) - self.currentConfig.setBilateralFilterSigma(self._sigma) - self.currentConfig.setLeftRightCheckThreshold(self._lrCheckThreshold) - stereoDepthConfigInQueue.send(self.currentConfig) - -# StereoDepth config options. -out_depth = True # Disparity by default -out_rectified = True # Output and display rectified streams + print("Control census transform kernel size using the 'c' key.") + print("Control disparity search range using the 'd' key.") + print("Control disparity companding using the 'f' key.") + print("Control census transform mean mode using the 'v' key.") + print("Control left-right check mode using the '1' key.") + print("Control subpixel mode using the '2' key.") + + StereoConfigHandler.config = config + + +# StereoDepth initial config options. +outDepth = True # Disparity by default +outConfidenceMap = True # Output disparity confidence map +outRectified = True # Output and display rectified streams lrcheck = True # Better handling for occlusions -extended = False # Closer-in minimum depth, disparity range is doubled -subpixel = False # Better accuracy for longer distance, fractional disparity 32-levels -median = dai.MedianFilter.KERNEL_7x7 - -# Sanitize some incompatible options -if extended or subpixel: - median = dai.MedianFilter.MEDIAN_OFF - -depth_handler = None - -print("StereoDepth config options: ") -print("Left-Right check: ", lrcheck) -print("Extended disparity: ", extended) -print("Subpixel: ", subpixel) -print("Median filtering: ", median) - -right_intrinsic = [[860.0, 0.0, 640.0], [0.0, 860.0, 360.0], [0.0, 0.0, 1.0]] - -def create_stereo_depth_pipeline(): - print("Creating Stereo Depth pipeline: ", end='') - - print("XLINK IN -> STEREO -> XLINK OUT") - pipeline = dai.Pipeline() - - monoLeft = pipeline.createXLinkIn() - monoRight = pipeline.createXLinkIn() - xinStereoDepthConfig = pipeline.createXLinkIn() - - stereo = pipeline.createStereoDepth() - xoutLeft = pipeline.createXLinkOut() - xoutRight = pipeline.createXLinkOut() - xoutDepth = pipeline.createXLinkOut() - xoutDisparity = pipeline.createXLinkOut() - xoutRectifLeft = pipeline.createXLinkOut() - xoutRectifRight = pipeline.createXLinkOut() - - xinStereoDepthConfig.setStreamName("stereoDepthConfig") - monoLeft.setStreamName('in_left') - monoRight.setStreamName('in_right') - - stereo.initialConfig.setConfidenceThreshold(200) - stereo.setRectifyEdgeFillColor(0) # Black, to better see the cutout - stereo.initialConfig.setMedianFilter(median) # KERNEL_7x7 default - stereo.setLeftRightCheck(lrcheck) - stereo.setExtendedDisparity(extended) - stereo.setSubpixel(subpixel) - xinStereoDepthConfig.out.link(stereo.inputConfig) - global depth_handler - _confidence=stereo.initialConfig.getConfidenceThreshold() - _sigma=stereo.initialConfig.getBilateralFilterSigma() - _lrCheckThreshold=stereo.initialConfig.getLeftRightCheckThreshold() - depth_handler = depthHandler(_confidence, _sigma, _lrCheckThreshold) - - - stereo.setInputResolution(1280, 720) - stereo.setRectification(False) - - xoutLeft.setStreamName('left') - xoutRight.setStreamName('right') - xoutDepth.setStreamName('depth') - xoutDisparity.setStreamName('disparity') - xoutRectifLeft.setStreamName('rectified_left') - xoutRectifRight.setStreamName('rectified_right') - - monoLeft.out.link(stereo.left) - monoRight.out.link(stereo.right) - stereo.syncedLeft.link(xoutLeft.input) - stereo.syncedRight.link(xoutRight.input) - if out_depth: - stereo.depth.link(xoutDepth.input) - stereo.disparity.link(xoutDisparity.input) - if out_rectified: - stereo.rectifiedLeft.link(xoutRectifLeft.input) - stereo.rectifiedRight.link(xoutRectifRight.input) - - streams = ['left', 'right'] - if out_rectified: - streams.extend(['rectified_left', 'rectified_right']) - streams.extend(['disparity', 'depth']) - - return pipeline, streams - -def convert_to_cv2_frame(name, image): - baseline = 75 #mm - focal = right_intrinsic[0][0] - max_disp = 96 - disp_type = np.uint8 - disp_levels = 1 - if (extended): - max_disp *= 2 - if (subpixel): - max_disp *= 32 - disp_type = np.uint16 - disp_levels = 32 - - data, w, h = image.getData(), image.getWidth(), image.getHeight() +extended = False # Closer-in minimum depth, disparity range is doubled. Unsupported for now. +subpixel = True # Better accuracy for longer distance, fractional disparity 32-levels + +width = 1280 +height = 800 + +xoutStereoCfg = None + +# Create pipeline +pipeline = dai.Pipeline() + +# Define sources and outputs +stereo = pipeline.createStereoDepth() + +monoLeft = pipeline.createXLinkIn() +monoRight = pipeline.createXLinkIn() +xinStereoDepthConfig = pipeline.createXLinkIn() + +xoutLeft = pipeline.createXLinkOut() +xoutRight = pipeline.createXLinkOut() +xoutDepth = pipeline.createXLinkOut() +xoutConfMap = pipeline.createXLinkOut() +xoutDisparity = pipeline.createXLinkOut() +xoutRectifLeft = pipeline.createXLinkOut() +xoutRectifRight = pipeline.createXLinkOut() +xoutStereoCfg = pipeline.createXLinkOut() +if args.debug: + xoutDebugLrCheckIt1 = pipeline.createXLinkOut() + xoutDebugLrCheckIt2 = pipeline.createXLinkOut() +if args.dumpdisparitycostvalues: + xoutDebugCostDump = pipeline.createXLinkOut() + +xinStereoDepthConfig.setStreamName("stereoDepthConfig") +monoLeft.setStreamName('in_left') +monoRight.setStreamName('in_right') + +xoutLeft.setStreamName('left') +xoutRight.setStreamName('right') +xoutDepth.setStreamName('depth') +xoutConfMap.setStreamName('confidence_map') +xoutDisparity.setStreamName('disparity') +xoutRectifLeft.setStreamName('rectified_left') +xoutRectifRight.setStreamName('rectified_right') +xoutStereoCfg.setStreamName('stereo_cfg') +if args.debug: + xoutDebugLrCheckIt1.setStreamName('disparity_lr_check_iteration1') + xoutDebugLrCheckIt2.setStreamName('disparity_lr_check_iteration2') +if args.dumpdisparitycostvalues: + xoutDebugCostDump.setStreamName('disparity_cost_dump') + +# Properties +stereo.initialConfig.setConfidenceThreshold(220) +stereo.setRectifyEdgeFillColor(0) # Black, to better see the cutout +stereo.setLeftRightCheck(lrcheck) +stereo.setExtendedDisparity(extended) +stereo.setSubpixel(subpixel) +# allocates resources for worst case scenario +# allowing runtime switch of stereo modes +stereo.setRuntimeModeSwitch(True) + +# Linking +monoLeft.out.link(stereo.left) +monoRight.out.link(stereo.right) +xinStereoDepthConfig.out.link(stereo.inputConfig) +stereo.syncedLeft.link(xoutLeft.input) +stereo.syncedRight.link(xoutRight.input) +if outDepth: + stereo.depth.link(xoutDepth.input) +if outConfidenceMap: + stereo.confidenceMap.link(xoutConfMap.input) +stereo.disparity.link(xoutDisparity.input) +if outRectified: + stereo.rectifiedLeft.link(xoutRectifLeft.input) + stereo.rectifiedRight.link(xoutRectifRight.input) +stereo.outConfig.link(xoutStereoCfg.input) +if args.debug: + stereo.debugDispLrCheckIt1.link(xoutDebugLrCheckIt1.input) + stereo.debugDispLrCheckIt2.link(xoutDebugLrCheckIt2.input) +if args.dumpdisparitycostvalues: + stereo.debugDispCostDump.link(xoutDebugCostDump.input) + +StereoConfigHandler(stereo.initialConfig.get()) +StereoConfigHandler.registerWindow('disparity') +if outDepth: + StereoConfigHandler.registerWindow('depth') + +stereo.setInputResolution(width, height) +stereo.setRectification(False) +baseline = 75 +fov = 71.86 +focal = width / (2 * math.tan(fov / 2 / 180 * math.pi)) + +streams = ['left', 'right'] +if outRectified: + streams.extend(['rectified_left', 'rectified_right']) +streams.append('disparity') +if outDepth: + streams.append('depth') +if outConfidenceMap: + streams.append('confidence_map') +debugStreams = [] +if args.debug: + debugStreams.extend(['disparity_lr_check_iteration1', 'disparity_lr_check_iteration2']) +if args.dumpdisparitycostvalues: + debugStreams.append('disparity_cost_dump') + +def convertToCv2Frame(name, image, config): + + maxDisp = config.getMaxDisparity() + subpixelLevels = pow(2, config.get().algorithmControl.subpixelFractionalBits) + subpixel = config.get().algorithmControl.enableSubpixel + dispIntegerLevels = maxDisp if not subpixel else maxDisp / subpixelLevels + + frame = image.getFrame() + + # frame.tofile(name+".raw") + if name == 'depth': - depthFrame = image.getFrame() - depthFrameColor = cv2.normalize(depthFrame, None, 255, 0, cv2.NORM_INF, cv2.CV_8UC1) - depthFrameColor = cv2.equalizeHist(depthFrameColor) - depthFrameColor = cv2.applyColorMap(depthFrameColor, cv2.COLORMAP_HOT) - frame = depthFrameColor - elif name == 'disparity': - disp = np.array(data).astype(np.uint8).view(disp_type).reshape((h, w)) - - # Compute depth from disparity + dispScaleFactor = baseline * focal with np.errstate(divide='ignore'): - depth = (disp_levels * baseline * focal / disp).astype(np.uint16) - + frame = dispScaleFactor / frame + + frame = (frame * 255. / dispIntegerLevels).astype(np.uint8) + frame = cv2.applyColorMap(frame, cv2.COLORMAP_HOT) + elif 'confidence_map' in name: + pass + elif name == 'disparity_cost_dump': + # frame.tofile(name+'.raw') + pass + elif 'disparity' in name: if 1: # Optionally, extend disparity range to better visualize it - frame = (disp * 255. / max_disp).astype(np.uint8) + frame = (frame * 255. / maxDisp).astype(np.uint8) if 1: # Optionally, apply a color map frame = cv2.applyColorMap(frame, cv2.COLORMAP_HOT) - else: # mono streams / single channel - frame = np.array(data).reshape((h, w)).astype(np.uint8) - if name.startswith('rectified_'): - frame = cv2.flip(frame, 1) - if name == 'rectified_right': - last_rectif_right = frame return frame -pipeline, streams = create_stereo_depth_pipeline() - print("Connecting and starting the pipeline") # Connect to device and start pipeline with dai.Device(pipeline) as device: @@ -218,10 +327,19 @@ def convert_to_cv2_frame(name, image): # Create a receive queue for each stream q_list = [] + q_list_debug = [] for s in streams: q = device.getOutputQueue(s, 8, blocking=False) q_list.append(q) + if args.debug or args.dumpdisparitycostvalues: + q_list_debug = q_list.copy() + for s in debugStreams: + q = device.getOutputQueue(s, 8, blocking=False) + q_list_debug.append(q) + + inCfg = device.getOutputQueue("stereo_cfg", 8, blocking=False) + # Need to set a timestamp for input frames, for the sync stage in Stereo node timestamp_ms = 0 index = 0 @@ -229,10 +347,12 @@ def convert_to_cv2_frame(name, image): # Handle input streams, if any if in_q_list: dataset_size = 1 # Number of image pairs - frame_interval_ms = 500 + frame_interval_ms = 50 for i, q in enumerate(in_q_list): path = args.dataset + '/' + str(index) + '/' + q.getName() + '.png' - data = cv2.imread(path, cv2.IMREAD_GRAYSCALE).reshape(720*1280) + data = cv2.imread(path, cv2.IMREAD_GRAYSCALE) + data = cv2.resize(data, (width, height), interpolation = cv2.INTER_AREA) + data = data.reshape(height*width) tstamp = datetime.timedelta(seconds = timestamp_ms // 1000, milliseconds = timestamp_ms % 1000) img = dai.ImgFrame() @@ -240,25 +360,36 @@ def convert_to_cv2_frame(name, image): img.setTimestamp(tstamp) img.setInstanceNum(inStreamsCameraID[i]) img.setType(dai.ImgFrame.Type.RAW8) - img.setWidth(1280) - img.setHeight(720) + 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) + # print("Sent frame: {:25s}".format(path), 'timestamp_ms:', timestamp_ms) timestamp_ms += frame_interval_ms index = (index + 1) % dataset_size sleep(frame_interval_ms / 1000) + # Handle output streams - for q in q_list: + currentConfig = inCfg.get() + + lrCheckEnabled = currentConfig.get().algorithmControl.enableLeftRightCheck + queues = q_list + + if (args.debug and lrCheckEnabled) or args.dumpdisparitycostvalues: + queues = q_list_debug + else: + for s in debugStreams: + cv2.destroyWindow(s) + + for q in queues: if q.getName() in ['left', 'right']: continue - frame = convert_to_cv2_frame(q.getName(), q.get()) - if q.getName() == 'depth': - depth_handler.imshow(frame) - else: - cv2.imshow(q.getName(), frame) + data = q.get() + frame = convertToCv2Frame(q.getName(), data, currentConfig) + cv2.imshow(q.getName(), frame) key = cv2.waitKey(1) if key == ord('q'): break - depth_handler.handleKeypress(key, stereoDepthConfigInQueue) + + StereoConfigHandler.handleKeypress(key, stereoDepthConfigInQueue) diff --git a/examples/stereo_depth_video.py b/examples/stereo_depth_video.py old mode 100755 new mode 100644 index 11d81fc77..2a54042d1 --- a/examples/stereo_depth_video.py +++ b/examples/stereo_depth_video.py @@ -1,116 +1,264 @@ #!/usr/bin/env python3 import cv2 -import depthai as dai import numpy as np +import depthai as dai +import argparse -withDepth = True +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.", +) +parser.add_argument( + "-rect", + "--out_rectified", + default=False, + action="store_true", + help="Generate and display rectified streams", +) +parser.add_argument( + "-lr", + "--lrcheck", + default=False, + action="store_true", + help="Better handling for occlusions", +) +parser.add_argument( + "-e", + "--extended", + default=False, + action="store_true", + help="Closer-in minimum depth, disparity range is doubled", +) +parser.add_argument( + "-s", + "--subpixel", + default=False, + action="store_true", + help="Better accuracy for longer distance, fractional disparity 32-levels", +) +parser.add_argument( + "-m", + "--median", + type=str, + default="7x7", + help="Choose the size of median filtering. Options: OFF | 3x3 | 5x5 | 7x7 (default)", +) +parser.add_argument( + "-d", + "--depth", + default=False, + action="store_true", + help="Display depth frames", +) +args = parser.parse_args() -outputDepth = False -outputRectified = True -lrcheck = True -extended = False -subpixel = False +resolutionMap = {"800": (1280, 800), "720": (1280, 720), "400": (640, 400)} +if args.resolution not in resolutionMap: + exit("Unsupported resolution!") + +resolution = resolutionMap[args.resolution] +meshDirectory = args.mesh_dir # Output dir for mesh files +generateMesh = args.load_mesh # Load mesh files + +outRectified = args.out_rectified # Output and display rectified streams +lrcheck = args.lrcheck # Better handling for occlusions +extended = args.extended # Closer-in minimum depth, disparity range is doubled +subpixel = args.subpixel # Better accuracy for longer distance, fractional disparity 32-levels +depth = args.depth # Display depth frames + +medianMap = { + "OFF": dai.StereoDepthProperties.MedianFilter.MEDIAN_OFF, + "3x3": dai.StereoDepthProperties.MedianFilter.KERNEL_3x3, + "5x5": dai.StereoDepthProperties.MedianFilter.KERNEL_5x5, + "7x7": dai.StereoDepthProperties.MedianFilter.KERNEL_7x7, +} +if args.median not in medianMap: + exit("Unsupported median size!") + +median = medianMap[args.median] + +print("StereoDepth config options:") +print(" Resolution: ", resolution) +print(" Left-Right check: ", lrcheck) +print(" Extended disparity:", extended) +print(" Subpixel: ", subpixel) +print(" Median filtering: ", median) +print(" Generating mesh files: ", generateMesh) +print(" Outputting mesh files to: ", meshDirectory) + + +def getMesh(calibData): + M1 = np.array(calibData.getCameraIntrinsics(dai.CameraBoardSocket.LEFT, resolution[0], resolution[1])) + d1 = np.array(calibData.getDistortionCoefficients(dai.CameraBoardSocket.LEFT)) + 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)) + 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) + + 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 -# Create pipeline -pipeline = dai.Pipeline() -# Define sources and outputs -monoLeft = pipeline.createMonoCamera() -monoRight = pipeline.createMonoCamera() -if withDepth: - stereo = pipeline.createStereoDepth() +def saveMeshFiles(meshLeft, meshRight, outputPath): + print("Saving mesh to:", outputPath) + meshLeft.tofile(outputPath + "/left_mesh.calib") + meshRight.tofile(outputPath + "/right_mesh.calib") + +def getDisparityFrame(frame): + maxDisp = stereo.initialConfig.getMaxDisparity() + disp = (frame * (255.0 / maxDisp)).astype(np.uint8) + disp = cv2.applyColorMap(disp, cv2.COLORMAP_JET) + + return disp + + +print("Creating Stereo Depth pipeline") +pipeline = dai.Pipeline() + +camLeft = pipeline.createMonoCamera() +camRight = pipeline.createMonoCamera() +stereo = pipeline.createStereoDepth() xoutLeft = pipeline.createXLinkOut() xoutRight = pipeline.createXLinkOut() -xoutDisp = pipeline.createXLinkOut() +xoutDisparity = pipeline.createXLinkOut() xoutDepth = pipeline.createXLinkOut() -xoutRectifL = pipeline.createXLinkOut() -xoutRectifR = pipeline.createXLinkOut() +xoutRectifLeft = pipeline.createXLinkOut() +xoutRectifRight = pipeline.createXLinkOut() + +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.setFps(20.0) + +stereo.initialConfig.setConfidenceThreshold(200) +stereo.initialConfig.setMedianFilter(median) # KERNEL_7x7 default +stereo.setRectifyEdgeFillColor(0) # Black, to better see the cutout +stereo.setLeftRightCheck(lrcheck) +# FIXME: RuntimeError: StereoDepth(2) - StereoDepth | ExtendedDisparity is not implemented yet. +stereo.setExtendedDisparity(extended) +stereo.setSubpixel(subpixel) -# XLinkOut xoutLeft.setStreamName("left") xoutRight.setStreamName("right") -if withDepth: - xoutDisp.setStreamName("disparity") - xoutDepth.setStreamName("depth") - xoutRectifL.setStreamName("rectified_left") - xoutRectifR.setStreamName("rectified_right") - -# Properties -monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) -monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) -monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) -monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) - -if withDepth: - # StereoDepth - stereo.initialConfig.setConfidenceThreshold(230) - stereo.setRectifyEdgeFillColor(0) # black, to better see the cutout - # stereo.setInputResolution(1280, 720) - stereo.initialConfig.setMedianFilter(dai.MedianFilter.KERNEL_5x5) - stereo.setLeftRightCheck(lrcheck) - stereo.setExtendedDisparity(extended) - stereo.setSubpixel(subpixel) - - # Linking - monoLeft.out.link(stereo.left) - monoRight.out.link(stereo.right) - - stereo.syncedLeft.link(xoutLeft.input) - stereo.syncedRight.link(xoutRight.input) - stereo.disparity.link(xoutDisp.input) - - if outputRectified: - stereo.rectifiedLeft.link(xoutRectifL.input) - stereo.rectifiedRight.link(xoutRectifR.input) - - if outputDepth: - stereo.depth.link(xoutDepth.input) - -else: - # Link plugins CAM . XLINK - monoLeft.out.link(xoutLeft.input) - monoRight.out.link(xoutRight.input) - -# Connect to device and start pipeline -with dai.Device(pipeline) as device: +xoutDisparity.setStreamName("disparity") +xoutDepth.setStreamName("depth") +xoutRectifLeft.setStreamName("rectifiedLeft") +xoutRectifRight.setStreamName("rectifiedRight") + +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) +if depth: + stereo.depth.link(xoutDepth.input) +if outRectified: + stereo.rectifiedLeft.link(xoutRectifLeft.input) + stereo.rectifiedRight.link(xoutRectifRight.input) + +streams = ["left", "right"] +if outRectified: + streams.extend(["rectifiedLeft", "rectifiedRight"]) +streams.append("disparity") +if depth: + streams.append("depth") + +calibData = dai.Device().readCalibration() +leftMesh, rightMesh = getMesh(calibData) +if generateMesh: + meshLeft = list(leftMesh.tobytes()) + meshRight = list(rightMesh.tobytes()) + stereo.loadMeshData(meshLeft, meshRight) - leftQueue = device.getOutputQueue(name="left", maxSize=8, blocking=False) - rightQueue = device.getOutputQueue(name="right", maxSize=8, blocking=False) - if (withDepth): - dispQueue = device.getOutputQueue(name="disparity", maxSize=8, blocking=False) - depthQueue = device.getOutputQueue(name="depth", maxSize=8, blocking=False) - rectifLeftQueue = device.getOutputQueue(name="rectified_left", maxSize=8, blocking=False) - rectifRightQueue = device.getOutputQueue(name="rectified_right", maxSize=8, blocking=False) +if meshDirectory is not None: + saveMeshFiles(leftMesh, rightMesh, meshDirectory) - # Disparity range is used for normalization - disparityMultiplier = 255 / stereo.getMaxDisparity() + +print("Creating DepthAI device") +with dai.Device(pipeline) as device: + # Create a receive queue for each stream + qList = [device.getOutputQueue(stream, 8, blocking=False) for stream in streams] while True: - left = leftQueue.get() - cv2.imshow("left", left.getFrame()) - right = rightQueue.get() - cv2.imshow("right", right.getFrame()) - - if withDepth: - disparity = dispQueue.get() - disp = disparity.getCvFrame() - disp = (disp*disparityMultiplier).astype(np.uint8) # Extended disparity range - cv2.imshow("disparity", disp) - disp = cv2.applyColorMap(disp, cv2.COLORMAP_JET) - cv2.imshow("disparity_color", disp) - - if outputDepth: - depth = depthQueue.get() - cv2.imshow("depth", depth.getCvFrame().astype(np.uint16)) - - if outputRectified: - rectifL = rectifLeftQueue.get() - cv2.imshow("rectified_left", rectifL.getFrame()) - - rectifR = rectifRightQueue.get() - cv2.imshow("rectified_right", rectifR.getFrame()) - - if cv2.waitKey(1) == ord('q'): + for q in qList: + name = q.getName() + frame = q.get().getCvFrame() + if name == "depth": + frame = frame.astype(np.uint16) + elif name == "disparity": + frame = getDisparityFrame(frame) + + cv2.imshow(name, frame) + if cv2.waitKey(1) == ord("q"): break diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index ebc46f957..c0e240707 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,2 +1,4 @@ # Add 'hedley' library -add_subdirectory(hedley) \ No newline at end of file +add_subdirectory(hedley) +# Add 'pybind11_json' library +add_subdirectory(pybind11_json) \ No newline at end of file diff --git a/external/pybind11_json/CMakeLists.txt b/external/pybind11_json/CMakeLists.txt new file mode 100644 index 000000000..1e2842c70 --- /dev/null +++ b/external/pybind11_json/CMakeLists.txt @@ -0,0 +1,3 @@ +# pybind11_json library +add_library(pybind11_json INTERFACE) +target_include_directories(pybind11_json INTERFACE "${CMAKE_CURRENT_LIST_DIR}/include") diff --git a/external/pybind11_json/include/pybind11_json/pybind11_json.hpp b/external/pybind11_json/include/pybind11_json/pybind11_json.hpp new file mode 100644 index 000000000..773a30cf4 --- /dev/null +++ b/external/pybind11_json/include/pybind11_json/pybind11_json.hpp @@ -0,0 +1,223 @@ +/*************************************************************************** +* Copyright (c) 2019, Martin Renou * +* * +* Distributed under the terms of the BSD 3-Clause License. * +* * +* The full license is in the file LICENSE, distributed with this software. * +****************************************************************************/ + +#ifndef PYBIND11_JSON_HPP +#define PYBIND11_JSON_HPP + +#include +#include + +#include "nlohmann/json.hpp" + +#include "pybind11/pybind11.h" + +namespace py = pybind11; +namespace nl = nlohmann; + +namespace pyjson +{ + inline py::object from_json(const nl::json& j) + { + if (j.is_null()) + { + return py::none(); + } + else if (j.is_boolean()) + { + return py::bool_(j.get()); + } + else if (j.is_number_integer()) + { + return py::int_(j.get()); + } + else if (j.is_number_unsigned()) + { + return py::int_(j.get()); + } + else if (j.is_number_float()) + { + return py::float_(j.get()); + } + else if (j.is_string()) + { + return py::str(j.get()); + } + else if (j.is_array()) + { + py::list obj; + for (const auto& el : j) + { + obj.append(from_json(el)); + } + return std::move(obj); + } + else // Object + { + py::dict obj; + for (nl::json::const_iterator it = j.cbegin(); it != j.cend(); ++it) + { + obj[py::str(it.key())] = from_json(it.value()); + } + return std::move(obj); + } + } + + inline nl::json to_json(const py::handle& obj) + { + if (obj.ptr() == nullptr || obj.is_none()) + { + return nullptr; + } + if (py::isinstance(obj)) + { + return obj.cast(); + } + if (py::isinstance(obj)) + { + try + { + nl::json::number_integer_t s = obj.cast(); + if (py::int_(s).equal(obj)) + { + return s; + } + } + catch (...) + { + } + try + { + nl::json::number_unsigned_t u = obj.cast(); + if (py::int_(u).equal(obj)) + { + return u; + } + } + catch (...) + { + } + throw std::runtime_error("to_json received an integer out of range for both nl::json::number_integer_t and nl::json::number_unsigned_t type: " + py::repr(obj).cast()); + } + if (py::isinstance(obj)) + { + return obj.cast(); + } + if (py::isinstance(obj)) + { + py::module base64 = py::module::import("base64"); + return base64.attr("b64encode")(obj).attr("decode")("utf-8").cast(); + } + if (py::isinstance(obj)) + { + return obj.cast(); + } + if (py::isinstance(obj) || py::isinstance(obj)) + { + auto out = nl::json::array(); + for (const py::handle value : obj) + { + out.push_back(to_json(value)); + } + return out; + } + if (py::isinstance(obj)) + { + auto out = nl::json::object(); + for (const py::handle key : obj) + { + out[py::str(key).cast()] = to_json(obj[key]); + } + return out; + } + throw std::runtime_error("to_json not implemented for this type of object: " + py::repr(obj).cast()); + } +} + +// nlohmann_json serializers +namespace nlohmann +{ + #define MAKE_NLJSON_SERIALIZER_DESERIALIZER(T) \ + template <> \ + struct adl_serializer \ + { \ + inline static void to_json(json& j, const T& obj) \ + { \ + j = pyjson::to_json(obj); \ + } \ + \ + inline static T from_json(const json& j) \ + { \ + return pyjson::from_json(j); \ + } \ + } + + #define MAKE_NLJSON_SERIALIZER_ONLY(T) \ + template <> \ + struct adl_serializer \ + { \ + inline static void to_json(json& j, const T& obj) \ + { \ + j = pyjson::to_json(obj); \ + } \ + } + + MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::object); + + MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::bool_); + MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::int_); + MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::float_); + MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::str); + + MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::list); + MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::tuple); + MAKE_NLJSON_SERIALIZER_DESERIALIZER(py::dict); + + MAKE_NLJSON_SERIALIZER_ONLY(py::handle); + MAKE_NLJSON_SERIALIZER_ONLY(py::detail::item_accessor); + MAKE_NLJSON_SERIALIZER_ONLY(py::detail::list_accessor); + MAKE_NLJSON_SERIALIZER_ONLY(py::detail::tuple_accessor); + MAKE_NLJSON_SERIALIZER_ONLY(py::detail::sequence_accessor); + MAKE_NLJSON_SERIALIZER_ONLY(py::detail::str_attr_accessor); + MAKE_NLJSON_SERIALIZER_ONLY(py::detail::obj_attr_accessor); + + #undef MAKE_NLJSON_SERIALIZER + #undef MAKE_NLJSON_SERIALIZER_ONLY +} + +// pybind11 caster +namespace pybind11 +{ + namespace detail + { + template <> struct type_caster + { + public: + PYBIND11_TYPE_CASTER(nl::json, _("json")); + + bool load(handle src, bool) + { + try { + value = pyjson::to_json(src); + return true; + } + catch (...) + { + return false; + } + } + + static handle cast(nl::json src, return_value_policy /* policy */, handle /* parent */) + { + object obj = pyjson::from_json(src); + return obj.release(); + } + }; + } +} + +#endif diff --git a/setup.py b/setup.py index c6e885a5c..5640d75ec 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ here = os.path.abspath(os.path.dirname(__file__)) version_file = os.path.join(here, "generated", "version.py") os.makedirs(os.path.join(here, "generated"), exist_ok=True) -if os.environ.get('CI') != None : +if os.environ.get('CI') != None : ### If CI build, respect 'BUILD_COMMIT_HASH' to determine final version if set final_version = find_version.get_package_version() if os.environ.get('BUILD_COMMIT_HASH') != None: @@ -67,7 +67,7 @@ def __init__(self, name, sourcedir=''): class CMakeBuild(build_ext): - + def run(self): try: out = subprocess.check_output(['cmake', '--version']) @@ -133,13 +133,13 @@ def build_extension(self, ext): if platform.system() == "Windows": cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] cmake_args += ['-DCMAKE_TOOLCHAIN_FILE={}'.format(os.path.dirname(os.path.abspath(__file__)) + '/cmake/toolchain/msvc.cmake')] - + # Detect whether 32 / 64 bit Python is used and compile accordingly if sys.maxsize > 2**32: cmake_args += ['-A', 'x64'] else: cmake_args += ['-A', 'Win32'] - + # Add flag to build with maximum available threads build_args += ['--', '/m'] # Unix @@ -155,13 +155,13 @@ def build_extension(self, ext): num_threads = (freeMemory // 1000) num_threads = min(num_threads, max_threads) if num_threads <= 0: - num_threads = 1 + num_threads = 1 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 if 'CMAKE_ARGS' in os.environ: cmake_args += [os.environ['CMAKE_ARGS']] @@ -176,8 +176,8 @@ def build_extension(self, ext): setup( name='depthai', version=__version__, - author='Martin Peterlin', - author_email='martin@luxonis.com', + author='Luxonis', + author_email='support@luxonis.com', description='DepthAI Python Library', license="MIT", long_description=long_description, @@ -200,15 +200,15 @@ def build_extension(self, ext): "Operating System :: Unix", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: C++", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Scientific/Engineering", "Topic :: Software Development", ], - python_requires='>=3.5', + python_requires='>=3.6', ) diff --git a/src/CalibrationHandlerBindings.cpp b/src/CalibrationHandlerBindings.cpp index b634aeccb..e4a018b12 100644 --- a/src/CalibrationHandlerBindings.cpp +++ b/src/CalibrationHandlerBindings.cpp @@ -38,11 +38,14 @@ void CalibrationHandlerBindings::bind(pybind11::module& m, void* pCallstack){ .def("getDefaultIntrinsics", &CalibrationHandler::getDefaultIntrinsics, py::arg("cameraId"), DOC(dai, CalibrationHandler, getDefaultIntrinsics)) .def("getDistortionCoefficients", &CalibrationHandler::getDistortionCoefficients, py::arg("cameraId"), DOC(dai, CalibrationHandler, getDistortionCoefficients)) - .def("getFov", &CalibrationHandler::getFov, py::arg("cameraId"), DOC(dai, CalibrationHandler, getFov)) + .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("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("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/src/DatatypeBindings.cpp b/src/DatatypeBindings.cpp index 1aeeeef88..37b347d7e 100644 --- a/src/DatatypeBindings.cpp +++ b/src/DatatypeBindings.cpp @@ -100,13 +100,25 @@ void DatatypeBindings::bind(pybind11::module& m, void* pCallstack){ py::class_ spatialLocations(m, "SpatialLocations", DOC(dai, SpatialLocations)); py::class_ rect(m, "Rect", DOC(dai, Rect)); py::class_ spatialLocationCalculatorConfigThresholds(m, "SpatialLocationCalculatorConfigThresholds", DOC(dai, SpatialLocationCalculatorConfigThresholds)); + py::enum_ spatialLocationCalculatorAlgorithm(m, "SpatialLocationCalculatorAlgorithm", DOC(dai, SpatialLocationCalculatorAlgorithm)); py::class_ spatialLocationCalculatorConfigData(m, "SpatialLocationCalculatorConfigData", DOC(dai, SpatialLocationCalculatorConfigData)); py::class_> spatialLocationCalculatorData(m, "SpatialLocationCalculatorData", DOC(dai, SpatialLocationCalculatorData)); py::class_> spatialLocationCalculatorConfig(m, "SpatialLocationCalculatorConfig", DOC(dai, SpatialLocationCalculatorConfig)); py::class_> tracklets(m, "Tracklets", DOC(dai, Tracklets)); py::class_> imuData(m, "IMUData", DOC(dai, IMUData)); + // Stereo depth py::class_> rawStereoDepthConfig(m, "RawStereoDepthConfig", DOC(dai, RawStereoDepthConfig)); + py::enum_ medianFilter(m, "MedianFilter", DOC(dai, MedianFilter)); + py::class_ algorithmControl(rawStereoDepthConfig, "AlgorithmControl", DOC(dai, RawStereoDepthConfig, AlgorithmControl)); + py::class_ postProcessing(rawStereoDepthConfig, "PostProcessing", DOC(dai, RawStereoDepthConfig, PostProcessing)); + py::class_ costAggregation(rawStereoDepthConfig, "CostAggregation", DOC(dai, RawStereoDepthConfig, CostAggregation)); + py::class_ costMatching(rawStereoDepthConfig, "CostMatching", DOC(dai, RawStereoDepthConfig, CostMatching)); + py::class_ costMatchingLinearEquationParameters(costMatching, "LinearEquationParameters", DOC(dai, RawStereoDepthConfig, CostMatching, LinearEquationParameters)); + py::enum_ costMatchingDisparityWidth(costMatching, "DisparityWidth", DOC(dai, RawStereoDepthConfig, CostMatching, DisparityWidth)); + py::class_ censusTransform(rawStereoDepthConfig, "CensusTransform", DOC(dai, RawStereoDepthConfig, CensusTransform)); + py::enum_ censusTransformKernelSize(censusTransform, "KernelSize", DOC(dai, RawStereoDepthConfig, CensusTransform, KernelSize)); py::class_> stereoDepthConfig(m, "StereoDepthConfig", DOC(dai, StereoDepthConfig)); + // Edge detector py::class_ edgeDetectorConfigData(m, "EdgeDetectorConfigData", DOC(dai, EdgeDetectorConfigData)); py::class_> rawEdgeDetectorConfig(m, "RawEdgeDetectorConfig", DOC(dai, RawEdgeDetectorConfig)); py::class_> edgeDetectorConfig(m, "EdgeDetectorConfig", DOC(dai, EdgeDetectorConfig)); @@ -808,6 +820,8 @@ void DatatypeBindings::bind(pybind11::module& m, void* pCallstack){ .def("setSequenceNum", &ImgFrame::setSequenceNum, py::arg("seq"), DOC(dai, ImgFrame, setSequenceNum)) .def("setWidth", &ImgFrame::setWidth, py::arg("width"), DOC(dai, ImgFrame, setWidth)) .def("setHeight", &ImgFrame::setHeight, py::arg("height"), DOC(dai, ImgFrame, setHeight)) + .def("setSize", static_cast(&ImgFrame::setSize), py::arg("width"), py::arg("height"), DOC(dai, ImgFrame, setSize)) + .def("setSize", static_cast)>(&ImgFrame::setSize), py::arg("sizer"), DOC(dai, ImgFrame, setSize, 2)) .def("setType", &ImgFrame::setType, py::arg("type"), DOC(dai, ImgFrame, setType)) ; // add aliases dai.ImgFrame.Type and dai.ImgFrame.Specs @@ -865,7 +879,8 @@ void DatatypeBindings::bind(pybind11::module& m, void* pCallstack){ imageManipConfig .def(py::init<>()) // setters - .def("setCropRect", &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("xmin"), py::arg("ymin"), py::arg("xmax"), py::arg("xmax"), 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)) .def("setWarpTransformFourPoints", &ImageManipConfig::setWarpTransformFourPoints, py::arg("pt"), py::arg("normalizedCoords"), DOC(dai, ImageManipConfig, setWarpTransformFourPoints)) @@ -874,8 +889,10 @@ void DatatypeBindings::bind(pybind11::module& m, void* pCallstack){ .def("setWarpBorderFillColor", &ImageManipConfig::setWarpBorderFillColor, py::arg("red"), py::arg("green"), py::arg("blue"), DOC(dai, ImageManipConfig, setWarpBorderFillColor)) .def("setRotationDegrees", &ImageManipConfig::setRotationDegrees, py::arg("deg"), DOC(dai, ImageManipConfig, setRotationDegrees)) .def("setRotationRadians", &ImageManipConfig::setRotationRadians, py::arg("rad"), DOC(dai, ImageManipConfig, setRotationRadians)) - .def("setResize", &ImageManipConfig::setResize, py::arg("w"), py::arg("h"), DOC(dai, ImageManipConfig, setResize)) - .def("setResizeThumbnail", &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("setResize", static_cast(&ImageManipConfig::setResize), py::arg("w"), py::arg("h"), DOC(dai, ImageManipConfig, setResize)) + .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("setHorizontalFlip", &ImageManipConfig::setHorizontalFlip, py::arg("flip"), DOC(dai, ImageManipConfig, setHorizontalFlip)) .def("setReusePreviousImage", &ImageManipConfig::setReusePreviousImage, py::arg("reuse"), DOC(dai, ImageManipConfig, setReusePreviousImage)) @@ -889,6 +906,9 @@ void DatatypeBindings::bind(pybind11::module& m, void* pCallstack){ .def("getCropYMax", &ImageManipConfig::getCropYMax, DOC(dai, ImageManipConfig, getCropYMax)) .def("getResizeWidth", &ImageManipConfig::getResizeWidth, DOC(dai, ImageManipConfig, getResizeWidth)) .def("getResizeHeight", &ImageManipConfig::getResizeHeight, DOC(dai, ImageManipConfig, getResizeHeight)) + .def("getCropConfig", &ImageManipConfig::getCropConfig, DOC(dai, ImageManipConfig, getCropConfig)) + .def("getResizeConfig", &ImageManipConfig::getResizeConfig, DOC(dai, ImageManipConfig, getResizeConfig)) + .def("getFormatConfig", &ImageManipConfig::getFormatConfig, DOC(dai, ImageManipConfig, getFormatConfig)) .def("isResizeThumbnail", &ImageManipConfig::isResizeThumbnail, DOC(dai, ImageManipConfig, isResizeThumbnail)) ; @@ -982,11 +1002,17 @@ void DatatypeBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("upperThreshold", &SpatialLocationCalculatorConfigThresholds::upperThreshold) ; + spatialLocationCalculatorAlgorithm + .value("AVERAGE", SpatialLocationCalculatorAlgorithm::AVERAGE) + .value("MIN", SpatialLocationCalculatorAlgorithm::MIN) + .value("MAX", SpatialLocationCalculatorAlgorithm::MAX) + ; spatialLocationCalculatorConfigData .def(py::init<>()) - .def_readwrite("roi", &SpatialLocationCalculatorConfigData::roi) - .def_readwrite("depthThresholds", &SpatialLocationCalculatorConfigData::depthThresholds) + .def_readwrite("roi", &SpatialLocationCalculatorConfigData::roi, DOC(dai, SpatialLocationCalculatorConfigData, roi)) + .def_readwrite("depthThresholds", &SpatialLocationCalculatorConfigData::depthThresholds, DOC(dai, SpatialLocationCalculatorConfigData, depthThresholds)) + .def_readwrite("calculationAlgorithm", &SpatialLocationCalculatorConfigData::calculationAlgorithm, DOC(dai, SpatialLocationCalculatorConfigData, calculationAlgorithm)) ; // Bind SpatialLocationCalculatorData @@ -1022,11 +1048,87 @@ void DatatypeBindings::bind(pybind11::module& m, void* pCallstack){ .def_property("packets", [](IMUData& imuDta) { return &imuDta.packets; }, [](IMUData& imuDta, std::vector val) { imuDta.packets = val; }, DOC(dai, IMUData, packets)) ; + // StereoDepth config + + // MedianFilter + medianFilter + .value("MEDIAN_OFF", MedianFilter::MEDIAN_OFF) + .value("KERNEL_3x3", MedianFilter::KERNEL_3x3) + .value("KERNEL_5x5", MedianFilter::KERNEL_5x5) + .value("KERNEL_7x7", MedianFilter::KERNEL_7x7) + ; + m.attr("StereoDepthProperties").attr("MedianFilter") = medianFilter; + + algorithmControl + .def(py::init<>()) + .def_readwrite("enableLeftRightCheck", &RawStereoDepthConfig::AlgorithmControl::enableLeftRightCheck, DOC(dai, RawStereoDepthConfig, AlgorithmControl, enableLeftRightCheck)) + .def_readwrite("enableSubpixel", &RawStereoDepthConfig::AlgorithmControl::enableSubpixel, DOC(dai, RawStereoDepthConfig, AlgorithmControl, enableSubpixel)) + .def_readwrite("leftRightCheckThreshold", &RawStereoDepthConfig::AlgorithmControl::leftRightCheckThreshold, DOC(dai, RawStereoDepthConfig, AlgorithmControl, leftRightCheckThreshold)) + .def_readwrite("subpixelFractionalBits", &RawStereoDepthConfig::AlgorithmControl::subpixelFractionalBits, DOC(dai, RawStereoDepthConfig, AlgorithmControl, subpixelFractionalBits)) + ; + + postProcessing + .def(py::init<>()) + .def_readwrite("median", &RawStereoDepthConfig::PostProcessing::median, DOC(dai, RawStereoDepthConfig, PostProcessing, median)) + .def_readwrite("bilateralSigmaValue", &RawStereoDepthConfig::PostProcessing::bilateralSigmaValue, DOC(dai, RawStereoDepthConfig, PostProcessing, bilateralSigmaValue)) + ; + + // KernelSize + censusTransformKernelSize + .value("AUTO", RawStereoDepthConfig::CensusTransform::KernelSize::AUTO) + .value("KERNEL_5x5", RawStereoDepthConfig::CensusTransform::KernelSize::KERNEL_5x5) + .value("KERNEL_7x7", RawStereoDepthConfig::CensusTransform::KernelSize::KERNEL_7x7) + .value("KERNEL_7x9", RawStereoDepthConfig::CensusTransform::KernelSize::KERNEL_7x9) + ; + + censusTransform + .def(py::init<>()) + .def_readwrite("kernelSize", &RawStereoDepthConfig::CensusTransform::kernelSize, DOC(dai, RawStereoDepthConfig, CensusTransform, kernelSize)) + .def_readwrite("kernelMask", &RawStereoDepthConfig::CensusTransform::kernelMask, DOC(dai, RawStereoDepthConfig, CensusTransform, kernelMask)) + .def_readwrite("enableMeanMode", &RawStereoDepthConfig::CensusTransform::enableMeanMode, DOC(dai, RawStereoDepthConfig, CensusTransform, enableMeanMode)) + .def_readwrite("threshold", &RawStereoDepthConfig::CensusTransform::threshold, DOC(dai, RawStereoDepthConfig, CensusTransform, threshold)) + ; + + costMatchingLinearEquationParameters + .def(py::init<>()) + .def_readwrite("alpha", &RawStereoDepthConfig::CostMatching::LinearEquationParameters::alpha, DOC(dai, RawStereoDepthConfig, CostMatching, LinearEquationParameters, alpha)) + .def_readwrite("beta", &RawStereoDepthConfig::CostMatching::LinearEquationParameters::beta, DOC(dai, RawStereoDepthConfig, CostMatching, LinearEquationParameters, beta)) + .def_readwrite("threshold", &RawStereoDepthConfig::CostMatching::LinearEquationParameters::threshold, DOC(dai, RawStereoDepthConfig, CostMatching, LinearEquationParameters, threshold)) + ; + + // Disparity width + costMatchingDisparityWidth + .value("DISPARITY_64", RawStereoDepthConfig::CostMatching::DisparityWidth::DISPARITY_64) + .value("DISPARITY_96", RawStereoDepthConfig::CostMatching::DisparityWidth::DISPARITY_96) + ; + + costMatching + .def(py::init<>()) + .def_readwrite("disparityWidth", &RawStereoDepthConfig::CostMatching::disparityWidth, DOC(dai, RawStereoDepthConfig, CostMatching, disparityWidth)) + .def_readwrite("enableCompanding", &RawStereoDepthConfig::CostMatching::enableCompanding, DOC(dai, RawStereoDepthConfig, CostMatching, enableCompanding)) + .def_readwrite("invalidDisparityValue", &RawStereoDepthConfig::CostMatching::invalidDisparityValue, DOC(dai, RawStereoDepthConfig, CostMatching, invalidDisparityValue)) + .def_readwrite("confidenceThreshold", &RawStereoDepthConfig::CostMatching::confidenceThreshold, DOC(dai, RawStereoDepthConfig, CostMatching, confidenceThreshold)) + .def_readwrite("linearEquationParameters", &RawStereoDepthConfig::CostMatching::linearEquationParameters, DOC(dai, RawStereoDepthConfig, CostMatching, linearEquationParameters)) + ; + + costAggregation + .def(py::init<>()) + .def_readwrite("divisionFactor", &RawStereoDepthConfig::CostAggregation::divisionFactor, DOC(dai, RawStereoDepthConfig, CostAggregation, divisionFactor)) + .def_readwrite("horizontalPenaltyCosts", &RawStereoDepthConfig::CostAggregation::horizontalPenaltyCosts, DOC(dai, RawStereoDepthConfig, CostAggregation, horizontalPenaltyCosts)) + .def_readwrite("verticalPenaltyCosts", &RawStereoDepthConfig::CostAggregation::verticalPenaltyCosts, DOC(dai, RawStereoDepthConfig, CostAggregation, verticalPenaltyCosts)) + ; rawStereoDepthConfig .def(py::init<>()) - .def_readwrite("config", &RawStereoDepthConfig::config) + .def_readwrite("algorithmControl", &RawStereoDepthConfig::algorithmControl, DOC(dai, RawStereoDepthConfig, algorithmControl)) + .def_readwrite("postProcessing", &RawStereoDepthConfig::postProcessing, DOC(dai, RawStereoDepthConfig, postProcessing)) + .def_readwrite("censusTransform", &RawStereoDepthConfig::censusTransform, DOC(dai, RawStereoDepthConfig, censusTransform)) + .def_readwrite("costMatching", &RawStereoDepthConfig::costMatching, DOC(dai, RawStereoDepthConfig, costMatching)) + .def_readwrite("costAggregation", &RawStereoDepthConfig::costAggregation, DOC(dai, RawStereoDepthConfig, costAggregation)) ; + // legacy + m.attr("RawStereoDepthConfig").attr("MedianFilter") = medianFilter; + stereoDepthConfig @@ -1039,6 +1141,11 @@ void DatatypeBindings::bind(pybind11::module& m, void* pCallstack){ .def("getMedianFilter", &StereoDepthConfig::getMedianFilter, DOC(dai, StereoDepthConfig, getMedianFilter)) .def("getBilateralFilterSigma", &StereoDepthConfig::getBilateralFilterSigma, DOC(dai, StereoDepthConfig, getBilateralFilterSigma)) .def("getLeftRightCheckThreshold", &StereoDepthConfig::getLeftRightCheckThreshold, DOC(dai, StereoDepthConfig, getLeftRightCheckThreshold)) + .def("setLeftRightCheck", &StereoDepthConfig::setLeftRightCheck, DOC(dai, StereoDepthConfig, setLeftRightCheck)) + .def("setSubpixel", &StereoDepthConfig::setSubpixel, DOC(dai, StereoDepthConfig, setSubpixel)) + .def("getMaxDisparity", &StereoDepthConfig::getMaxDisparity, DOC(dai, StereoDepthConfig, getMaxDisparity)) + .def("set", &StereoDepthConfig::set, py::arg("config"), DOC(dai, StereoDepthConfig, set)) + .def("get", &StereoDepthConfig::get, DOC(dai, StereoDepthConfig, get)) ; edgeDetectorConfigData diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 4e4a7269f..8e4fa5575 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -2,6 +2,7 @@ // depthai #include "depthai/device/Device.hpp" +#include "depthai/pipeline/Pipeline.hpp" // std::chrono bindings #include @@ -10,11 +11,13 @@ // hedley #include + // Searches for available devices (as Device constructor) // but pooling, to check for python interrupts, and releases GIL in between -template -static std::unique_ptr deviceConstructorHelper(const ARG1& arg, const std::string& pathToCmd = "", bool usb2Mode = false){ +template +static auto deviceSearchHelper(Args&&... args){ + auto startTime = std::chrono::steady_clock::now(); bool found; dai::DeviceInfo deviceInfo = {}; @@ -42,17 +45,11 @@ static std::unique_ptr deviceConstructorHelper(const ARG1& arg, const st // if no devices found, then throw if(!found) throw std::runtime_error("No available devices"); - // Check if pathToCmd supplied - if(pathToCmd.empty()){ - return std::make_unique(arg, deviceInfo, usb2Mode); - } else { - return std::make_unique(arg, deviceInfo, pathToCmd); - } - return nullptr; + return deviceInfo; } -std::vector deviceGetQueueEventsHelper(dai::Device& d, const std::vector& queueNames, std::size_t maxNumEvents, std::chrono::microseconds timeout){ +static std::vector deviceGetQueueEventsHelper(dai::Device& d, const std::vector& queueNames, std::size_t maxNumEvents, std::chrono::microseconds timeout){ using namespace std::chrono; // if timeout < 0, unlimited timeout @@ -75,6 +72,92 @@ std::vector deviceGetQueueEventsHelper(dai::Device& d, const std::v } +template +static void bindConstructors(ARG& arg){ + using namespace dai; + + arg + .def(py::init([](const Pipeline& pipeline){ + auto dev = deviceSearchHelper(); + py::gil_scoped_release release; + return std::make_unique(pipeline, dev); + }), py::arg("pipeline"), DOC(dai, DeviceBase, DeviceBase)) + .def(py::init([](const Pipeline& pipeline, bool usb2Mode){ + auto dev = deviceSearchHelper(); + py::gil_scoped_release release; + return std::make_unique(pipeline, dev, usb2Mode); + }), py::arg("pipeline"), py::arg("usb2Mode"), DOC(dai, DeviceBase, DeviceBase, 2)) + .def(py::init([](const Pipeline& pipeline, UsbSpeed maxUsbSpeed){ + auto dev = deviceSearchHelper(); + py::gil_scoped_release release; + return std::make_unique(pipeline, dev, maxUsbSpeed); + }), py::arg("pipeline"), py::arg("maxUsbSpeed"), DOC(dai, DeviceBase, DeviceBase, 3)) + .def(py::init([](const Pipeline& pipeline, const std::string& pathToCmd){ + auto dev = deviceSearchHelper(); + py::gil_scoped_release release; + 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){ + 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, 7)) + .def(py::init([](const Pipeline& pipeline, const DeviceInfo& deviceInfo, UsbSpeed maxUsbSpeed){ + py::gil_scoped_release release; + return std::make_unique(pipeline, deviceInfo, maxUsbSpeed); + }), py::arg("pipeline"), py::arg("deviceInfo"), py::arg("maxUsbSpeed"), DOC(dai, DeviceBase, DeviceBase, 8)) + .def(py::init([](const Pipeline& pipeline, const DeviceInfo& deviceInfo, std::string pathToCmd){ + py::gil_scoped_release release; + return std::make_unique(pipeline, deviceInfo, pathToCmd); + }), py::arg("pipeline"), py::arg("devInfo"), py::arg("pathToCmd"), DOC(dai, DeviceBase, DeviceBase, 9)) + + // DeviceBase constructor - OpenVINO version + .def(py::init([](OpenVINO::Version version){ + auto dev = deviceSearchHelper(); + py::gil_scoped_release release; + return std::make_unique(version, dev); + }), py::arg("version") = OpenVINO::DEFAULT_VERSION, DOC(dai, DeviceBase, DeviceBase, 11)) + .def(py::init([](OpenVINO::Version version, bool usb2Mode){ + auto dev = deviceSearchHelper(); + py::gil_scoped_release release; + return std::make_unique(version, dev, usb2Mode); + }), py::arg("version"), py::arg("usb2Mode") = false, DOC(dai, DeviceBase, DeviceBase, 13)) + .def(py::init([](OpenVINO::Version version, UsbSpeed maxUsbSpeed){ + auto dev = deviceSearchHelper(); + py::gil_scoped_release release; + return std::make_unique(version, dev, maxUsbSpeed); + }), py::arg("version"), py::arg("maxUsbSpeed"), DOC(dai, DeviceBase, DeviceBase, 14)) + .def(py::init([](OpenVINO::Version version, const std::string& pathToCmd){ + auto dev = deviceSearchHelper(); + py::gil_scoped_release release; + return std::make_unique(version, dev, pathToCmd); + }), py::arg("version"), py::arg("pathToCmd"), DOC(dai, DeviceBase, DeviceBase, 15)) + .def(py::init([](OpenVINO::Version version, const DeviceInfo& deviceInfo, bool usb2Mode){ + py::gil_scoped_release release; + return std::make_unique(version, deviceInfo, usb2Mode); + }), py::arg("version"), py::arg("deviceDesc"), py::arg("usb2Mode") = false, DOC(dai, DeviceBase, DeviceBase, 18)) + .def(py::init([](OpenVINO::Version version, const DeviceInfo& deviceInfo, UsbSpeed maxUsbSpeed){ + py::gil_scoped_release release; + return std::make_unique(version, deviceInfo, maxUsbSpeed); + }), py::arg("version"), py::arg("deviceInfo"), py::arg("maxUsbSpeed"), DOC(dai, DeviceBase, DeviceBase, 19)) + .def(py::init([](OpenVINO::Version version, const DeviceInfo& deviceInfo, std::string pathToCmd){ + py::gil_scoped_release release; + return std::make_unique(version, deviceInfo, pathToCmd); + }), py::arg("version"), py::arg("deviceDesc"), py::arg("pathToCmd"), DOC(dai, DeviceBase, DeviceBase, 20)) + .def(py::init([](typename D::Config config){ + auto dev = deviceSearchHelper(); + py::gil_scoped_release release; + return std::make_unique(config, dev); + }), py::arg("config"), DOC(dai, DeviceBase, DeviceBase, 22)) + .def(py::init([](typename D::Config config, const DeviceInfo& deviceInfo){ + py::gil_scoped_release release; + return std::make_unique(config, deviceInfo); + }), py::arg("config"), py::arg("deviceInfo"), DOC(dai, DeviceBase, DeviceBase, 23)) + ; + +} + + + void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ using namespace dai; @@ -82,6 +165,9 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ // Type definitions 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_ prebootConfig(m, "PrebootConfig", DOC(dai, PrebootConfig)); + py::class_ prebootConfigUsb(prebootConfig, "USB", DOC(dai, PrebootConfig, USB)); /////////////////////////////////////////////////////////////////////// @@ -97,8 +183,33 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ /////////////////////////////////////////////////////////////////////// + // Bind PrebootConfig::USB + prebootConfigUsb + .def(py::init<>()) + .def_readwrite("vid", &PrebootConfig::USB::vid) + .def_readwrite("pid", &PrebootConfig::USB::pid) + .def_readwrite("flashBootedVid", &PrebootConfig::USB::flashBootedVid) + .def_readwrite("flashBootedPid", &PrebootConfig::USB::flashBootedPid) + .def_readwrite("maxSpeed", &PrebootConfig::USB::maxSpeed) + ; + + // Bind PrebootConfig + prebootConfig + .def(py::init<>()) + .def_readwrite("usb", &PrebootConfig::usb) + .def_readwrite("watchdogTimeoutMs", &PrebootConfig::watchdogTimeoutMs) + ; + + // Bind Device::Config + deviceConfig + .def(py::init<>()) + .def_readwrite("version", &Device::Config::version) + .def_readwrite("preboot", &Device::Config::preboot) + ; - // Bind Device, using DeviceWrapper to be able to destruct the object by calling close() + // Bind constructors + bindConstructors(deviceBase); + // Bind the rest deviceBase // Python only methods .def("__enter__", [](py::object obj){ return obj; }) @@ -115,53 +226,11 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def_static("getAnyAvailableDevice", [](){ return DeviceBase::getAnyAvailableDevice(); }, DOC(dai, DeviceBase, getAnyAvailableDevice, 2)) .def_static("getFirstAvailableDevice", &DeviceBase::getFirstAvailableDevice, DOC(dai, DeviceBase, getFirstAvailableDevice)) .def_static("getAllAvailableDevices", &DeviceBase::getAllAvailableDevices, DOC(dai, DeviceBase, getAllAvailableDevices)) - .def_static("getEmbeddedDeviceBinary", &DeviceBase::getEmbeddedDeviceBinary, py::arg("usb2Mode"), py::arg("version") = Pipeline::DEFAULT_OPENVINO_VERSION, DOC(dai, DeviceBase, getEmbeddedDeviceBinary)) + .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("config"), DOC(dai, DeviceBase, getEmbeddedDeviceBinary, 2)) .def_static("getDeviceByMxId", &DeviceBase::getDeviceByMxId, py::arg("mxId"), DOC(dai, DeviceBase, getDeviceByMxId)) // methods - - // Device constructor - Pipeline - .def(py::init([](const Pipeline& pipeline){ return deviceConstructorHelper(pipeline); }), py::arg("pipeline"), DOC(dai, DeviceBase, DeviceBase)) - .def(py::init([](const Pipeline& pipeline, bool usb2Mode){ - // Blocking constructor - return deviceConstructorHelper(pipeline, std::string(""), usb2Mode); - }), py::arg("pipeline"), py::arg("usb2Mode"), DOC(dai, DeviceBase, DeviceBase, 2)) - .def(py::init([](const Pipeline& pipeline, const std::string& pathToCmd){ - // Blocking constructor - return deviceConstructorHelper(pipeline, pathToCmd); - }), py::arg("pipeline"), py::arg("pathToCmd"), DOC(dai, DeviceBase, DeviceBase, 3)) - .def(py::init([](const Pipeline& pipeline, const DeviceInfo& deviceInfo, bool usb2Mode){ - // Non blocking constructor - 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)) - .def(py::init([](const Pipeline& pipeline, const DeviceInfo& deviceInfo, std::string pathToCmd){ - // Non blocking constructor - py::gil_scoped_release release; - return std::make_unique(pipeline, deviceInfo, pathToCmd); - }), py::arg("pipeline"), py::arg("devInfo"), py::arg("pathToCmd"), DOC(dai, DeviceBase, DeviceBase, 7)) - - // DeviceBase constructor - OpenVINO version - .def(py::init([](OpenVINO::Version version){ return deviceConstructorHelper(version); }), py::arg("version") = Pipeline::DEFAULT_OPENVINO_VERSION, DOC(dai, DeviceBase, DeviceBase, 10)) - .def(py::init([](OpenVINO::Version version, bool usb2Mode){ - // Blocking constructor - return deviceConstructorHelper(version, std::string(""), usb2Mode); - }), py::arg("version"), py::arg("usb2Mode"), DOC(dai, DeviceBase, DeviceBase, 11)) - .def(py::init([](OpenVINO::Version version, const std::string& pathToCmd){ - // Blocking constructor - return deviceConstructorHelper(version, pathToCmd); - }), py::arg("version"), py::arg("pathToCmd"), DOC(dai, DeviceBase, DeviceBase, 12)) - .def(py::init([](OpenVINO::Version version, const DeviceInfo& deviceInfo, bool usb2Mode){ - // Non blocking constructor - py::gil_scoped_release release; - return std::make_unique(version, deviceInfo, usb2Mode); - }), py::arg("version"), py::arg("devInfo"), py::arg("usb2Mode") = false, DOC(dai, DeviceBase, DeviceBase, 15)) - .def(py::init([](OpenVINO::Version version, const DeviceInfo& deviceInfo, std::string pathToCmd){ - // Non blocking constructor - py::gil_scoped_release release; - return std::make_unique(version, deviceInfo, pathToCmd); - }), py::arg("version"), py::arg("devInfo"), py::arg("pathToCmd"), DOC(dai, DeviceBase, DeviceBase, 16)) - .def("isPipelineRunning", [](DeviceBase& d) { py::gil_scoped_release release; return d.isPipelineRunning(); }, DOC(dai, DeviceBase, isPipelineRunning)) .def("startPipeline", [](DeviceBase& d){ // Issue an deprecation warning @@ -203,48 +272,10 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ ; + // Bind constructors + bindConstructors(device); + // Bind the rest device - .def(py::init([](const Pipeline& pipeline){ return deviceConstructorHelper(pipeline); }), py::arg("pipeline"), DOC(dai, Device, Device)) - .def(py::init([](const Pipeline& pipeline, bool usb2Mode){ - // Blocking constructor - return deviceConstructorHelper(pipeline, std::string(""), usb2Mode); - }), py::arg("pipeline"), py::arg("usb2Mode"), DOC(dai, Device, Device, 2)) - .def(py::init([](const Pipeline& pipeline, const std::string& pathToCmd){ - // Blocking constructor - return deviceConstructorHelper(pipeline, pathToCmd); - }), py::arg("pipeline"), py::arg("pathToCmd"), DOC(dai, Device, Device, 3)) - .def(py::init([](const Pipeline& pipeline, const DeviceInfo& deviceInfo, bool usb2Mode){ - // Non blocking constructor - py::gil_scoped_release release; - return std::make_unique(pipeline, deviceInfo, usb2Mode); - }), py::arg("pipeline"), py::arg("devInfo"), py::arg("usb2Mode") = false, DOC(dai, Device, Device, 6)) - .def(py::init([](const Pipeline& pipeline, const DeviceInfo& deviceInfo, std::string pathToCmd){ - // Non blocking constructor - py::gil_scoped_release release; - return std::make_unique(pipeline, deviceInfo, pathToCmd); - }), py::arg("pipeline"), py::arg("devInfo"), py::arg("pathToCmd"), DOC(dai, Device, Device, 7)) - - // Device constructor - OpenVINO version - .def(py::init([](OpenVINO::Version version){ return deviceConstructorHelper(version); }), py::arg("version") = Pipeline::DEFAULT_OPENVINO_VERSION, DOC(dai, DeviceBase, DeviceBase, 10)) - .def(py::init([](OpenVINO::Version version, bool usb2Mode){ - // Blocking constructor - return deviceConstructorHelper(version, std::string(""), usb2Mode); - }), py::arg("version"), py::arg("usb2Mode"), DOC(dai, DeviceBase, DeviceBase, 11)) - .def(py::init([](OpenVINO::Version version, const std::string& pathToCmd){ - // Blocking constructor - return deviceConstructorHelper(version, pathToCmd); - }), py::arg("version"), py::arg("pathToCmd"), DOC(dai, DeviceBase, DeviceBase, 12)) - .def(py::init([](OpenVINO::Version version, const DeviceInfo& deviceInfo, bool usb2Mode){ - // Non blocking constructor - py::gil_scoped_release release; - return std::make_unique(version, deviceInfo, usb2Mode); - }), py::arg("version"), py::arg("devInfo"), py::arg("usb2Mode") = false, DOC(dai, DeviceBase, DeviceBase, 15)) - .def(py::init([](OpenVINO::Version version, const DeviceInfo& deviceInfo, std::string pathToCmd){ - // Non blocking constructor - py::gil_scoped_release release; - return std::make_unique(version, deviceInfo, pathToCmd); - }), py::arg("version"), py::arg("devInfo"), py::arg("pathToCmd"), DOC(dai, DeviceBase, DeviceBase, 16)) - .def("getOutputQueue", static_cast(Device::*)(const std::string&)>(&Device::getOutputQueue), py::arg("name"), DOC(dai, Device, getOutputQueue)) .def("getOutputQueue", static_cast(Device::*)(const std::string&, unsigned int, bool)>(&Device::getOutputQueue), py::arg("name"), py::arg("maxSize"), py::arg("blocking") = true, DOC(dai, Device, getOutputQueue, 2)) .def("getOutputQueueNames", &Device::getOutputQueueNames, DOC(dai, Device, getOutputQueueNames)) diff --git a/src/DeviceBootloaderBindings.cpp b/src/DeviceBootloaderBindings.cpp index 890364a30..a975bc10b 100644 --- a/src/DeviceBootloaderBindings.cpp +++ b/src/DeviceBootloaderBindings.cpp @@ -13,6 +13,9 @@ void DeviceBootloaderBindings::bind(pybind11::module& m, void* pCallstack){ py::enum_ deviceBootloaderType(deviceBootloader, "Type"); py::enum_ deviceBootloaderMemory(deviceBootloader, "Memory"); py::enum_ deviceBootloaderSection(deviceBootloader, "Section"); + py::class_ deviceBootlaoderUsbConfig(deviceBootloader, "UsbConfig"); + py::class_ deviceBootlaoderNetworkConfig(deviceBootloader, "NetworkConfig"); + py::class_ deviceBootloderConfig(deviceBootloader, "Config"); /////////////////////////////////////////////////////////////////////// @@ -39,20 +42,72 @@ void DeviceBootloaderBindings::bind(pybind11::module& m, void* pCallstack){ ; deviceBootloaderType + .value("AUTO", DeviceBootloader::Type::AUTO) .value("USB", DeviceBootloader::Type::USB) .value("NETWORK", DeviceBootloader::Type::NETWORK) ; deviceBootloaderMemory + .value("AUTO", DeviceBootloader::Memory::AUTO) .value("FLASH", DeviceBootloader::Memory::FLASH) .value("EMMC", DeviceBootloader::Memory::EMMC) ; deviceBootloaderSection + .value("AUTO", DeviceBootloader::Section::AUTO) .value("HEADER", DeviceBootloader::Section::HEADER) .value("BOOTLOADER", DeviceBootloader::Section::BOOTLOADER) .value("BOOTLOADER_CONFIG", DeviceBootloader::Section::BOOTLOADER_CONFIG) .value("APPLICATION", DeviceBootloader::Section::APPLICATION) ; + deviceBootlaoderUsbConfig + .def(py::init<>()) + .def_readwrite("timeoutMs", &DeviceBootloader::UsbConfig::timeoutMs) + .def_readwrite("maxUsbSpeed", &DeviceBootloader::UsbConfig::maxUsbSpeed) + .def_readwrite("vid", &DeviceBootloader::UsbConfig::vid) + .def_readwrite("pid", &DeviceBootloader::UsbConfig::pid) + ; + deviceBootlaoderNetworkConfig + .def(py::init<>()) + .def_readwrite("timeoutMs", &DeviceBootloader::NetworkConfig::timeoutMs) + .def_readwrite("ipv4", &DeviceBootloader::NetworkConfig::ipv4) + .def_readwrite("ipv4Mask", &DeviceBootloader::NetworkConfig::ipv4Mask) + .def_readwrite("ipv4Gateway", &DeviceBootloader::NetworkConfig::ipv4Gateway) + .def_readwrite("ipv4Dns", &DeviceBootloader::NetworkConfig::ipv4Dns) + .def_readwrite("ipv4DnsAlt", &DeviceBootloader::NetworkConfig::ipv4DnsAlt) + .def_readwrite("staticIpv4", &DeviceBootloader::NetworkConfig::staticIpv4) + .def_readwrite("ipv6", &DeviceBootloader::NetworkConfig::ipv6) + .def_readwrite("ipv6Prefix", &DeviceBootloader::NetworkConfig::ipv6Prefix) + .def_readwrite("ipv6Gateway", &DeviceBootloader::NetworkConfig::ipv6Gateway) + .def_readwrite("ipv6Dns", &DeviceBootloader::NetworkConfig::ipv6Dns) + .def_readwrite("ipv6DnsAlt", &DeviceBootloader::NetworkConfig::ipv6DnsAlt) + .def_readwrite("staticIpv6", &DeviceBootloader::NetworkConfig::staticIpv6) + .def_readwrite("mac", &DeviceBootloader::NetworkConfig::mac) + ; + + deviceBootloderConfig + .def(py::init<>()) + .def_readwrite("appMem", &DeviceBootloader::Config::appMem) + .def_readwrite("usb", &DeviceBootloader::Config::usb) + .def_readwrite("network", &DeviceBootloader::Config::network) + .def("setStaticIPv4", &DeviceBootloader::Config::setStaticIPv4) + .def("setDynamicIPv4", &DeviceBootloader::Config::setDynamicIPv4) + .def("isStaticIPV4", &DeviceBootloader::Config::isStaticIPV4) + .def("getIPv4", &DeviceBootloader::Config::getIPv4) + .def("getIPv4Mask", &DeviceBootloader::Config::getIPv4Mask) + .def("getIPv4Gateway", &DeviceBootloader::Config::getIPv4Gateway) + .def("setDnsIPv4", &DeviceBootloader::Config::setDnsIPv4) + .def("getDnsIPv4", &DeviceBootloader::Config::getDnsIPv4) + .def("getDnsAltIPv4", &DeviceBootloader::Config::getDnsAltIPv4) + .def("setUsbTimeout", &DeviceBootloader::Config::setUsbTimeout) + .def("getUsbTimeout", &DeviceBootloader::Config::getUsbTimeout) + .def("setNetworkTimeout", &DeviceBootloader::Config::setNetworkTimeout) + .def("getNetworkTimeout", &DeviceBootloader::Config::getNetworkTimeout) + .def("setMacAddress", &DeviceBootloader::Config::setMacAddress) + .def("getMacAddress", &DeviceBootloader::Config::getMacAddress) + .def("setUsbMaxSpeed", &DeviceBootloader::Config::setUsbMaxSpeed) + .def("getUsbMaxSpeed", &DeviceBootloader::Config::getUsbMaxSpeed) + ; + deviceBootloader // Python only methods .def("__enter__", [](py::object obj){ return obj; }) @@ -61,21 +116,38 @@ void DeviceBootloaderBindings::bind(pybind11::module& m, void* pCallstack){ .def_static("getFirstAvailableDevice", &DeviceBootloader::getFirstAvailableDevice, DOC(dai, DeviceBootloader, getFirstAvailableDevice)) .def_static("getAllAvailableDevices", &DeviceBootloader::getAllAvailableDevices, DOC(dai, DeviceBootloader, getAllAvailableDevices)) - .def_static("saveDepthaiApplicationPackage", &DeviceBootloader::saveDepthaiApplicationPackage, py::arg("path"), py::arg("pipeline"), py::arg("pathToCmd") = "", DOC(dai, DeviceBootloader, saveDepthaiApplicationPackage)) - .def_static("createDepthaiApplicationPackage", &DeviceBootloader::createDepthaiApplicationPackage, py::arg("pipeline"), py::arg("pathToCmd") = "", DOC(dai, DeviceBootloader, createDepthaiApplicationPackage)) + .def_static("saveDepthaiApplicationPackage", py::overload_cast(&DeviceBootloader::saveDepthaiApplicationPackage), py::arg("path"), py::arg("pipeline"), py::arg("pathToCmd") = "", py::arg("compress") = false, DOC(dai, DeviceBootloader, saveDepthaiApplicationPackage)) + .def_static("saveDepthaiApplicationPackage", py::overload_cast(&DeviceBootloader::saveDepthaiApplicationPackage), py::arg("path"), py::arg("pipeline"), py::arg("compress") = false, DOC(dai, DeviceBootloader, saveDepthaiApplicationPackage, 2)) + .def_static("createDepthaiApplicationPackage", py::overload_cast(&DeviceBootloader::createDepthaiApplicationPackage), py::arg("pipeline"), py::arg("pathToCmd") = "", py::arg("compress") = false, DOC(dai, DeviceBootloader, createDepthaiApplicationPackage)) + .def_static("createDepthaiApplicationPackage", py::overload_cast(&DeviceBootloader::createDepthaiApplicationPackage), py::arg("pipeline"), py::arg("compress"), DOC(dai, DeviceBootloader, createDepthaiApplicationPackage, 2)) .def_static("getEmbeddedBootloaderVersion", &DeviceBootloader::getEmbeddedBootloaderVersion, DOC(dai, DeviceBootloader, getEmbeddedBootloaderVersion)) .def_static("getEmbeddedBootloaderBinary", &DeviceBootloader::getEmbeddedBootloaderBinary, DOC(dai, DeviceBootloader, getEmbeddedBootloaderBinary)) - .def(py::init(), py::arg("deviceDesc"), DOC(dai, DeviceBootloader, DeviceBootloader)) - .def(py::init(), py::arg("deviceDesc"), py::arg("pathToCmd"), DOC(dai, DeviceBootloader, DeviceBootloader, 2)) - .def("flash", [](DeviceBootloader& db, std::function progressCallback, Pipeline& pipeline) { py::gil_scoped_release release; return db.flash(progressCallback, pipeline); }, py::arg("progressCallback"), py::arg("pipeline"), DOC(dai, DeviceBootloader, flash)) + .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("flash", [](DeviceBootloader& db, std::function progressCallback, const Pipeline& pipeline, bool compress) { py::gil_scoped_release release; return db.flash(progressCallback, pipeline, compress); }, py::arg("progressCallback"), py::arg("pipeline"), py::arg("compress") = false, DOC(dai, DeviceBootloader, flash)) + .def("flash", [](DeviceBootloader& db, const Pipeline& pipeline, bool compress) { py::gil_scoped_release release; return db.flash(pipeline, compress); }, py::arg("pipeline"), py::arg("compress") = false, DOC(dai, DeviceBootloader, flash, 2)) .def("flashDepthaiApplicationPackage", [](DeviceBootloader& db, std::function progressCallback, std::vector package) { py::gil_scoped_release release; return db.flashDepthaiApplicationPackage(progressCallback, package); }, py::arg("progressCallback"), py::arg("package"), DOC(dai, DeviceBootloader, flashDepthaiApplicationPackage)) + .def("flashDepthaiApplicationPackage", [](DeviceBootloader& db, std::vector package) { py::gil_scoped_release release; return db.flashDepthaiApplicationPackage(package); }, py::arg("package"), DOC(dai, DeviceBootloader, flashDepthaiApplicationPackage, 2)) .def("flashBootloader", [](DeviceBootloader& db, std::function progressCallback, std::string path) { py::gil_scoped_release release; return db.flashBootloader(progressCallback, path); }, py::arg("progressCallback"), py::arg("path") = "", DOC(dai, DeviceBootloader, flashBootloader)) .def("flashBootloader", [](DeviceBootloader& db, DeviceBootloader::Memory memory, DeviceBootloader::Type type, std::function progressCallback, std::string path) { py::gil_scoped_release release; return db.flashBootloader(memory, type, progressCallback, path); }, py::arg("memory"), py::arg("type"), py::arg("progressCallback"), py::arg("path") = "", DOC(dai, DeviceBootloader, flashBootloader, 2)) + + .def("readConfigData", [](DeviceBootloader& db, DeviceBootloader::Memory memory, DeviceBootloader::Type type) { py::gil_scoped_release release; return db.readConfigData(memory, type); }, py::arg("memory") = DeviceBootloader::Memory::AUTO, py::arg("type") = DeviceBootloader::Type::AUTO, DOC(dai, DeviceBootloader, readConfigData)) + .def("flashConfigData", [](DeviceBootloader& db, nlohmann::json configData, DeviceBootloader::Memory memory, DeviceBootloader::Type type) { py::gil_scoped_release release; return db.flashConfigData(configData, memory, type); }, py::arg("configData"), py::arg("memory") = DeviceBootloader::Memory::AUTO, py::arg("type") = DeviceBootloader::Type::AUTO, DOC(dai, DeviceBootloader, flashConfigData)) + .def("flashConfigFile", [](DeviceBootloader& db, std::string configPath, DeviceBootloader::Memory memory, DeviceBootloader::Type type) { py::gil_scoped_release release; return db.flashConfigFile(configPath, memory, type); }, py::arg("configData"), py::arg("memory") = DeviceBootloader::Memory::AUTO, py::arg("type") = DeviceBootloader::Type::AUTO, DOC(dai, DeviceBootloader, flashConfigFile)) + .def("flashConfigClear", [](DeviceBootloader& db, DeviceBootloader::Memory memory, DeviceBootloader::Type type) { py::gil_scoped_release release; return db.flashConfigClear(memory, type); }, py::arg("memory") = DeviceBootloader::Memory::AUTO, py::arg("type") = DeviceBootloader::Type::AUTO, DOC(dai, DeviceBootloader, flashConfigClear)) + .def("readConfig", [](DeviceBootloader& db, DeviceBootloader::Memory memory, DeviceBootloader::Type type) { py::gil_scoped_release release; return db.readConfig(memory, type); }, py::arg("memory") = DeviceBootloader::Memory::AUTO, py::arg("type") = DeviceBootloader::Type::AUTO, DOC(dai, DeviceBootloader, readConfig)) + .def("flashConfig", [](DeviceBootloader& db, const DeviceBootloader::Config& config, DeviceBootloader::Memory memory, DeviceBootloader::Type type) { py::gil_scoped_release release; return db.flashConfig(config, memory, type); }, py::arg("config"), py::arg("memory") = DeviceBootloader::Memory::AUTO, py::arg("type") = DeviceBootloader::Type::AUTO, DOC(dai, DeviceBootloader, flashConfig)) + + .def("bootMemory", [](DeviceBootloader& db, const std::vector& fw) { py::gil_scoped_release release; return db.bootMemory(fw); }, py::arg("fw"), DOC(dai, DeviceBootloader, bootMemory)) + .def("bootUsbRomBootloader", [](DeviceBootloader& db) { py::gil_scoped_release release; return db.bootUsbRomBootloader(); }, DOC(dai, DeviceBootloader, bootUsbRomBootloader)) + //.def("flashCustom", &DeviceBootloader::flashCustom, py::arg("memory"), py::arg("offset"), py::arg("progressCallback"), py::arg("data"), DOC(dai, DeviceBootloader, flashCustom)) .def("getVersion", [](DeviceBootloader& db) { py::gil_scoped_release release; return db.getVersion(); }, DOC(dai, DeviceBootloader, getVersion)) .def("isEmbeddedVersion", &DeviceBootloader::isEmbeddedVersion, DOC(dai, DeviceBootloader, isEmbeddedVersion)) + .def("getType", &DeviceBootloader::getType, DOC(dai, DeviceBootloader, getType)) + .def("isAllowedFlashingBootloader", &DeviceBootloader::isAllowedFlashingBootloader, DOC(dai, DeviceBootloader, isAllowedFlashingBootloader)) ; } diff --git a/src/XLinkConnectionBindings.cpp b/src/XLinkBindings.cpp similarity index 54% rename from src/XLinkConnectionBindings.cpp rename to src/XLinkBindings.cpp index 07b9d37cf..91d5f0238 100644 --- a/src/XLinkConnectionBindings.cpp +++ b/src/XLinkBindings.cpp @@ -1,29 +1,43 @@ -#include "XLinkConnectionBindings.hpp" +#include "XLinkBindings.hpp" #include "depthai/xlink/XLinkConnection.hpp" +#include "depthai/xlink/XLinkStream.hpp" #include #include -void XLinkConnectionBindings::bind(pybind11::module& m, void* pCallstack){ +void XLinkBindings::bind(pybind11::module &m, void *pCallstack) +{ using namespace dai; - // Type definitions py::class_ deviceInfo(m, "DeviceInfo", DOC(dai, DeviceInfo)); py::class_ deviceDesc(m, "DeviceDesc"); py::enum_ xLinkDeviceState(m, "XLinkDeviceState"); py::enum_ xLinkProtocol(m, "XLinkProtocol"); py::enum_ xLinkPlatform(m, "XLinkPlatform"); - py::class_> xLinkConnection(m, "XLinkConnection", DOC(dai, XLinkConnection)); + py::class_ > xLinkConnection(m, "XLinkConnection", DOC(dai, XLinkConnection)); + + // pybind11 limitation of having actual classes as exceptions + // Possible but requires a larger workaround + // https://stackoverflow.com/questions/62087383/how-can-you-bind-exceptions-with-custom-fields-and-constructors-in-pybind11-and + + // For now just create simple exceptions that will expose what() message + auto xlinkError = py::register_exception(m, "XLinkError", PyExc_RuntimeError); + auto xlinkReadError = py::register_exception(m, "XLinkReadError", xlinkError.ptr()); + auto xlinkWriteError = py::register_exception(m, "XLinkWriteError", xlinkError.ptr()); + + //py::class_ xLinkError(m, "XLinkError", DOC(dai, XLinkError)); + //py::class_ xLinkReadError(m, "XLinkReadError", DOC(dai, XLinkReadError)); + //py::class_ xLinkWriteError(m, "XLinkWriteError", DOC(dai, XLinkWriteError)); /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // Call the rest of the type defines, then perform the actual bindings - Callstack* callstack = (Callstack*) pCallstack; + Callstack *callstack = (Callstack *)pCallstack; auto cb = callstack->top(); callstack->pop(); cb(m, pCallstack); @@ -43,10 +57,12 @@ void XLinkConnectionBindings::bind(pybind11::module& m, void* pCallstack){ .def(py::init<>()) .def_readwrite("protocol", &deviceDesc_t::protocol) .def_readwrite("platform", &deviceDesc_t::platform) - .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()));} - ) + .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())); }) ; xLinkDeviceState @@ -54,10 +70,10 @@ void XLinkConnectionBindings::bind(pybind11::module& m, void* pCallstack){ .value("X_LINK_BOOTED", X_LINK_BOOTED) .value("X_LINK_UNBOOTED", X_LINK_UNBOOTED) .value("X_LINK_BOOTLOADER", X_LINK_BOOTLOADER) - .export_values(); + .value("X_LINK_FLASH_BOOTED", X_LINK_FLASH_BOOTED) + .export_values() ; - xLinkProtocol .value("X_LINK_USB_VSC", X_LINK_USB_VSC) .value("X_LINK_USB_CDC", X_LINK_USB_CDC) @@ -77,13 +93,29 @@ void XLinkConnectionBindings::bind(pybind11::module& m, void* pCallstack){ ; xLinkConnection - .def(py::init>()) - .def(py::init()) - .def(py::init()) + .def(py::init >()) + .def(py::init()) + .def(py::init()) .def_static("getAllConnectedDevices", &XLinkConnection::getAllConnectedDevices, py::arg("state") = X_LINK_ANY_STATE) .def_static("getFirstDevice", &XLinkConnection::getFirstDevice, py::arg("state") = X_LINK_ANY_STATE) .def_static("getDeviceByMxId", &XLinkConnection::getDeviceByMxId, py::arg("mxId"), py::arg("state") = X_LINK_ANY_STATE) + .def_static("bootBootloader", &XLinkConnection::bootBootloader, py::arg("devInfo")) ; + //// Exceptions + + // Applicable if above pybind11 limitation is removed + // xLinkError + // .def(py::init()) + // .def_readonly("status", &XLinkError::status) + // .def_readonly("streamName", &XLinkError::streamName) + // .def("what", &XLinkError::what); + // + // xLinkReadError + // .def(py::init()); + // + // xLinkWriteError + // .def(py::init()); + } \ No newline at end of file diff --git a/src/XLinkBindings.hpp b/src/XLinkBindings.hpp new file mode 100644 index 000000000..ea360b302 --- /dev/null +++ b/src/XLinkBindings.hpp @@ -0,0 +1,9 @@ +#pragma once + +// pybind +#include "pybind11_common.hpp" + +struct XLinkBindings +{ + static void bind(pybind11::module &m, void *pCallstack); +}; diff --git a/src/XLinkConnectionBindings.hpp b/src/XLinkConnectionBindings.hpp deleted file mode 100644 index e2a5288f2..000000000 --- a/src/XLinkConnectionBindings.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -// pybind -#include "pybind11_common.hpp" - -struct XLinkConnectionBindings { - static void bind(pybind11::module& m, void* pCallstack); -}; diff --git a/src/pipeline/NodeBindings.cpp b/src/pipeline/NodeBindings.cpp index 5566d3775..4dc374e33 100644 --- a/src/pipeline/NodeBindings.cpp +++ b/src/pipeline/NodeBindings.cpp @@ -155,8 +155,6 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ py::class_ monoCameraProperties(m, "MonoCameraProperties", DOC(dai, MonoCameraProperties)); py::enum_ monoCameraPropertiesSensorResolution(monoCameraProperties, "SensorResolution", DOC(dai, MonoCameraProperties, SensorResolution)); py::class_ stereoDepthProperties(m, "StereoDepthProperties", DOC(dai, StereoDepthProperties)); - py::enum_ medianFilter(m, "MedianFilter", DOC(dai, MedianFilter)); - py::class_ stereoDepthConfigData(m, "StereoDepthConfigData", DOC(dai, StereoDepthConfigData)); py::class_ videoEncoderProperties(m, "VideoEncoderProperties", DOC(dai, VideoEncoderProperties)); py::enum_ videoEncoderPropertiesProfile(videoEncoderProperties, "Profile", DOC(dai, VideoEncoderProperties, Profile)); py::enum_ videoEncoderPropertiesProfileRateControlMode(videoEncoderProperties, "RateControlMode", DOC(dai, VideoEncoderProperties, RateControlMode)); @@ -280,24 +278,12 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ // StereoDepth props - // MedianFilter - medianFilter - .value("MEDIAN_OFF", MedianFilter::MEDIAN_OFF) - .value("KERNEL_3x3", MedianFilter::KERNEL_3x3) - .value("KERNEL_5x5", MedianFilter::KERNEL_5x5) - .value("KERNEL_7x7", MedianFilter::KERNEL_7x7) - ; - stereoDepthProperties - .def_readwrite("calibration", &StereoDepthProperties::calibration) .def_readwrite("initialConfig", &StereoDepthProperties::initialConfig) .def_readwrite("inputConfigSync", &StereoDepthProperties::inputConfigSync) .def_readwrite("depthAlign", &StereoDepthProperties::depthAlign) .def_readwrite("depthAlignCamera", &StereoDepthProperties::depthAlignCamera) - .def_readwrite("enableLeftRightCheck", &StereoDepthProperties::enableLeftRightCheck) - .def_readwrite("enableSubpixel", &StereoDepthProperties::enableSubpixel) .def_readwrite("enableExtendedDisparity", &StereoDepthProperties::enableExtendedDisparity) - .def_readwrite("rectifyMirrorFrame", &StereoDepthProperties::rectifyMirrorFrame) .def_readwrite("rectifyEdgeFillColor", &StereoDepthProperties::rectifyEdgeFillColor) .def_readwrite("width", &StereoDepthProperties::width) .def_readwrite("height", &StereoDepthProperties::height) @@ -306,16 +292,6 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("outKeepAspectRatio", &StereoDepthProperties::outKeepAspectRatio, DOC(dai, StereoDepthProperties, outKeepAspectRatio)) .def_readwrite("mesh", &StereoDepthProperties::mesh, DOC(dai, StereoDepthProperties, mesh)) ; - m.attr("StereoDepthProperties").attr("MedianFilter") = medianFilter; - - stereoDepthConfigData - .def(py::init<>()) - .def_readwrite("median", &StereoDepthConfigData::median, DOC(dai, StereoDepthConfigData, median)) - .def_readwrite("confidenceThreshold", &StereoDepthConfigData::confidenceThreshold, DOC(dai, StereoDepthConfigData, confidenceThreshold)) - .def_readwrite("bilateralSigmaValue", &StereoDepthConfigData::bilateralSigmaValue, DOC(dai, StereoDepthConfigData, bilateralSigmaValue)) - .def_readwrite("leftRightCheckThreshold", &StereoDepthConfigData::leftRightCheckThreshold, DOC(dai, StereoDepthConfigData, leftRightCheckThreshold)) - ; - m.attr("StereoDepthConfigData").attr("MedianFilter") = medianFilter; // VideoEncoder props @@ -778,10 +754,20 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def_readonly("syncedRight", &StereoDepth::syncedRight, DOC(dai, node, StereoDepth, syncedRight)) .def_readonly("rectifiedLeft", &StereoDepth::rectifiedLeft, DOC(dai, node, StereoDepth, rectifiedLeft)) .def_readonly("rectifiedRight", &StereoDepth::rectifiedRight, DOC(dai, node, StereoDepth, rectifiedRight)) + .def_readonly("outConfig", &StereoDepth::outConfig, DOC(dai, node, StereoDepth, outConfig)) + .def_readonly("debugDispLrCheckIt1", &StereoDepth::debugDispLrCheckIt1, DOC(dai, node, StereoDepth, debugDispLrCheckIt1)) + .def_readonly("debugDispLrCheckIt2", &StereoDepth::debugDispLrCheckIt2, DOC(dai, node, StereoDepth, debugDispLrCheckIt2)) + .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 aligment/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)) - .def("setInputResolution", &StereoDepth::setInputResolution, py::arg("width"), py::arg("height"), DOC(dai, node, StereoDepth, setInputResolution)) + .def("setInputResolution", static_cast(&StereoDepth::setInputResolution), py::arg("width"), py::arg("height"), DOC(dai, node, StereoDepth, setInputResolution)) + .def("setInputResolution", static_cast)>(&StereoDepth::setInputResolution), py::arg("resolution"), DOC(dai, node, StereoDepth, setInputResolution, 2)) .def("setOutputSize", &StereoDepth::setOutputSize, py::arg("width"), py::arg("height"), DOC(dai, node, StereoDepth, setOutputSize)) .def("setOutputKeepAspectRatio",&StereoDepth::setOutputKeepAspectRatio, py::arg("keep"), DOC(dai, node, StereoDepth, setOutputKeepAspectRatio)) .def("setDepthAlign", static_cast(&StereoDepth::setDepthAlign), py::arg("align"), DOC(dai, node, StereoDepth, setDepthAlign)) @@ -791,7 +777,15 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def("setSubpixel", &StereoDepth::setSubpixel, py::arg("enable"), DOC(dai, node, StereoDepth, setSubpixel)) .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::setRectifyMirrorFrame, py::arg("enable"), DOC(dai, node, StereoDepth, setRectifyMirrorFrame)) + .def("setRectifyMirrorFrame", [](StereoDepth& s, bool enable) { + // Issue an deprecation warning + PyErr_WarnEx(PyExc_DeprecationWarning, "setRectifyMirrorFrame() is deprecated.", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + s.setRectifyMirrorFrame(enable); + HEDLEY_DIAGNOSTIC_POP + }, DOC(dai, node, StereoDepth, setRectifyMirrorFrame)) + .def("setConfidenceThreshold", [](StereoDepth& s, int confThr) { // Issue an deprecation warning PyErr_WarnEx(PyExc_DeprecationWarning, "setConfidenceThreshold() is deprecated, Use 'initialConfig.setConfidenceThreshold()' instead", 1); @@ -845,7 +839,15 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ s.setEmptyCalibration(); HEDLEY_DIAGNOSTIC_POP }, DOC(dai, node, StereoDepth, setEmptyCalibration)) - .def("getMaxDisparity", &StereoDepth::getMaxDisparity, DOC(dai, node, StereoDepth, getMaxDisparity)) + .def("setRuntimeModeSwitch", &StereoDepth::setRuntimeModeSwitch, DOC(dai, node, StereoDepth, setRuntimeModeSwitch)) + .def("setNumFramesPool", &StereoDepth::setNumFramesPool, DOC(dai, node, StereoDepth, setNumFramesPool)) + .def("getMaxDisparity", [](StereoDepth& s){ + PyErr_WarnEx(PyExc_DeprecationWarning, "getMaxDisparity() is deprecated, Use 'initialConfig.getMaxDisparity()' instead", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + return s.getMaxDisparity(); + HEDLEY_DIAGNOSTIC_POP + }, DOC(dai, node, StereoDepth, getMaxDisparity)) ; // ALIAS daiNodeModule.attr("StereoDepth").attr("Properties") = stereoDepthProperties; @@ -943,6 +945,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def("setBoundingBoxScaleFactor", &SpatialDetectionNetwork::setBoundingBoxScaleFactor, py::arg("scaleFactor"), DOC(dai, node, SpatialDetectionNetwork, setBoundingBoxScaleFactor)) .def("setDepthLowerThreshold", &SpatialDetectionNetwork::setDepthLowerThreshold, py::arg("lowerThreshold"), DOC(dai, node, SpatialDetectionNetwork, setDepthLowerThreshold)) .def("setDepthUpperThreshold", &SpatialDetectionNetwork::setDepthUpperThreshold, py::arg("upperThreshold"), DOC(dai, node, SpatialDetectionNetwork, setDepthUpperThreshold)) + .def("setSpatialCalculationAlgorithm", &SpatialDetectionNetwork::setSpatialCalculationAlgorithm, py::arg("calculationAlgorithm"), DOC(dai, node, SpatialDetectionNetwork, setSpatialCalculationAlgorithm)) ; // ALIAS daiNodeModule.attr("SpatialDetectionNetwork").attr("Properties") = spatialDetectionNetworkProperties; diff --git a/src/pipeline/PipelineBindings.cpp b/src/pipeline/PipelineBindings.cpp index d0ce5af3b..074727c92 100644 --- a/src/pipeline/PipelineBindings.cpp +++ b/src/pipeline/PipelineBindings.cpp @@ -93,12 +93,14 @@ 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") = Pipeline::DEFAULT_OPENVINO_VERSION, DOC(dai, Pipeline, setOpenVINOVersion)) + .def("setOpenVINOVersion", &Pipeline::setOpenVINOVersion, py::arg("version") = OpenVINO::DEFAULT_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)) .def("setXLinkChunkSize", &Pipeline::setXLinkChunkSize, py::arg("sizeBytes"), DOC(dai, Pipeline, setXLinkChunkSize)) .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)) // 'Template' create function .def("create", [](dai::Pipeline& p, py::object class_) { auto node = createNode(p, class_); diff --git a/src/py_bindings.cpp b/src/py_bindings.cpp index 75bcf9ec7..1a3927ab9 100644 --- a/src/py_bindings.cpp +++ b/src/py_bindings.cpp @@ -19,7 +19,7 @@ #include "pipeline/PipelineBindings.hpp" #include "pipeline/CommonBindings.hpp" #include "pipeline/NodeBindings.hpp" -#include "XLinkConnectionBindings.hpp" +#include "XLinkBindings.hpp" #include "DeviceBindings.hpp" #include "CalibrationHandlerBindings.hpp" #include "DeviceBootloaderBindings.hpp" @@ -28,8 +28,7 @@ #include "openvino/OpenVINOBindings.hpp" #include "log/LogBindings.hpp" - -PYBIND11_MODULE(depthai,m) +PYBIND11_MODULE(depthai, m) { // Depthai python version consists of: (depthai-core).(bindings revision)[+bindings hash] @@ -47,22 +46,39 @@ PYBIND11_MODULE(depthai,m) callstack.push_front(&AssetManagerBindings::bind); callstack.push_front(&NodeBindings::bind); callstack.push_front(&PipelineBindings::bind); - callstack.push_front(&XLinkConnectionBindings::bind); + callstack.push_front(&XLinkBindings::bind); callstack.push_front(&DeviceBindings::bind); callstack.push_front(&DeviceBootloaderBindings::bind); callstack.push_front(&CalibrationHandlerBindings::bind); // end of the callstack - callstack.push_front([](py::module&, void*){}); + callstack.push_front([](py::module &, void *) {}); Callstack callstackAdapter(callstack); // Initial call CommonBindings::bind(m, &callstackAdapter); + // Install signal handler option + bool installSignalHandler = true; + constexpr static const char* signalHandlerKey = "DEPTHAI_INSTALL_SIGNAL_HANDLER"; + try { + auto sysModule = py::module_::import("sys"); + if(py::hasattr(sysModule, signalHandlerKey)){ + installSignalHandler = installSignalHandler && sysModule.attr(signalHandlerKey).cast(); + } + } catch (...) { + // ignore + } + try { + auto builtinsModule = py::module_::import("builtins"); + if(py::hasattr(builtinsModule, signalHandlerKey)){ + installSignalHandler = installSignalHandler && builtinsModule.attr(signalHandlerKey).cast(); + } + } catch (...){ + // ignore + } + // 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); + dai::initialize(std::string("Python bindings - version: ") + DEPTHAI_PYTHON_VERSION + " from " + DEPTHAI_PYTHON_COMMIT_DATETIME + " build: " + DEPTHAI_PYTHON_BUILD_DATETIME, installSignalHandler); } - - - diff --git a/src/pybind11_common.hpp b/src/pybind11_common.hpp index 8fbab8dd8..1669cdf40 100644 --- a/src/pybind11_common.hpp +++ b/src/pybind11_common.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e69de29bb..c3aad2e65 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -0,0 +1,36 @@ + +set(TARGET_TEST_MODULE "depthai_pybind11_tests") + +# Specify path separator +set(SYS_PATH_SEPARATOR ";") +if(UNIX) + set(SYS_PATH_SEPARATOR ":") +endif() + +set(PYBIND11_TEST_FILES + "xlink_exceptions_test.cpp" +) + +string(REPLACE ".cpp" ".py" PYBIND11_PYTEST_FILES "${PYBIND11_TEST_FILES}") + +# Create the binding library at the end +pybind11_add_module(${TARGET_TEST_MODULE} THIN_LTO ${TARGET_TEST_MODULE}.cpp ${PYBIND11_TEST_FILES}) + +# A single command to compile and run the tests +add_custom_target( + pytest COMMAND + ${CMAKE_COMMAND} -E env + # Python path (to find compiled modules) + "PYTHONPATH=$${SYS_PATH_SEPARATOR}$${SYS_PATH_SEPARATOR}$ENV{PYTHONPATH}" + # ASAN in case of sanitizers + ${ASAN_ENVIRONMENT_VARS} + ${PYTHON_EXECUTABLE} -m pytest ${PYBIND11_PYTEST_FILES} + DEPENDS + ${TARGET_TEST_MODULE} # Compiled tests + ${TARGET_NAME} # DepthAI Python Library + WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" + USES_TERMINAL +) + +# Link to depthai +target_link_libraries(${TARGET_TEST_MODULE} PRIVATE pybind11::pybind11 depthai::core) \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..f7605bf82 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- +"""pytest configuration + +Extends output capture as needed by pybind11: ignore constructors, optional unordered lines. +Adds docstring and exceptions message sanitizers: ignore Python 2 vs 3 differences. +""" + +import contextlib +import difflib +import gc +import re +import textwrap + +import pytest + +# Early diagnostic for failed imports +import depthai_pybind11_tests # noqa: F401 + +_unicode_marker = re.compile(r"u(\'[^\']*\')") +_long_marker = re.compile(r"([0-9])L") +_hexadecimal = re.compile(r"0x[0-9a-fA-F]+") + +def _strip_and_dedent(s): + """For triple-quote strings""" + return textwrap.dedent(s.lstrip("\n").rstrip()) + + +def _split_and_sort(s): + """For output which does not require specific line order""" + return sorted(_strip_and_dedent(s).splitlines()) + + +def _make_explanation(a, b): + """Explanation for a failed assert -- the a and b arguments are List[str]""" + return ["--- actual / +++ expected"] + [ + line.strip("\n") for line in difflib.ndiff(a, b) + ] + + +class Output(object): + """Basic output post-processing and comparison""" + + def __init__(self, string): + self.string = string + self.explanation = [] + + def __str__(self): + return self.string + + def __eq__(self, other): + # Ignore constructor/destructor output which is prefixed with "###" + a = [ + line + for line in self.string.strip().splitlines() + if not line.startswith("###") + ] + b = _strip_and_dedent(other).splitlines() + if a == b: + return True + else: + self.explanation = _make_explanation(a, b) + return False + + +class Unordered(Output): + """Custom comparison for output without strict line ordering""" + + def __eq__(self, other): + a = _split_and_sort(self.string) + b = _split_and_sort(other) + if a == b: + return True + else: + self.explanation = _make_explanation(a, b) + return False + + +class Capture(object): + def __init__(self, capfd): + self.capfd = capfd + self.out = "" + self.err = "" + + def __enter__(self): + self.capfd.readouterr() + return self + + def __exit__(self, *args): + self.out, self.err = self.capfd.readouterr() + + def __eq__(self, other): + a = Output(self.out) + b = other + if a == b: + return True + else: + self.explanation = a.explanation + return False + + def __str__(self): + return self.out + + def __contains__(self, item): + return item in self.out + + @property + def unordered(self): + return Unordered(self.out) + + @property + def stderr(self): + return Output(self.err) + + +@pytest.fixture +def capture(capsys): + """Extended `capsys` with context manager and custom equality operators""" + return Capture(capsys) + + +class SanitizedString(object): + def __init__(self, sanitizer): + self.sanitizer = sanitizer + self.string = "" + self.explanation = [] + + def __call__(self, thing): + self.string = self.sanitizer(thing) + return self + + def __eq__(self, other): + a = self.string + b = _strip_and_dedent(other) + if a == b: + return True + else: + self.explanation = _make_explanation(a.splitlines(), b.splitlines()) + return False + + +def _sanitize_general(s): + s = s.strip() + s = s.replace("pybind11_tests.", "m.") + s = s.replace("unicode", "str") + s = _long_marker.sub(r"\1", s) + s = _unicode_marker.sub(r"\1", s) + return s + + +def _sanitize_docstring(thing): + s = thing.__doc__ + s = _sanitize_general(s) + return s + + +@pytest.fixture +def doc(): + """Sanitize docstrings and add custom failure explanation""" + return SanitizedString(_sanitize_docstring) + + +def _sanitize_message(thing): + s = str(thing) + s = _sanitize_general(s) + s = _hexadecimal.sub("0", s) + return s + + +@pytest.fixture +def msg(): + """Sanitize messages and add custom failure explanation""" + return SanitizedString(_sanitize_message) + + +# noinspection PyUnusedLocal +def pytest_assertrepr_compare(op, left, right): + """Hook to insert custom failure explanation""" + if hasattr(left, "explanation"): + return left.explanation + + +@contextlib.contextmanager +def suppress(exception): + """Suppress the desired exception""" + try: + yield + except exception: + pass + + +def gc_collect(): + """Run the garbage collector twice (needed when running + reference counting tests with PyPy)""" + gc.collect() + gc.collect() + + +def pytest_configure(): + pytest.suppress = suppress + pytest.gc_collect = gc_collect diff --git a/tests/depthai_pybind11_tests.cpp b/tests/depthai_pybind11_tests.cpp new file mode 100644 index 000000000..7d12722e8 --- /dev/null +++ b/tests/depthai_pybind11_tests.cpp @@ -0,0 +1,55 @@ +/* + tests/pybind11_tests.cpp -- pybind example plugin + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include "depthai_pybind11_tests.hpp" + +#include +#include + +/* +For testing purposes, we define a static global variable here in a function that each individual +test .cpp calls with its initialization lambda. It's convenient here because we can just not +compile some test files to disable/ignore some of the test code. + +It is NOT recommended as a way to use pybind11 in practice, however: the initialization order will +be essentially random, which is okay for our test scripts (there are no dependencies between the +individual pybind11 test .cpp files), but most likely not what you want when using pybind11 +productively. + +Instead, see the "How can I reduce the build time?" question in the "Frequently asked questions" +section of the documentation for good practice on splitting binding code over multiple files. +*/ +std::list> &initializers() { + static std::list> inits; + return inits; +} + +test_initializer::test_initializer(Initializer init) { + initializers().emplace_back(init); +} + +test_initializer::test_initializer(const char *submodule_name, Initializer init) { + initializers().emplace_back([=](py::module_ &parent) { + auto m = parent.def_submodule(submodule_name); + init(m); + }); +} + +PYBIND11_MODULE(depthai_pybind11_tests, m) { + m.doc() = "depthai pybind11 test module"; + +#if !defined(NDEBUG) + m.attr("debug_enabled") = true; +#else + m.attr("debug_enabled") = false; +#endif + + for (const auto &initializer : initializers()) + initializer(m); +} diff --git a/tests/depthai_pybind11_tests.hpp b/tests/depthai_pybind11_tests.hpp new file mode 100644 index 000000000..b6e12bef4 --- /dev/null +++ b/tests/depthai_pybind11_tests.hpp @@ -0,0 +1,29 @@ +#pragma once + +// This must be kept first for MSVC 2015. +// Do not remove the empty line between the #includes. +#include + +#include + +#if defined(_MSC_VER) && _MSC_VER < 1910 +// We get some really long type names here which causes MSVC 2015 to emit warnings +# pragma warning( \ + disable : 4503) // warning C4503: decorated name length exceeded, name was truncated +#endif + +namespace py = pybind11; +using namespace pybind11::literals; + +class test_initializer { + using Initializer = void (*)(py::module_ &); + +public: + test_initializer(Initializer init); + test_initializer(const char *submodule_name, Initializer init); +}; + +#define TEST_SUBMODULE(name, variable) \ + void test_submodule_##name(py::module_ &); \ + test_initializer name(#name, test_submodule_##name); \ + void test_submodule_##name(py::module_ &(variable)) diff --git a/tests/xlink_exceptions_test.cpp b/tests/xlink_exceptions_test.cpp new file mode 100644 index 000000000..bb2c538fe --- /dev/null +++ b/tests/xlink_exceptions_test.cpp @@ -0,0 +1,19 @@ +#include "depthai_pybind11_tests.hpp" + +#include "depthai/depthai.hpp" + +TEST_SUBMODULE(xlink_exceptions, m) { + + m.def("throw_xlink_error", [](){ + throw dai::XLinkError(X_LINK_ERROR, "stream", "Yikes!"); + }); + + m.def("throw_xlink_read_error", [](){ + throw dai::XLinkReadError(X_LINK_ERROR, "stream_read"); + }); + + m.def("throw_xlink_write_error", [](){ + throw dai::XLinkWriteError(X_LINK_ERROR, "stream_write"); + }); + +} diff --git a/tests/xlink_exceptions_test.py b/tests/xlink_exceptions_test.py new file mode 100644 index 000000000..a41d21278 --- /dev/null +++ b/tests/xlink_exceptions_test.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +import sys + +import pytest + +import depthai as dai +from depthai_pybind11_tests import xlink_exceptions as m + +def test_xlink_error_exception(msg): + with pytest.raises(dai.XLinkError) as excinfo: + m.throw_xlink_error() + assert msg(excinfo.value) == "Yikes!" + +def test_xlink_read_error_exception(msg): + with pytest.raises(dai.XLinkReadError) as excinfo: + m.throw_xlink_read_error() + assert msg(excinfo.value) == "Couldn't read data from stream: 'stream_read' (X_LINK_ERROR)" + +def test_xlink_write_error_exception(msg): + with pytest.raises(dai.XLinkWriteError) as excinfo: + m.throw_xlink_write_error() + assert msg(excinfo.value) == "Couldn't write data to stream: 'stream_write' (X_LINK_ERROR)" + +def test_xlink_error_exception_runtime_error(msg): + with pytest.raises(RuntimeError) as excinfo: + m.throw_xlink_error() + assert msg(excinfo.value) == "Yikes!" + +def test_xlink_read_error_exception_xlink_error(msg): + with pytest.raises(dai.XLinkError) as excinfo: + m.throw_xlink_read_error() + assert msg(excinfo.value) == "Couldn't read data from stream: 'stream_read' (X_LINK_ERROR)" + +def test_xlink_write_error_exception_xlink_error(msg): + with pytest.raises(dai.XLinkError) as excinfo: + m.throw_xlink_write_error() + assert msg(excinfo.value) == "Couldn't write data to stream: 'stream_write' (X_LINK_ERROR)"