Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DepthAI SDK 1.12.0 #1064

Merged
merged 44 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
39e22c7
Add lock to SequenceNumSync.sync method
daniilpastukhov Jun 6, 2023
428c92c
Add encoded output for multi stage pipeline
daniilpastukhov Jun 8, 2023
1eb1da0
Fix recording path
daniilpastukhov Jun 8, 2023
4dc7ade
Add lock to SequenceNumSync.sync method
daniilpastukhov Jun 6, 2023
8d127e0
Merge remote-tracking branch 'origin/fix/race_cond' into fix/race_cond
daniilpastukhov Jun 8, 2023
5823eff
Add lock to critical zone in XoutTwoStage.new_msg
daniilpastukhov Jun 8, 2023
0afe871
NNComponent.Out.main now returns encoded output in case it was turned on
daniilpastukhov Jun 8, 2023
c7d6b72
Fix issue when incorrect folder was passed to on_finish_callback
daniilpastukhov Jun 12, 2023
478881e
Added option to contorl camera component (auto-focus and auto-exposur…
Erol444 Jun 13, 2023
46f23b8
Added example
Erol444 Jun 13, 2023
49bb9fe
Included Script node template and helper script
Erol444 Jun 13, 2023
bd017a8
Added support for 2stage NNs to use all resize modes for detection mo…
Erol444 Jun 14, 2023
8c8fae1
Merge pull request #1054 from luxonis/twostage_support_resize_mode
daniilpastukhov Jun 14, 2023
e689c9c
Merge pull request #1051 from luxonis/ae_af_on_detection_results
Erol444 Jun 14, 2023
8d9c9d1
Update type hint for track_labels
daniilpastukhov Jun 15, 2023
7893433
Merge branch 'develop' of github.com:luxonis/depthai into develop
daniilpastukhov Jun 20, 2023
2aa1c8f
Call cv2.waitKey only CV2 has GUI support
daniilpastukhov Jun 20, 2023
3a2a492
Merge remote-tracking branch 'origin/main' into develop
daniilpastukhov Jun 20, 2023
3929586
Move CV2_HAS_GUI_SUPPORT to constants.py
daniilpastukhov Jun 20, 2023
207299f
Change Sentry DSN
daniilpastukhov Jun 22, 2023
52e9b84
Add check for first packet to be keyframe, add frame shape passing fo…
daniilpastukhov Jun 26, 2023
86f08cd
Add docstrings
daniilpastukhov Jun 26, 2023
2ea8ffa
Update tests
daniilpastukhov Jun 26, 2023
ef7ad3c
Check for IR drivers before setting IR setting
daniilpastukhov Jun 26, 2023
0349602
Fix issue when OAK-D-LR produces exception
daniilpastukhov Jun 26, 2023
1a9b6ea
Minor refactor of AvWriter
daniilpastukhov Jun 26, 2023
f0684a0
Merge pull request #1062 from luxonis/fix/video_recording
daniilpastukhov Jun 26, 2023
379b452
Remove redundant condition
daniilpastukhov Jun 26, 2023
002252f
Merge pull request #1057 from luxonis/fix/cv2_poll
daniilpastukhov Jun 26, 2023
78d4e3c
Merge pull request #1063 from luxonis/fix/stereo_comp
daniilpastukhov Jun 26, 2023
88efaba
Merge pull request #1065 from luxonis/fix/race_cond
daniilpastukhov Jun 26, 2023
6844cea
Address issue when OAK-D-SR crashed
daniilpastukhov Jun 27, 2023
fb2c7ac
Merge remote-tracking branch 'origin/develop' into fix/sr_stereo
daniilpastukhov Jun 27, 2023
5731a82
Merge pull request #1067 from luxonis/fix/sr_stereo
daniilpastukhov Jun 27, 2023
60701dc
Add minor style improvements
daniilpastukhov Jun 27, 2023
4a77518
Update test_examples.py so it runs every example as a separate test
daniilpastukhov Jun 27, 2023
00c1a4f
Remove hardcoded h264 format
daniilpastukhov Jun 27, 2023
d67950d
Add test to extra_require list
daniilpastukhov Jun 27, 2023
b0565d8
Change colormap in stereo_encoded.py
daniilpastukhov Jun 27, 2023
5dd6970
Update test_examples.py
daniilpastukhov Jun 27, 2023
ecb31ec
Update custom_trigger.py
daniilpastukhov Jun 27, 2023
5ddaa16
Bump version to 1.12.0
daniilpastukhov Jun 27, 2023
dc55a2f
Bump depthai to 2.22.0
daniilpastukhov Jun 27, 2023
7479b25
Add text background color and transparency to visualizer
daniilpastukhov Jun 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion depthai_sdk/docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
author = 'Luxonis'

# The full version, including alpha/beta/rc tags
release = '1.11.0'
release = '1.12.0'


# -- General configuration ---------------------------------------------------
Expand Down
10 changes: 10 additions & 0 deletions depthai_sdk/examples/CameraComponent/camera_control_with_nn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from depthai_sdk import OakCamera

with OakCamera() as oak:
color = oak.create_camera('color')
face_det = oak.create_nn('face-detection-retail-0004', color)
# Control the camera's exposure/focus based on the (largest) detected face
color.control_with_nn(face_det, auto_focus=True, auto_exposure=True, debug=False)

oak.visualize(face_det, fps=True)
oak.start(blocking=True)
2 changes: 1 addition & 1 deletion depthai_sdk/examples/StereoComponent/stereo_encoded.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
stereo = oak.create_stereo('800p', fps=30, encode='h264')

# Set on-device output colorization, works only for encoded output
stereo.set_colormap(dai.Colormap.STEREO_JET)
stereo.set_colormap(dai.Colormap.JET)

oak.visualize(stereo.out.encoded, fps=True)
oak.start(blocking=True)
2 changes: 1 addition & 1 deletion depthai_sdk/examples/trigger_action/custom_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def my_condition(packet) -> bool:
with OakCamera() as oak:
color = oak.create_camera('color', fps=30)
stereo = oak.create_stereo('800p')
stereo.config_stereo(align='color')
stereo.config_stereo(align=color)

trigger = Trigger(input=stereo.out.depth, condition=my_condition, cooldown=30)
action = RecordAction(
Expand Down
2 changes: 1 addition & 1 deletion depthai_sdk/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ opencv-contrib-python>4
blobconverter>=1.4.1
pytube>=12.1.0
--extra-index-url https://artifacts.luxonis.com/artifactory/luxonis-python-snapshot-local/
depthai==2.21.2
depthai==2.22.0
PyTurboJPEG==1.6.4
marshmallow==3.17.0
xmltodict
Expand Down
12 changes: 12 additions & 0 deletions depthai_sdk/sdk_tests/components/nn/test_nn_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,15 @@ def callback(packet):
if not oak_camera.poll():
raise RuntimeError('Polling failed')
time.sleep(0.1)


def test_encoded_output():
with OakCamera() as oak_camera:
camera = oak_camera.create_camera('color', '1080p', encode='h264')

oak_camera.callback(camera.out.encoded, lambda x: print(x))
oak_camera.start(blocking=False)

for i in range(10):
oak_camera.poll()
time.sleep(0.1)
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import time

import depthai as dai
import pytest

from depthai_sdk.oak_camera import OakCamera
import depthai as dai


def test_stereo_output():
with OakCamera() as oak_camera:
if dai.CameraBoardSocket.LEFT not in oak_camera.sensors:
pytest.skip('Looks like camera does not have mono pair, skipping...')
else:
stereo = oak_camera.create_stereo('400p')
stereo = oak_camera.create_stereo('800p', encode='h264')

oak_camera.callback([stereo.out.depth, stereo.out.disparity,
stereo.out.rectified_left, stereo.out.rectified_right], callback=lambda x: None)
stereo.out.rectified_left, stereo.out.rectified_right,
stereo.out.encoded], callback=lambda x: None)
oak_camera.start(blocking=False)

for i in range(10):
Expand Down
45 changes: 28 additions & 17 deletions depthai_sdk/sdk_tests/test_examples.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
import os
import subprocess
import sys
import time
from pathlib import Path

import cv2
import pytest

EXAMPLES_DIR = Path(__file__).parent.parent.parent / "examples"
EXAMPLES_DIR = Path(__file__).parents[1] / 'examples'

# Create a temporary directory for the tests
Path('/tmp/depthai_sdk_tests').mkdir(exist_ok=True)
os.chdir('/tmp/depthai_sdk_tests')

def test_examples():
python_executable = Path(sys.executable)
for example in EXAMPLES_DIR.rglob("**/*.py"):
print(f"Running example: {example.name}")

result = subprocess.Popen(f"{python_executable} {example}", stdout=subprocess.PIPE, stderr=subprocess.PIPE,
env={"DISPLAY": ""}, shell=True)

time.sleep(5)
result.kill()
time.sleep(5)
print('Stderr: ', result.stderr.read().decode())

# if result.returncode and result.returncode != 0:
# assert False, f"{example} raised an exception: {result.stderr}"

cv2.destroyAllWindows()
@pytest.mark.parametrize('example', list(EXAMPLES_DIR.rglob("**/*.py")))
def test_examples(example):
print(f"Running {example}")
python_executable = Path(sys.executable)
result = subprocess.Popen(f"{python_executable} {example}",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env={
'DISPLAY': '',
'PYTHONPATH': f'{os.environ["PYTHONPATH"]}:{EXAMPLES_DIR.parent}'
},
shell=True)

time.sleep(5)
result.kill()
time.sleep(5)
print('Stderr: ', result.stderr.read().decode())

if result.returncode and result.returncode != 0:
assert False, f"{example} raised an exception: {result.stderr}"

cv2.destroyAllWindows()
5 changes: 3 additions & 2 deletions depthai_sdk/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

setup(
name='depthai-sdk',
version='1.11.0',
version='1.12.0',
description='This package provides an abstraction of the DepthAI API library.',
long_description=io.open("README.md", encoding="utf-8").read(),
long_description_content_type="text/markdown",
Expand All @@ -30,7 +30,8 @@
"replay": ['mcap>=0.0.10',
'mcap-ros1-support==0.0.8',
'rosbags==0.9.11'],
"record": ['av']
"record": ['av'],
"test": ['pytest']
},
project_urls={
"Bug Tracker": "https://github.com/luxonis/depthai/issues",
Expand Down
3 changes: 2 additions & 1 deletion depthai_sdk/src/depthai_sdk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from depthai_sdk.args_parser import ArgsParser
from depthai_sdk.classes.enum import ResizeMode
from depthai_sdk.constants import CV2_HAS_GUI_SUPPORT
from depthai_sdk.logger import set_logging_level
from depthai_sdk.oak_camera import OakCamera
from depthai_sdk.oak_device import OakDevice
Expand All @@ -10,7 +11,7 @@
from depthai_sdk.utils import _create_config, get_config_field
from depthai_sdk.visualize import *

__version__ = '1.11.0'
__version__ = '1.12.0'


def __import_sentry(sentry_dsn: str) -> None:
Expand Down
27 changes: 13 additions & 14 deletions depthai_sdk/src/depthai_sdk/classes/output_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,21 @@
from depthai_sdk.trigger_action.triggers.abstract_trigger import Trigger
from depthai_sdk.visualize.visualizer import Visualizer

def find_new_name(name: str, names: List[str]):
while True:
arr = name.split(' ')
num = arr[-1]
if num.isnumeric():
arr[-1] = str(int(num) + 1)
name = " ".join(arr)
else:
name = f"{name} 2"
if name not in names:
return name

class BaseConfig:
@abstractmethod
def setup(self, pipeline: dai.Pipeline, device, names: List[str]) -> List[XoutBase]:
def setup(self, pipeline: dai.Pipeline, device: dai.Device, names: List[str]) -> List[XoutBase]:
raise NotImplementedError()


Expand All @@ -39,24 +50,12 @@ def __init__(self, output: Callable,
self.visualizer_enabled = visualizer_enabled
self.record_path = record_path

def find_new_name(self, name: str, names: List[str]):
while True:
arr = name.split(' ')
num = arr[-1]
if num.isnumeric():
arr[-1] = str(int(num) + 1)
name = " ".join(arr)
else:
name = f"{name} 2"
if name not in names:
return name

def setup(self, pipeline: dai.Pipeline, device, names: List[str]) -> List[XoutBase]:
xoutbase: XoutBase = self.output(pipeline, device)
xoutbase.setup_base(self.callback)

if xoutbase.name in names: # Stream name already exist, append a number to it
xoutbase.name = self.find_new_name(xoutbase.name, names)
xoutbase.name = find_new_name(xoutbase.name, names)
names.append(xoutbase.name)

recorder = None
Expand Down
3 changes: 3 additions & 0 deletions depthai_sdk/src/depthai_sdk/classes/packets.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class _TrackingDetection(_Detection):
class _TwoStageDetection(_Detection):
nn_data: dai.NNData


class NNDataPacket:
"""
Contains only dai.NNData message
Expand All @@ -50,6 +51,7 @@ def __init__(self, name: str, nn_data: dai.NNData):
self.name = name
self.msg = nn_data


class FramePacket:
"""
Contains only dai.ImgFrame message and cv2 frame, which is used by visualization logic.
Expand Down Expand Up @@ -80,6 +82,7 @@ def __init__(self,
self.color_frame = color_frame
self.visualizer = visualizer


class DepthPacket(FramePacket):
mono_frame: dai.ImgFrame

Expand Down
35 changes: 35 additions & 0 deletions depthai_sdk/src/depthai_sdk/components/camera_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(self,
"""
super().__init__()
self.out = self.Out(self)
self._pipeline = pipeline

self.node: Optional[Union[dai.node.ColorCamera, dai.node.MonoCamera, dai.node.XLinkIn]] = None
self.encoder: Optional[dai.node.VideoEncoder] = None
Expand Down Expand Up @@ -279,6 +280,40 @@ def _config_camera_args(self, args: Dict):
else: # Replay
self.config_camera(fps=args.get('fps', None))

def control_with_nn(self, detection_component: 'NNComponent', auto_focus=True, auto_exposure=True, debug=False):
"""
Control the camera AF/AE/AWB based on the object detection results.

:param detection_component: NNComponent that will be used to control the camera
:param auto_focus: Enable auto focus to the object
:param auto_exposure: Enable auto exposure to the object
:param auto_white_balance: auto white balance to the object
"""

if not auto_focus and not auto_exposure:
logging.error(
'Attempted to control camera with NN, but both Auto-Focus and Auto-Exposure were disabled! Attempt ignored.'
)
return
if 'NNComponent' not in str(type(detection_component)):
raise ValueError('nn_component must be an instance of NNComponent!')
if not detection_component._is_detector():
raise ValueError('nn_component must be a object detection model (YOLO/MobileNetSSD based)!')

from depthai_sdk.components.control_camera_with_nn import control_camera_with_nn

control_camera_with_nn(
pipeline=self._pipeline,
camera_control=self.node.inputControl,
nn_output=detection_component.node.out,
resize_mode=detection_component._ar_resize_mode,
resolution=self.node.getResolution(),
nn_size = detection_component._size,
af=auto_focus,
ae=auto_exposure,
debug=debug
)

def config_color_camera(self,
interleaved: Optional[bool] = None,
color_order: Union[None, dai.ColorCameraProperties.ColorOrder, str] = None,
Expand Down
12 changes: 12 additions & 0 deletions depthai_sdk/src/depthai_sdk/components/camera_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,18 @@ def get_res(resolutions: Dict[Any, Tuple[int,int]]):
else:
raise Exception('Camera sensor type unknown!', type)

def get_resolution_size(
resolution: Union[
dai.ColorCameraProperties.SensorResolution,
dai.MonoCameraProperties.SensorResolution
]) -> Tuple[int,int]:
if resolution in colorResolutions:
return colorResolutions[resolution]
elif resolution in monoResolutions:
return monoResolutions[resolution]
else:
raise Exception('Camera sensor resolution unknown!', resolution)

def getClosesResolution(sensor: dai.CameraFeatures,
type: dai.CameraSensorType,
width: Optional[int] = None,
Expand Down
69 changes: 69 additions & 0 deletions depthai_sdk/src/depthai_sdk/components/control_camera_with_nn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import depthai as dai
from depthai_sdk.classes.enum import ResizeMode
from typing import Union, Tuple
from depthai_sdk.components.camera_helper import get_resolution_size
from pathlib import Path
from string import Template

def control_camera_with_nn(
pipeline: dai.Pipeline,
camera_control: dai.Node.Input,
nn_output: dai.Node.Output,
resize_mode: ResizeMode,
resolution: Union[dai.ColorCameraProperties.SensorResolution, dai.MonoCameraProperties.SensorResolution],
nn_size: Tuple[int, int],
af: bool,
ae: bool,
debug: bool
):
sensor_resolution = get_resolution_size(resolution)
# width / height (old ar)
sensor_ar = sensor_resolution[0] / sensor_resolution[1]
# NN ar (new ar)
nn_ar = nn_size[0] / nn_size[1]

if resize_mode == ResizeMode.LETTERBOX:
padding = (sensor_ar - nn_ar) / 2
if padding > 0:
init = f"xmin = 0; ymin = {-padding}; xmax = 1; ymax = {1 + padding}"
else:
init = f"xmin = {padding}; ymin = 0; xmax = {1 - padding}; ymax = 1"
elif resize_mode in [ResizeMode.CROP, ResizeMode.FULL_CROP]:
cropping = (1 - (nn_ar / sensor_ar)) / 2
if cropping < 0:
init = f"xmin = 0; ymin = {-cropping}; xmax = 1; ymax = {1 + cropping}"
else:
init = f"xmin = {cropping}; ymin = 0; xmax = {1 - cropping}; ymax = 1"
else: # Stretch
init = f"xmin=0; ymin=0; xmax=1; ymax=1"


resize_str = f"new_xmin=xmin+width*det.xmin; new_ymin=ymin+height*det.ymin; new_xmax=xmin+width*det.xmax; new_ymax=ymin+height*det.ymax;"
denormalize = f"startx=int(new_xmin*{sensor_resolution[0]}); starty=int(new_ymin*{sensor_resolution[1]}); new_width=int((new_xmax-new_xmin)*{sensor_resolution[0]}); new_height=int((new_ymax-new_ymin)*{sensor_resolution[1]});"
control_str = ''
if ae:
control_str += f"control.setAutoExposureRegion(startx, starty, new_width, new_height);"
if af:
control_str += f"control.setAutoFocusRegion(startx, starty, new_width, new_height);"


script_node = pipeline.create(dai.node.Script)
script_node.setProcessor(dai.ProcessorType.LEON_CSS) # More stable

with open(Path(__file__).parent / 'template_control_cam_with_nn.py', 'r') as file:
code = Template(file.read()).substitute(
DEBUG='' if debug else '#',
INIT=init,
RESIZE=resize_str,
DENORMALIZE=denormalize,
CONTROL=control_str
)
script_node.setScript(code)

if debug:
print(code)

# Node linking:
# NN output -> Script -> Camera input
nn_output.link(script_node.inputs['detections'])
script_node.outputs['control'].link(camera_control)
Loading