feat(disparity): publish disparity messages
This commit is contained in:
parent
552f69e46e
commit
1ee37f65af
@ -11,6 +11,7 @@ import depthai as dai
|
|||||||
import paho.mqtt.client as mqtt
|
import paho.mqtt.client as mqtt
|
||||||
|
|
||||||
from camera import oak_pipeline as cam
|
from camera import oak_pipeline as cam
|
||||||
|
from oak_pipeline import DisparityProcessor
|
||||||
|
|
||||||
CAMERA_EXPOSITION_DEFAULT = "default"
|
CAMERA_EXPOSITION_DEFAULT = "default"
|
||||||
CAMERA_EXPOSITION_8300US = "8300us"
|
CAMERA_EXPOSITION_8300US = "8300us"
|
||||||
@ -49,6 +50,9 @@ def _parse_args_cli() -> argparse.Namespace:
|
|||||||
help="threshold to filter detected objects",
|
help="threshold to filter detected objects",
|
||||||
type=float,
|
type=float,
|
||||||
default=_get_env_float_value("OBJECTS_THRESHOLD", 0.2))
|
default=_get_env_float_value("OBJECTS_THRESHOLD", 0.2))
|
||||||
|
parser.add_argument("-o", "---mqtt-topic-robocar-disparity",
|
||||||
|
help="MQTT topic where to publish disparity results",
|
||||||
|
default=_get_env_value("MQTT_TOPIC_DISPARITY", "/disparity"))
|
||||||
parser.add_argument("-f", "--camera-fps",
|
parser.add_argument("-f", "--camera-fps",
|
||||||
help="set rate at which camera should produce frames",
|
help="set rate at which camera should produce frames",
|
||||||
type=int,
|
type=int,
|
||||||
@ -104,6 +108,7 @@ def execute_from_command_line() -> None:
|
|||||||
object_processor = cam.ObjectProcessor(mqtt_client=client,
|
object_processor = cam.ObjectProcessor(mqtt_client=client,
|
||||||
objects_topic=args.mqtt_topic_robocar_objects,
|
objects_topic=args.mqtt_topic_robocar_objects,
|
||||||
objects_threshold=args.objects_threshold)
|
objects_threshold=args.objects_threshold)
|
||||||
|
disparity_processor = cam.DisparityProcessor(mqtt_client=client, disparity_topic=args.mqtt_topic_robocar_disparity)
|
||||||
|
|
||||||
pipeline = dai.Pipeline()
|
pipeline = dai.Pipeline()
|
||||||
if args.camera_tuning_exposition == CAMERA_EXPOSITION_500US:
|
if args.camera_tuning_exposition == CAMERA_EXPOSITION_500US:
|
||||||
@ -120,7 +125,9 @@ def execute_from_command_line() -> None:
|
|||||||
img_width=args.image_width,
|
img_width=args.image_width,
|
||||||
img_height=args.image_height,
|
img_height=args.image_height,
|
||||||
fps=args.camera_fps,
|
fps=args.camera_fps,
|
||||||
))
|
),
|
||||||
|
depth_source=cam.DepthSource(pipeline=pipeline),
|
||||||
|
disparity_processor=disparity_processor)
|
||||||
|
|
||||||
def sigterm_handler(signum: int, frame: typing.Optional[
|
def sigterm_handler(signum: int, frame: typing.Optional[
|
||||||
types.FrameType]) -> None: # pylint: disable=unused-argument # need to implement handler signature
|
types.FrameType]) -> None: # pylint: disable=unused-argument # need to implement handler signature
|
||||||
|
@ -26,6 +26,8 @@ _NN_HEIGHT = 192
|
|||||||
_PREVIEW_WIDTH = 640
|
_PREVIEW_WIDTH = 640
|
||||||
_PREVIEW_HEIGHT = 480
|
_PREVIEW_HEIGHT = 480
|
||||||
|
|
||||||
|
_CAMERA_BASELINE_IN_MM = 75
|
||||||
|
|
||||||
|
|
||||||
class ObjectProcessor:
|
class ObjectProcessor:
|
||||||
"""
|
"""
|
||||||
@ -124,6 +126,37 @@ class FrameProcessor:
|
|||||||
return frame_msg.id
|
return frame_msg.id
|
||||||
|
|
||||||
|
|
||||||
|
class DisparityProcessor:
|
||||||
|
"""
|
||||||
|
Processor for camera frames
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, mqtt_client: mqtt.Client, disparity_topic: str):
|
||||||
|
self._mqtt_client = mqtt_client
|
||||||
|
self._disparity_topic = disparity_topic
|
||||||
|
|
||||||
|
def process(self, img: dai.ImgFrame, frame_ref: evt.FrameRef, focal_length_in_pixels: float,
|
||||||
|
baseline_mm: float = _CAMERA_BASELINE_IN_MM) -> None:
|
||||||
|
im_frame = img.getCvFrame()
|
||||||
|
is_success, im_buf_arr = cv2.imencode(".jpg", im_frame)
|
||||||
|
if not is_success:
|
||||||
|
raise FrameProcessError("unable to process to encode frame to jpg")
|
||||||
|
byte_im = im_buf_arr.tobytes()
|
||||||
|
|
||||||
|
disparity_msg = evt.DisparityMessage()
|
||||||
|
disparity_msg.disparity = byte_im
|
||||||
|
disparity_msg.frame_ref.name = frame_ref.name
|
||||||
|
disparity_msg.frame_ref.id = frame_ref.id
|
||||||
|
disparity_msg.frame_ref.created_at.FromDatetime(frame_ref.created_at.ToDatetime())
|
||||||
|
disparity_msg.focal_length_in_pixels = focal_length_in_pixels
|
||||||
|
disparity_msg.baseline_in_mm = baseline_mm
|
||||||
|
|
||||||
|
self._mqtt_client.publish(topic=self._disparity_topic,
|
||||||
|
payload=disparity_msg.SerializeToString(),
|
||||||
|
qos=0,
|
||||||
|
retain=False)
|
||||||
|
|
||||||
|
|
||||||
class Source(abc.ABC):
|
class Source(abc.ABC):
|
||||||
"""Base class for image source"""
|
"""Base class for image source"""
|
||||||
|
|
||||||
@ -233,6 +266,49 @@ class CameraSource(Source):
|
|||||||
return manip
|
return manip
|
||||||
|
|
||||||
|
|
||||||
|
class DepthSource(Source):
|
||||||
|
def __init__(self, pipeline: dai.Pipeline,
|
||||||
|
extended_disparity: bool = False,
|
||||||
|
subpixel: bool = False,
|
||||||
|
lr_check: bool = True
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
# Closer-in minimum depth, disparity range is doubled (from 95 to 190):
|
||||||
|
extended_disparity = False
|
||||||
|
# Better accuracy for longer distance, fractional disparity 32-levels:
|
||||||
|
subpixel = False
|
||||||
|
# Better handling for occlusions:
|
||||||
|
lr_check = True
|
||||||
|
"""
|
||||||
|
self._monoLeft = pipeline.create(dai.node.MonoCamera)
|
||||||
|
self._monoRight = pipeline.create(dai.node.MonoCamera)
|
||||||
|
self._depth = pipeline.create(dai.node.StereoDepth)
|
||||||
|
self._xout_disparity = pipeline.create(dai.node.XLinkOut)
|
||||||
|
|
||||||
|
self._xout_disparity.setStreamName("disparity")
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
self._monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
|
||||||
|
self._monoLeft.setCamera("left")
|
||||||
|
self._monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P)
|
||||||
|
self._monoRight.setCamera("right")
|
||||||
|
|
||||||
|
# Create a node that will produce the depth map
|
||||||
|
# (using disparity output as it's easier to visualize depth this way)
|
||||||
|
self._depth.setDefaultProfilePreset(dai.node.StereoDepth.PresetMode.HIGH_DENSITY)
|
||||||
|
# Options: MEDIAN_OFF, KERNEL_3x3, KERNEL_5x5, KERNEL_7x7 (default)
|
||||||
|
self._depth.initialConfig.setMedianFilter(dai.MedianFilter.KERNEL_7x7)
|
||||||
|
self._depth.setLeftRightCheck(lr_check)
|
||||||
|
self._depth.setExtendedDisparity(extended_disparity)
|
||||||
|
self._depth.setSubpixel(subpixel)
|
||||||
|
|
||||||
|
def get_stream_name(self) -> str:
|
||||||
|
return self._xout_disparity.getStreamName()
|
||||||
|
|
||||||
|
def link(self, input_node: dai.Node.Input) -> None:
|
||||||
|
self._depth.disparity.link(input_node)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MqttConfig:
|
class MqttConfig:
|
||||||
"""MQTT configuration"""
|
"""MQTT configuration"""
|
||||||
@ -305,15 +381,19 @@ class PipelineController:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, frame_processor: FrameProcessor,
|
def __init__(self, frame_processor: FrameProcessor,
|
||||||
object_processor: ObjectProcessor, camera: Source, object_node: ObjectDetectionNN,
|
object_processor: ObjectProcessor, disparity_processor: DisparityProcessor,
|
||||||
|
camera: Source, depth_source: Source, object_node: ObjectDetectionNN,
|
||||||
pipeline: dai.Pipeline):
|
pipeline: dai.Pipeline):
|
||||||
self._frame_processor = frame_processor
|
self._frame_processor = frame_processor
|
||||||
self._object_processor = object_processor
|
self._object_processor = object_processor
|
||||||
|
self._disparity_processor = disparity_processor
|
||||||
self._camera = camera
|
self._camera = camera
|
||||||
|
self._depth_source = depth_source
|
||||||
self._object_node = object_node
|
self._object_node = object_node
|
||||||
self._stop = False
|
self._stop = False
|
||||||
self._pipeline = pipeline
|
self._pipeline = pipeline
|
||||||
self._configure_pipeline()
|
self._configure_pipeline()
|
||||||
|
self._focal_length_in_pixels: float | None = None
|
||||||
|
|
||||||
def _configure_pipeline(self) -> None:
|
def _configure_pipeline(self) -> None:
|
||||||
logger.info("configure pipeline")
|
logger.info("configure pipeline")
|
||||||
@ -337,6 +417,11 @@ class PipelineController:
|
|||||||
logger.info('Connected cameras: %s', str(dev.getConnectedCameras()))
|
logger.info('Connected cameras: %s', str(dev.getConnectedCameras()))
|
||||||
logger.info("output queues found: %s", str(''.join(dev.getOutputQueueNames()))) # type: ignore
|
logger.info("output queues found: %s", str(''.join(dev.getOutputQueueNames()))) # type: ignore
|
||||||
|
|
||||||
|
calib_data = dev.readCalibration()
|
||||||
|
intrinsics = calib_data.getCameraIntrinsics(dai.CameraBoardSocket.CAM_C)
|
||||||
|
self._focal_length_in_pixels = intrinsics[0][0]
|
||||||
|
logger.info('Right mono camera focal length in pixels: %s', self._focal_length_in_pixels)
|
||||||
|
|
||||||
dev.startPipeline()
|
dev.startPipeline()
|
||||||
# Queues
|
# Queues
|
||||||
queue_size = 4
|
queue_size = 4
|
||||||
@ -344,6 +429,8 @@ class PipelineController:
|
|||||||
blocking=False)
|
blocking=False)
|
||||||
q_nn = dev.getOutputQueue(name=self._object_node.get_stream_name(), maxSize=queue_size, # type: ignore
|
q_nn = dev.getOutputQueue(name=self._object_node.get_stream_name(), maxSize=queue_size, # type: ignore
|
||||||
blocking=False)
|
blocking=False)
|
||||||
|
q_disparity = dev.getOutputQueue(name=self._depth_source.get_stream_name(), maxSize=queue_size, # type: ignore
|
||||||
|
blocking=False)
|
||||||
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
counter = 0
|
counter = 0
|
||||||
@ -355,7 +442,7 @@ class PipelineController:
|
|||||||
logger.info("stop loop event")
|
logger.info("stop loop event")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self._loop_on_camera_events(q_nn, q_rgb)
|
self._loop_on_camera_events(q_nn, q_rgb, q_disparity)
|
||||||
# pylint: disable=broad-except # bad frame or event must not stop loop
|
# pylint: disable=broad-except # bad frame or event must not stop loop
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.exception("unexpected error: %s", str(ex))
|
logger.exception("unexpected error: %s", str(ex))
|
||||||
@ -369,8 +456,7 @@ class PipelineController:
|
|||||||
display_time = time.time()
|
display_time = time.time()
|
||||||
logger.info("fps: %s", fps)
|
logger.info("fps: %s", fps)
|
||||||
|
|
||||||
|
def _loop_on_camera_events(self, q_nn: dai.DataOutputQueue, q_rgb: dai.DataOutputQueue, q_disparity: dai.DataOutputQueue) -> None:
|
||||||
def _loop_on_camera_events(self, q_nn: dai.DataOutputQueue, q_rgb: dai.DataOutputQueue) -> None:
|
|
||||||
logger.debug("wait for new frame")
|
logger.debug("wait for new frame")
|
||||||
|
|
||||||
# Wait for frame
|
# Wait for frame
|
||||||
@ -390,6 +476,11 @@ class PipelineController:
|
|||||||
self._object_processor.process(in_nn, frame_ref)
|
self._object_processor.process(in_nn, frame_ref)
|
||||||
logger.debug("objects processed")
|
logger.debug("objects processed")
|
||||||
|
|
||||||
|
logger.debug("process disparity")
|
||||||
|
in_disparity: dai.ImgFrame = q_disparity.get() # type: ignore
|
||||||
|
self._disparity_processor.process(in_disparity, frame_ref=frame_ref,
|
||||||
|
focal_length_in_pixels=self._focal_length_in_pixels)
|
||||||
|
logger.debug("disparity processed")
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -10,7 +10,7 @@ import paho.mqtt.client as mqtt
|
|||||||
import pytest
|
import pytest
|
||||||
import pytest_mock
|
import pytest_mock
|
||||||
|
|
||||||
import camera.depthai
|
from camera.oak_pipeline import DisparityProcessor, ObjectProcessor, FrameProcessor, FrameProcessError
|
||||||
|
|
||||||
Object = dict[str, float]
|
Object = dict[str, float]
|
||||||
|
|
||||||
@ -76,16 +76,16 @@ class TestObjectProcessor:
|
|||||||
return m
|
return m
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def object_processor(self, mqtt_client: mqtt.Client) -> camera.depthai.ObjectProcessor:
|
def object_processor(self, mqtt_client: mqtt.Client) -> ObjectProcessor:
|
||||||
return camera.depthai.ObjectProcessor(mqtt_client, "topic/object", 0.2)
|
return ObjectProcessor(mqtt_client, "topic/object", 0.2)
|
||||||
|
|
||||||
def test_process_without_object(self, object_processor: camera.depthai.ObjectProcessor, mqtt_client: mqtt.Client,
|
def test_process_without_object(self, object_processor: ObjectProcessor, mqtt_client: mqtt.Client,
|
||||||
raw_objects_empty: dai.NNData, frame_ref: events.events_pb2.FrameRef) -> None:
|
raw_objects_empty: dai.NNData, frame_ref: events.events_pb2.FrameRef) -> None:
|
||||||
object_processor.process(raw_objects_empty, frame_ref)
|
object_processor.process(raw_objects_empty, frame_ref)
|
||||||
publish_mock: unittest.mock.MagicMock = mqtt_client.publish # type: ignore
|
publish_mock: unittest.mock.MagicMock = mqtt_client.publish # type: ignore
|
||||||
publish_mock.assert_not_called()
|
publish_mock.assert_not_called()
|
||||||
|
|
||||||
def test_process_with_object_with_low_score(self, object_processor: camera.depthai.ObjectProcessor,
|
def test_process_with_object_with_low_score(self, object_processor: ObjectProcessor,
|
||||||
mqtt_client: mqtt.Client, raw_objects_one: dai.NNData,
|
mqtt_client: mqtt.Client, raw_objects_one: dai.NNData,
|
||||||
frame_ref: events.events_pb2.FrameRef) -> None:
|
frame_ref: events.events_pb2.FrameRef) -> None:
|
||||||
object_processor._objects_threshold = 0.9
|
object_processor._objects_threshold = 0.9
|
||||||
@ -94,7 +94,7 @@ class TestObjectProcessor:
|
|||||||
publish_mock.assert_not_called()
|
publish_mock.assert_not_called()
|
||||||
|
|
||||||
def test_process_with_one_object(self,
|
def test_process_with_one_object(self,
|
||||||
object_processor: camera.depthai.ObjectProcessor, mqtt_client: mqtt.Client,
|
object_processor: ObjectProcessor, mqtt_client: mqtt.Client,
|
||||||
raw_objects_one: dai.NNData, frame_ref: events.events_pb2.FrameRef,
|
raw_objects_one: dai.NNData, frame_ref: events.events_pb2.FrameRef,
|
||||||
object1: Object) -> None:
|
object1: Object) -> None:
|
||||||
object_processor.process(raw_objects_one, frame_ref)
|
object_processor.process(raw_objects_one, frame_ref)
|
||||||
@ -120,10 +120,10 @@ class TestObjectProcessor:
|
|||||||
|
|
||||||
class TestFrameProcessor:
|
class TestFrameProcessor:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def frame_processor(self, mqtt_client: mqtt.Client) -> camera.depthai.FrameProcessor:
|
def frame_processor(self, mqtt_client: mqtt.Client) -> FrameProcessor:
|
||||||
return camera.depthai.FrameProcessor(mqtt_client, "topic/frame")
|
return FrameProcessor(mqtt_client, "topic/frame")
|
||||||
|
|
||||||
def test_process(self, frame_processor: camera.depthai.FrameProcessor, mocker: pytest_mock.MockerFixture,
|
def test_process(self, frame_processor: FrameProcessor, mocker: pytest_mock.MockerFixture,
|
||||||
mqtt_client: mqtt.Client) -> None:
|
mqtt_client: mqtt.Client) -> None:
|
||||||
img: dai.ImgFrame = mocker.MagicMock()
|
img: dai.ImgFrame = mocker.MagicMock()
|
||||||
mocker.patch(target="cv2.imencode").return_value = (True, np.array(b"img content"))
|
mocker.patch(target="cv2.imencode").return_value = (True, np.array(b"img content"))
|
||||||
@ -145,12 +145,57 @@ class TestFrameProcessor:
|
|||||||
assert now - datetime.timedelta(
|
assert now - datetime.timedelta(
|
||||||
milliseconds=10) < frame_msg.id.created_at.ToDatetime() < now + datetime.timedelta(milliseconds=10)
|
milliseconds=10) < frame_msg.id.created_at.ToDatetime() < now + datetime.timedelta(milliseconds=10)
|
||||||
|
|
||||||
def test_process_error(self, frame_processor: camera.depthai.FrameProcessor, mocker: pytest_mock.MockerFixture,
|
def test_process_error(self, frame_processor: FrameProcessor, mocker: pytest_mock.MockerFixture,
|
||||||
mqtt_client: mqtt.Client) -> None:
|
mqtt_client: mqtt.Client) -> None:
|
||||||
img: dai.ImgFrame = mocker.MagicMock()
|
img: dai.ImgFrame = mocker.MagicMock()
|
||||||
mocker.patch(target="cv2.imencode").return_value = (False, None)
|
mocker.patch(target="cv2.imencode").return_value = (False, None)
|
||||||
|
|
||||||
with pytest.raises(camera.depthai.FrameProcessError) as ex:
|
with pytest.raises(FrameProcessError) as ex:
|
||||||
_ = frame_processor.process(img)
|
_ = frame_processor.process(img)
|
||||||
exception_raised = ex.value
|
exception_raised = ex.value
|
||||||
assert exception_raised.message == "unable to process to encode frame to jpg"
|
assert exception_raised.message == "unable to process to encode frame to jpg"
|
||||||
|
|
||||||
|
|
||||||
|
class TestDisparityProcessor:
|
||||||
|
@pytest.fixture
|
||||||
|
def frame_ref(self) -> events.events_pb2.FrameRef:
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
frame_msg = events.events_pb2.FrameMessage()
|
||||||
|
frame_msg.id.name = "robocar-oak-camera-oak"
|
||||||
|
frame_msg.id.id = str(int(now.timestamp() * 1000))
|
||||||
|
frame_msg.id.created_at.FromDatetime(now)
|
||||||
|
return frame_msg.id
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def disparity_processor(self, mqtt_client: mqtt.Client) -> DisparityProcessor:
|
||||||
|
return DisparityProcessor(mqtt_client, "topic/disparity")
|
||||||
|
|
||||||
|
def test_process(self, disparity_processor: DisparityProcessor, mocker: pytest_mock.MockerFixture,
|
||||||
|
frame_ref: events.events_pb2.FrameRef,
|
||||||
|
mqtt_client: mqtt.Client) -> None:
|
||||||
|
img: dai.ImgFrame = mocker.MagicMock()
|
||||||
|
mocker.patch(target="cv2.imencode").return_value = (True, np.array(b"img content"))
|
||||||
|
|
||||||
|
disparity_processor.process(img, frame_ref, 42)
|
||||||
|
|
||||||
|
pub_mock: unittest.mock.MagicMock = mqtt_client.publish # type: ignore
|
||||||
|
pub_mock.assert_called_once_with(payload=unittest.mock.ANY, qos=0, retain=False, topic="topic/disparity")
|
||||||
|
payload = pub_mock.call_args.kwargs['payload']
|
||||||
|
disparity_msg = events.events_pb2.DisparityMessage()
|
||||||
|
disparity_msg.ParseFromString(payload)
|
||||||
|
|
||||||
|
assert disparity_msg.frame_ref == frame_ref
|
||||||
|
assert disparity_msg.disparity == b"img content"
|
||||||
|
assert disparity_msg.focal_length_in_pixels == 42
|
||||||
|
assert disparity_msg.baseline_in_mm == 75
|
||||||
|
|
||||||
|
def test_process_error(self, disparity_processor: DisparityProcessor, mocker: pytest_mock.MockerFixture,
|
||||||
|
frame_ref: events.events_pb2.FrameRef,
|
||||||
|
mqtt_client: mqtt.Client) -> None:
|
||||||
|
img: dai.ImgFrame = mocker.MagicMock()
|
||||||
|
mocker.patch(target="cv2.imencode").return_value = (False, None)
|
||||||
|
|
||||||
|
with pytest.raises(FrameProcessError) as ex:
|
||||||
|
disparity_processor.process(img, frame_ref, 42)
|
||||||
|
exception_raised = ex.value
|
||||||
|
assert exception_raised.message == "unable to process to encode frame to jpg"
|
||||||
|
8
poetry.lock
generated
8
poetry.lock
generated
@ -1325,13 +1325,13 @@ requests = ">=2.0.1,<3.0.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "robocar-protobuf"
|
name = "robocar-protobuf"
|
||||||
version = "1.3.0"
|
version = "1.5.2"
|
||||||
description = "Protobuf message definitions for robocar"
|
description = "Protobuf message definitions for robocar"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10,<4.0"
|
python-versions = ">=3.10,<4.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "robocar_protobuf-1.3.0-py3-none-any.whl", hash = "sha256:61216d6957f650cca9adaf958a707e740b11f8030888202bfaf66f0752a3026f"},
|
{file = "robocar_protobuf-1.5.2-py3-none-any.whl", hash = "sha256:79b977c996cb1a65b2724bb8c524b8175bd2138fb5687a9d00d564c9037fbd37"},
|
||||||
{file = "robocar_protobuf-1.3.0.tar.gz", hash = "sha256:935d2e3542ed3db9f15a8d94d728f69276f4850d66b9c1987d0899abb95ab2ac"},
|
{file = "robocar_protobuf-1.5.2.tar.gz", hash = "sha256:ac1ab1322fd5f23a73d5d88905438b06e0a83eb918c6f882643d37dd29efde70"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -1652,4 +1652,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "8fd14cd097e9dee4d06b2264cb5a11979f95a00863920002b8d0106a20e7c65a"
|
content-hash = "a092e2de71959e10e690264dcf83191a20f87d9b63bc4f45e9e7f20999e35c6e"
|
||||||
|
@ -16,7 +16,7 @@ protobuf3 = "^0.2.1"
|
|||||||
google = "^3.0.0"
|
google = "^3.0.0"
|
||||||
protobuf = "^4.21.8"
|
protobuf = "^4.21.8"
|
||||||
opencv-python-headless = "^4.6.0.66"
|
opencv-python-headless = "^4.6.0.66"
|
||||||
robocar-protobuf = {version = "^1.3.0", source = "robocar"}
|
robocar-protobuf = {version = "^1.5", source = "robocar"}
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.test.dependencies]
|
[tool.poetry.group.test.dependencies]
|
||||||
|
Loading…
Reference in New Issue
Block a user