From 2dab350cab0671844d7e942c7124abfd71e9440a Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 15 Nov 2021 16:53:24 +0200 Subject: [PATCH 1/4] FW support for OAK-D-Lite and bindings for: - new resolutions: Color THE_13_MP and Mono THE_480_P - device.getCameraSensorNames --- depthai-core | 2 +- src/DeviceBindings.cpp | 3 ++- src/pipeline/NodeBindings.cpp | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/depthai-core b/depthai-core index 5a5b2f0ae..cfc32c96e 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 5a5b2f0ae279dc82cdd93da371751c1c5a208afe +Subproject commit cfc32c96ed2948f090cbe863f5c97e7c1be83a44 diff --git a/src/DeviceBindings.cpp b/src/DeviceBindings.cpp index 68f9bce57..8661d3e5a 100644 --- a/src/DeviceBindings.cpp +++ b/src/DeviceBindings.cpp @@ -253,6 +253,7 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ .def("setSystemInformationLoggingRate", [](DeviceBase& d, float hz) { py::gil_scoped_release release; d.setSystemInformationLoggingRate(hz); }, py::arg("rateHz"), DOC(dai, DeviceBase, setSystemInformationLoggingRate)) .def("getSystemInformationLoggingRate", [](DeviceBase& d) { py::gil_scoped_release release; return d.getSystemInformationLoggingRate(); }, DOC(dai, DeviceBase, getSystemInformationLoggingRate)) .def("getConnectedCameras", [](DeviceBase& d) { py::gil_scoped_release release; return d.getConnectedCameras(); }, DOC(dai, DeviceBase, getConnectedCameras)) + .def("getCameraSensorNames", [](DeviceBase& d) { py::gil_scoped_release release; return d.getCameraSensorNames(); }, DOC(dai, DeviceBase, getCameraSensorNames)) .def("getDdrMemoryUsage", [](DeviceBase& d) { py::gil_scoped_release release; return d.getDdrMemoryUsage(); }, DOC(dai, DeviceBase, getDdrMemoryUsage)) .def("getCmxMemoryUsage", [](DeviceBase& d) { py::gil_scoped_release release; return d.getCmxMemoryUsage(); }, DOC(dai, DeviceBase, getCmxMemoryUsage)) .def("getLeonCssHeapUsage", [](DeviceBase& d) { py::gil_scoped_release release; return d.getLeonCssHeapUsage(); }, DOC(dai, DeviceBase, getLeonCssHeapUsage)) @@ -316,4 +317,4 @@ void DeviceBindings::bind(pybind11::module& m, void* pCallstack){ ; -} \ No newline at end of file +} diff --git a/src/pipeline/NodeBindings.cpp b/src/pipeline/NodeBindings.cpp index 44dc85a44..0ae11feb3 100644 --- a/src/pipeline/NodeBindings.cpp +++ b/src/pipeline/NodeBindings.cpp @@ -234,6 +234,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .value("THE_1080_P", ColorCameraProperties::SensorResolution::THE_1080_P) .value("THE_4_K", ColorCameraProperties::SensorResolution::THE_4_K) .value("THE_12_MP", ColorCameraProperties::SensorResolution::THE_12_MP) + .value("THE_13_MP", ColorCameraProperties::SensorResolution::THE_13_MP) ; colorCameraPropertiesColorOrder @@ -266,6 +267,7 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .value("THE_720_P", MonoCameraProperties::SensorResolution::THE_720_P) .value("THE_800_P", MonoCameraProperties::SensorResolution::THE_800_P) .value("THE_400_P", MonoCameraProperties::SensorResolution::THE_400_P) + .value("THE_480_P", MonoCameraProperties::SensorResolution::THE_480_P) ; monoCameraProperties From e662317a84ee88171ddeb51a2596e526f4e04ed8 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Sun, 21 Nov 2021 16:55:40 +0200 Subject: [PATCH 2/4] Update core/FW: VideoEncoder source size configured when receiving 1st frame --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index cfc32c96e..e56e1bbce 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit cfc32c96ed2948f090cbe863f5c97e7c1be83a44 +Subproject commit e56e1bbce21d1f87304e89c154ce0cb09df2f94a From 1cbc036926b56ee605c6f31d22334117523ee1d7 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 22 Nov 2021 18:13:36 +0200 Subject: [PATCH 3/4] Update VideoEncoder bindings and examples for frame size removal --- examples/ColorCamera/rgb_camera_control.py | 4 +- examples/Script/script_http_server.py | 2 +- examples/Script/script_mjpeg_server.py | 2 +- examples/VideoEncoder/disparity_encoding.py | 2 +- examples/VideoEncoder/encoding_max_limit.py | 6 +- examples/VideoEncoder/rgb_encoding.py | 2 +- .../VideoEncoder/rgb_full_resolution_saver.py | 2 +- examples/VideoEncoder/rgb_mono_encoding.py | 6 +- examples/mixed/rgb_encoding_mobilenet.py | 2 +- examples/mixed/rgb_encoding_mono_mobilenet.py | 2 +- .../rgb_encoding_mono_mobilenet_depth.py | 2 +- src/pipeline/NodeBindings.cpp | 60 ++++++++++++++++--- 12 files changed, 67 insertions(+), 25 deletions(-) diff --git a/examples/ColorCamera/rgb_camera_control.py b/examples/ColorCamera/rgb_camera_control.py index 179ebdd62..edddf8ec6 100755 --- a/examples/ColorCamera/rgb_camera_control.py +++ b/examples/ColorCamera/rgb_camera_control.py @@ -49,8 +49,8 @@ def clamp(num, v0, v1): # Properties camRgb.setVideoSize(640, 360) camRgb.setPreviewSize(300, 300) -videoEncoder.setDefaultProfilePreset(camRgb.getVideoSize(), camRgb.getFps(), dai.VideoEncoderProperties.Profile.MJPEG) -stillEncoder.setDefaultProfilePreset(camRgb.getStillSize(), 1, dai.VideoEncoderProperties.Profile.MJPEG) +videoEncoder.setDefaultProfilePreset(camRgb.getFps(), dai.VideoEncoderProperties.Profile.MJPEG) +stillEncoder.setDefaultProfilePreset(1, dai.VideoEncoderProperties.Profile.MJPEG) # Linking camRgb.video.link(videoEncoder.input) diff --git a/examples/Script/script_http_server.py b/examples/Script/script_http_server.py index ef4fbcf2b..daabba634 100755 --- a/examples/Script/script_http_server.py +++ b/examples/Script/script_http_server.py @@ -10,7 +10,7 @@ cam = pipeline.create(dai.node.ColorCamera) # VideoEncoder jpeg = pipeline.create(dai.node.VideoEncoder) -jpeg.setDefaultProfilePreset(cam.getVideoSize(), cam.getFps(), dai.VideoEncoderProperties.Profile.MJPEG) +jpeg.setDefaultProfilePreset(cam.getFps(), dai.VideoEncoderProperties.Profile.MJPEG) # Script node script = pipeline.create(dai.node.Script) diff --git a/examples/Script/script_mjpeg_server.py b/examples/Script/script_mjpeg_server.py index a93d6bee7..f6a8f58e1 100755 --- a/examples/Script/script_mjpeg_server.py +++ b/examples/Script/script_mjpeg_server.py @@ -10,7 +10,7 @@ cam = pipeline.create(dai.node.ColorCamera) # VideoEncoder jpeg = pipeline.create(dai.node.VideoEncoder) -jpeg.setDefaultProfilePreset(cam.getVideoSize(), cam.getFps(), dai.VideoEncoderProperties.Profile.MJPEG) +jpeg.setDefaultProfilePreset(cam.getFps(), dai.VideoEncoderProperties.Profile.MJPEG) # Script node script = pipeline.create(dai.node.Script) diff --git a/examples/VideoEncoder/disparity_encoding.py b/examples/VideoEncoder/disparity_encoding.py index 8d14799fb..a341aede1 100755 --- a/examples/VideoEncoder/disparity_encoding.py +++ b/examples/VideoEncoder/disparity_encoding.py @@ -29,7 +29,7 @@ videoEnc = pipeline.create(dai.node.VideoEncoder) # Depth resolution/FPS will be the same as mono resolution/FPS -videoEnc.setDefaultProfilePreset(monoLeft.getResolutionSize(), monoLeft.getFps(), dai.VideoEncoderProperties.Profile.MJPEG) +videoEnc.setDefaultProfilePreset(monoLeft.getFps(), dai.VideoEncoderProperties.Profile.MJPEG) depth.disparity.link(videoEnc.input) xout = pipeline.create(dai.node.XLinkOut) diff --git a/examples/VideoEncoder/encoding_max_limit.py b/examples/VideoEncoder/encoding_max_limit.py index d25c07478..b01f430ed 100755 --- a/examples/VideoEncoder/encoding_max_limit.py +++ b/examples/VideoEncoder/encoding_max_limit.py @@ -28,9 +28,9 @@ monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) # Setting to 26fps will trigger error -ve1.setDefaultProfilePreset(1280, 720, 25, dai.VideoEncoderProperties.Profile.H264_MAIN) -ve2.setDefaultProfilePreset(3840, 2160, 25, dai.VideoEncoderProperties.Profile.H265_MAIN) -ve3.setDefaultProfilePreset(1280, 720, 25, dai.VideoEncoderProperties.Profile.H264_MAIN) +ve1.setDefaultProfilePreset(25, dai.VideoEncoderProperties.Profile.H264_MAIN) +ve2.setDefaultProfilePreset(25, dai.VideoEncoderProperties.Profile.H265_MAIN) +ve3.setDefaultProfilePreset(25, dai.VideoEncoderProperties.Profile.H264_MAIN) # Linking monoLeft.out.link(ve1.input) diff --git a/examples/VideoEncoder/rgb_encoding.py b/examples/VideoEncoder/rgb_encoding.py index dbbb5f062..45b51f991 100755 --- a/examples/VideoEncoder/rgb_encoding.py +++ b/examples/VideoEncoder/rgb_encoding.py @@ -15,7 +15,7 @@ # Properties camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) -videoEnc.setDefaultProfilePreset(3840, 2160, 30, dai.VideoEncoderProperties.Profile.H265_MAIN) +videoEnc.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H265_MAIN) # Linking camRgb.video.link(videoEnc.input) diff --git a/examples/VideoEncoder/rgb_full_resolution_saver.py b/examples/VideoEncoder/rgb_full_resolution_saver.py index f15aac561..bf62c0efe 100755 --- a/examples/VideoEncoder/rgb_full_resolution_saver.py +++ b/examples/VideoEncoder/rgb_full_resolution_saver.py @@ -20,7 +20,7 @@ # Properties camRgb.setBoardSocket(dai.CameraBoardSocket.RGB) camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K) -videoEnc.setDefaultProfilePreset(camRgb.getVideoSize(), camRgb.getFps(), dai.VideoEncoderProperties.Profile.MJPEG) +videoEnc.setDefaultProfilePreset(camRgb.getFps(), dai.VideoEncoderProperties.Profile.MJPEG) # Linking camRgb.video.link(xoutRgb.input) diff --git a/examples/VideoEncoder/rgb_mono_encoding.py b/examples/VideoEncoder/rgb_mono_encoding.py index ca613f176..09ec1c036 100755 --- a/examples/VideoEncoder/rgb_mono_encoding.py +++ b/examples/VideoEncoder/rgb_mono_encoding.py @@ -26,9 +26,9 @@ monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) # Create encoders, one for each camera, consuming the frames and encoding them using H.264 / H.265 encoding -ve1.setDefaultProfilePreset(1280, 720, 30, dai.VideoEncoderProperties.Profile.H264_MAIN) -ve2.setDefaultProfilePreset(1920, 1080, 30, dai.VideoEncoderProperties.Profile.H265_MAIN) -ve3.setDefaultProfilePreset(1280, 720, 30, dai.VideoEncoderProperties.Profile.H264_MAIN) +ve1.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H264_MAIN) +ve2.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H265_MAIN) +ve3.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H264_MAIN) # Linking monoLeft.out.link(ve1.input) diff --git a/examples/mixed/rgb_encoding_mobilenet.py b/examples/mixed/rgb_encoding_mobilenet.py index af9ce6f05..fbcb10192 100755 --- a/examples/mixed/rgb_encoding_mobilenet.py +++ b/examples/mixed/rgb_encoding_mobilenet.py @@ -41,7 +41,7 @@ camRgb.setPreviewSize(300, 300) camRgb.setInterleaved(False) -videoEncoder.setDefaultProfilePreset(1920, 1080, 30, dai.VideoEncoderProperties.Profile.H265_MAIN) +videoEncoder.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H265_MAIN) nn.setConfidenceThreshold(0.5) nn.setBlobPath(nnPath) diff --git a/examples/mixed/rgb_encoding_mono_mobilenet.py b/examples/mixed/rgb_encoding_mono_mobilenet.py index a15cc980b..f4822ab0e 100755 --- a/examples/mixed/rgb_encoding_mono_mobilenet.py +++ b/examples/mixed/rgb_encoding_mono_mobilenet.py @@ -44,7 +44,7 @@ camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) monoRight.setBoardSocket(dai.CameraBoardSocket.RIGHT) monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_720_P) -videoEncoder.setDefaultProfilePreset(1920, 1080, 30, dai.VideoEncoderProperties.Profile.H265_MAIN) +videoEncoder.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H265_MAIN) nn.setConfidenceThreshold(0.5) nn.setBlobPath(nnPath) diff --git a/examples/mixed/rgb_encoding_mono_mobilenet_depth.py b/examples/mixed/rgb_encoding_mono_mobilenet_depth.py index 9ec39c7eb..dd37128e9 100755 --- a/examples/mixed/rgb_encoding_mono_mobilenet_depth.py +++ b/examples/mixed/rgb_encoding_mono_mobilenet_depth.py @@ -50,7 +50,7 @@ monoRight.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) monoLeft.setBoardSocket(dai.CameraBoardSocket.LEFT) monoLeft.setResolution(dai.MonoCameraProperties.SensorResolution.THE_400_P) -videoEncoder.setDefaultProfilePreset(1920, 1080, 30, dai.VideoEncoderProperties.Profile.H265_MAIN) +videoEncoder.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H265_MAIN) depth.initialConfig.setConfidenceThreshold(255) depth.setRectifyEdgeFillColor(0) # Black, to better see the cutout diff --git a/src/pipeline/NodeBindings.cpp b/src/pipeline/NodeBindings.cpp index 0ae11feb3..7857804f7 100644 --- a/src/pipeline/NodeBindings.cpp +++ b/src/pipeline/NodeBindings.cpp @@ -319,8 +319,6 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ .def_readwrite("profile", &VideoEncoderProperties::profile) .def_readwrite("quality", &VideoEncoderProperties::quality) .def_readwrite("rateCtrlMode", &VideoEncoderProperties::rateCtrlMode) - .def_readwrite("width", &VideoEncoderProperties::width) - .def_readwrite("height", &VideoEncoderProperties::height) ; @@ -864,13 +862,39 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ videoEncoder .def_readonly("input", &VideoEncoder::input, DOC(dai, node, VideoEncoder, input), DOC(dai, node, VideoEncoder, input)) .def_readonly("bitstream", &VideoEncoder::bitstream, DOC(dai, node, VideoEncoder, bitstream), DOC(dai, node, VideoEncoder, bitstream)) - .def("setDefaultProfilePreset", static_cast(&VideoEncoder::setDefaultProfilePreset), py::arg("width"), py::arg("height"), py::arg("fps"), py::arg("profile"), DOC(dai, node, VideoEncoder, setDefaultProfilePreset)) - .def("setDefaultProfilePreset", static_cast, float, VideoEncoderProperties::Profile)>(&VideoEncoder::setDefaultProfilePreset), py::arg("size"), py::arg("fps"), py::arg("profile"), DOC(dai, node, VideoEncoder, setDefaultProfilePreset, 2)) + .def("setDefaultProfilePreset", static_cast(&VideoEncoder::setDefaultProfilePreset), py::arg("fps"), py::arg("profile"), DOC(dai, node, VideoEncoder, setDefaultProfilePreset)) + .def("setDefaultProfilePreset", [](VideoEncoder& v, int width, int height, float fps, VideoEncoderProperties::Profile profile){ + PyErr_WarnEx(PyExc_DeprecationWarning, "Input width/height no longer needed, automatically determined from first frame", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + v.setDefaultProfilePreset(width, height, fps, profile); + HEDLEY_DIAGNOSTIC_POP + }, DOC(dai, node, VideoEncoder, setDefaultProfilePreset, 2)) + .def("setDefaultProfilePreset", [](VideoEncoder& v, std::tuple size, float fps, VideoEncoderProperties::Profile profile){ + PyErr_WarnEx(PyExc_DeprecationWarning, "Input size no longer needed, automatically determined from first frame", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + v.setDefaultProfilePreset(size, fps, profile); + HEDLEY_DIAGNOSTIC_POP + }, DOC(dai, node, VideoEncoder, setDefaultProfilePreset, 3)) .def("setNumFramesPool", &VideoEncoder::setNumFramesPool, py::arg("frames"), DOC(dai, node, VideoEncoder, setNumFramesPool)) .def("getNumFramesPool", &VideoEncoder::getNumFramesPool, DOC(dai, node, VideoEncoder, getNumFramesPool)) .def("setRateControlMode", &VideoEncoder::setRateControlMode, py::arg("mode"), DOC(dai, node, VideoEncoder, setRateControlMode)) - .def("setProfile", static_cast, VideoEncoder::Properties::Profile)>(&VideoEncoder::setProfile), py::arg("size"), py::arg("profile"), DOC(dai, node, VideoEncoder, setProfile)) - .def("setProfile", static_cast(&VideoEncoder::setProfile), py::arg("width"), py::arg("height"), py::arg("profile"), DOC(dai, node, VideoEncoder, setProfile, 2)) + .def("setProfile", static_cast(&VideoEncoder::setProfile), py::arg("profile"), DOC(dai, node, VideoEncoder, setProfile)) + .def("setProfile", [](VideoEncoder& v, std::tuple size, VideoEncoderProperties::Profile profile){ + PyErr_WarnEx(PyExc_DeprecationWarning, "Input width/height no longer needed, automatically determined from first frame", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + v.setProfile(size, profile); + HEDLEY_DIAGNOSTIC_POP + }, DOC(dai, node, VideoEncoder, setProfile, 2)) + .def("setProfile", [](VideoEncoder& v, int width, int height, VideoEncoderProperties::Profile profile){ + PyErr_WarnEx(PyExc_DeprecationWarning, "Input width/height no longer needed, automatically determined from first frame", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + v.setProfile(width, height, profile); + HEDLEY_DIAGNOSTIC_POP + }, DOC(dai, node, VideoEncoder, setProfile, 3)) .def("setBitrate", &VideoEncoder::setBitrate, py::arg("bitrate"), DOC(dai, node, VideoEncoder, setBitrate)) .def("setBitrateKbps", &VideoEncoder::setBitrateKbps, py::arg("bitrateKbps"), DOC(dai, node, VideoEncoder, setBitrateKbps)) .def("setKeyframeFrequency", &VideoEncoder::setKeyframeFrequency, py::arg("freq"), DOC(dai, node, VideoEncoder, setKeyframeFrequency)) @@ -887,10 +911,28 @@ void NodeBindings::bind(pybind11::module& m, void* pCallstack){ //.def("getMaxBitrate", &VideoEncoder::getMaxBitrate) .def("getNumBFrames", &VideoEncoder::getNumBFrames, DOC(dai, node, VideoEncoder, getNumBFrames)) .def("getQuality", &VideoEncoder::getQuality, DOC(dai, node, VideoEncoder, getQuality)) - .def("getWidth", &VideoEncoder::getWidth, DOC(dai, node, VideoEncoder, getWidth)) - .def("getHeight", &VideoEncoder::getHeight, DOC(dai, node, VideoEncoder, getHeight)) + .def("getWidth", [](VideoEncoder& v){ + PyErr_WarnEx(PyExc_DeprecationWarning, "Input size no longer available, it's determined when first frame arrives", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + return v.getWidth(); + HEDLEY_DIAGNOSTIC_POP + }, DOC(dai, node, VideoEncoder, getWidth)) + .def("getHeight", [](VideoEncoder& v){ + PyErr_WarnEx(PyExc_DeprecationWarning, "Input size no longer available, it's determined when first frame arrives", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + return v.getHeight(); + HEDLEY_DIAGNOSTIC_POP + }, DOC(dai, node, VideoEncoder, getHeight)) + .def("getSize", [](VideoEncoder& v){ + PyErr_WarnEx(PyExc_DeprecationWarning, "Input size no longer available, it's determined when first frame arrives", 1); + HEDLEY_DIAGNOSTIC_PUSH + HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED + return v.getSize(); + HEDLEY_DIAGNOSTIC_POP + }, DOC(dai, node, VideoEncoder, getSize)) .def("getFrameRate", &VideoEncoder::getFrameRate, DOC(dai, node, VideoEncoder, getFrameRate)) - .def("getSize", &VideoEncoder::getSize, DOC(dai, node, VideoEncoder, getSize)) .def("getLossless", &VideoEncoder::getLossless, DOC(dai, node, VideoEncoder, getLossless)) ; // ALIAS From 2f2c24ff065e6628c3820a2fb08c072435822377 Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 22 Nov 2021 19:35:41 +0200 Subject: [PATCH 4/4] Update core: oak-d-lite merged --- depthai-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depthai-core b/depthai-core index 421a1d3a6..0ef693d2b 160000 --- a/depthai-core +++ b/depthai-core @@ -1 +1 @@ -Subproject commit 421a1d3a6899ed1cad9c35e17e34e2dcb07103ff +Subproject commit 0ef693d2b7636d092598490d7104fd252ba25aee