Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ void bind_encodedframe(pybind11::module& m, void* pCallstack) {
.def("setLossless", &EncodedFrame::setLossless, DOC(dai, EncodedFrame, getLossless))
.def("setProfile", &EncodedFrame::setProfile, DOC(dai, EncodedFrame, getProfile))
.def("setTransformation", [](EncodedFrame& msg, const ImgTransformation& transformation) { msg.transformation = transformation; });
#ifdef DEPTHAI_ENABLE_PROTOBUF
encodedFrame.def("save", &EncodedFrame::save, py::arg("path"), py::arg("metadataOnly") = false)
.def("load", &EncodedFrame::load, py::arg("path"), py::arg("metadataOnly") = false);
#endif
// // add aliases dai.ImgFrame.Type and dai.ImgFrame.Specs
// m.attr("EncodedFrame").attr("FrameType") =
// m.attr("RawEncodedFrame").attr("FrameType");
Expand Down
4 changes: 4 additions & 0 deletions bindings/python/src/pipeline/datatype/IMUDataBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,8 @@ void bind_imudata(pybind11::module& m, void* pCallstack) {
[](IMUData& imuDta) -> std::vector<IMUPacket>& { return imuDta.packets; },
[](IMUData& imuDta, std::vector<IMUPacket>& val) { imuDta.packets = val; },
DOC(dai, IMUData, packets));
#ifdef DEPTHAI_ENABLE_PROTOBUF
imuData.def("save", &IMUData::save, py::arg("path"), py::arg("metadataOnly") = false)
.def("load", &IMUData::load, py::arg("path"), py::arg("metadataOnly") = false);
#endif
}
15 changes: 15 additions & 0 deletions bindings/python/src/pipeline/datatype/ImgFrameBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,21 @@ void bind_imgframe(pybind11::module& m, void* pCallstack) {
.def("setTransformation", &ImgFrame::setTransformation, py::arg("transformation"), DOC(dai, ImgFrame, setTransformation))
// .def("set", &ImgFrame::set, py::arg("type"), DOC(dai, ImgFrame, set))
;

#ifdef DEPTHAI_ENABLE_PROTOBUF
imgFrame
.def(
"save",
&ImgFrame::save,
py::arg("path"),
py::arg("metadataOnly") = false)
.def(
"load",
&ImgFrame::load,
py::arg("path"),
py::arg("metadataOnly") = false);
#endif

// add aliases dai.ImgFrame.Type and dai.ImgFrame.Specs
// m.attr("ImgFrame").attr("Type") = m.attr("RawImgFrame").attr("Type");
// m.attr("ImgFrame").attr("Specs") = m.attr("RawImgFrame").attr("Specs");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,8 @@ void bind_pointclouddata(pybind11::module& m, void* pCallstack) {
.def("updateBoundingBox", &PointCloudData::updateBoundingBox, DOC(dai, PointCloudData, updateBoundingBox))
.def("getTransformation", &PointCloudData::getTransformation, py::return_value_policy::reference_internal)
.def("setTransformation", &PointCloudData::setTransformation, py::arg("transformation"));
#ifdef DEPTHAI_ENABLE_PROTOBUF
pointCloudData.def("save", &PointCloudData::save, py::arg("path"), py::arg("metadataOnly") = false)
.def("load", &PointCloudData::load, py::arg("path"), py::arg("metadataOnly") = false);
#endif
}
4 changes: 4 additions & 0 deletions bindings/python/src/pipeline/datatype/RGBDDataBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,8 @@ void bind_rgbddata(pybind11::module& m, void* pCallstack) {
.def("getDepthFrame", &RGBDData::getDepthFrame, DOC(dai, RGBDData, getDepthFrame))
.def("setRGBFrame", &RGBDData::setRGBFrame, py::arg("frame"), DOC(dai, RGBDData, setRGBFrame))
.def("setDepthFrame", &RGBDData::setDepthFrame, py::arg("frame"), DOC(dai, RGBDData, setDepthFrame));
#ifdef DEPTHAI_ENABLE_PROTOBUF
rgbdData.def("save", &RGBDData::save, py::arg("path"), py::arg("metadataOnly") = false)
.def("load", &RGBDData::load, py::arg("path"), py::arg("metadataOnly") = false);
#endif
}
85 changes: 85 additions & 0 deletions bindings/python/tests/imgframe_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import numpy as np
import pytest
import numpy.typing as npt
from pathlib import Path

DEBUG = False

Expand Down Expand Up @@ -44,6 +45,38 @@ def assert_images_close(expected, recovered, tolerance, msg):
assert max_diff <= tolerance, f"{msg} max abs diff too high: {max_diff} > {tolerance}"


def make_test_frame():
image = generate_color_image()
frame = dai.ImgFrame()
frame.setCvFrame(image, dai.ImgFrame.Type.BGR888i)
frame.setTimestamp(frame.getTimestamp())
frame.setTimestampDevice(frame.getTimestampDevice())
frame.setSequenceNum(123)
frame.setInstanceNum(7)
frame.setCategory(11)

transformation = dai.ImgTransformation(160, 120, image.shape[1], image.shape[0])
transformation.addCrop(8, 6, image.shape[1] - 12, image.shape[0] - 10)
frame.setTransformation(transformation)

return frame, image


def assert_frame_metadata_equal(expected, actual):
assert actual.getSequenceNum() == expected.getSequenceNum()
assert actual.getInstanceNum() == expected.getInstanceNum()
assert actual.getCategory() == expected.getCategory()
assert actual.getWidth() == expected.getWidth()
assert actual.getHeight() == expected.getHeight()
assert actual.getType() == expected.getType()
assert actual.getTimestamp() == expected.getTimestamp()
assert actual.getTimestampDevice() == expected.getTimestampDevice()

expected_transform = np.array(expected.getTransformation().getTransformationMatrix())
actual_transform = np.array(actual.getTransformation().getTransformationMatrix())
np.testing.assert_allclose(actual_transform, expected_transform)



COLOR_TYPES = [
pytest.param(dai.ImgFrame.Type.BGR888p, 0.5, id="BGR888p"),
Expand Down Expand Up @@ -127,3 +160,55 @@ def test_setcvframe_raw32():
assert recovered.shape == image.shape
assert recovered.dtype == np.int32
assert_images_close(image, recovered, tolerance=0.0, msg="RAW32")


def test_imgframe_file_roundtrip(tmp_path: Path):
frame, image = make_test_frame()
path = tmp_path / "frame.pb"

frame.save(path)
recovered = dai.ImgFrame()
recovered.load(path)

assert_frame_metadata_equal(frame, recovered)
assert np.array_equal(np.asarray(recovered.getData()), np.asarray(frame.getData()))
assert_images_close(image, recovered.getCvFrame(), tolerance=0.5, msg="imgframe file roundtrip")


def test_imgframe_metadata_only_roundtrip(tmp_path: Path):
frame, _ = make_test_frame()
path = tmp_path / "frame-metadata.pb"

frame.save(path, metadataOnly=True)
recovered = dai.ImgFrame()
recovered.load(path, metadataOnly=True)

assert_frame_metadata_equal(frame, recovered)
assert recovered.getData().size == 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a container-agnostic emptiness check for payload.

The assertion currently depends on a .size attribute, which may not exist for all getData() Python return shapes. Use len(...) == 0 to avoid false test failures.

✅ Proposed fix
-    assert recovered.getData().size == 0
+    assert len(recovered.getData()) == 0
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert recovered.getData().size == 0
assert len(recovered.getData()) == 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bindings/python/tests/imgframe_test.py` at line 187, The assertion checking
for an empty payload uses the `.size` attribute which is not available on all
possible Python container types returned by getData(). Replace the emptiness
check on recovered.getData().size == 0 with len(recovered.getData()) == 0 to use
a container-agnostic approach that works with any standard Python container
type.



def test_encodedframe_file_roundtrip(tmp_path: Path):
frame = dai.EncodedFrame()
frame.setWidth(320)
frame.setHeight(180)
frame.setQuality(90)
frame.setBitrate(1_000_000)
frame.setProfile(dai.EncodedFrame.Profile.JPEG)
frame.setFrameType(dai.EncodedFrame.FrameType.I)
frame.setLossless(False)
frame.setData(np.arange(64, dtype=np.uint8))

path = tmp_path / "encoded-frame.pb"
frame.save(path)

recovered = dai.EncodedFrame()
recovered.load(path)

assert recovered.getWidth() == frame.getWidth()
assert recovered.getHeight() == frame.getHeight()
assert recovered.getQuality() == frame.getQuality()
assert recovered.getBitrate() == frame.getBitrate()
assert recovered.getProfile() == frame.getProfile()
assert recovered.getFrameType() == frame.getFrameType()
assert recovered.getLossless() == frame.getLossless()
assert np.array_equal(np.asarray(recovered.getData()), np.asarray(frame.getData()))
5 changes: 5 additions & 0 deletions include/depthai/pipeline/datatype/EncodedFrame.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ class EncodedFrame : public Buffer, public ProtoSerializable {
* @returns serialized schema
*/
ProtoSerializable::SchemaPair serializeSchema() const override;

protected:
void deserializeProtoMessage(const std::vector<std::uint8_t>& bytes, bool metadataOnly) override;

public:
#endif

DEPTHAI_SERIALIZE(EncodedFrame,
Expand Down
5 changes: 5 additions & 0 deletions include/depthai/pipeline/datatype/IMUData.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@ class IMUData : public Buffer, public ProtoSerializable {
* @returns serialized schema
*/
ProtoSerializable::SchemaPair serializeSchema() const override;

protected:
void deserializeProtoMessage(const std::vector<std::uint8_t>& bytes, bool metadataOnly) override;

public:
#endif

DEPTHAI_SERIALIZE(IMUData, Buffer::ts, Buffer::tsDevice, Buffer::tsSystem, Buffer::sequenceNum, packets);
Expand Down
7 changes: 5 additions & 2 deletions include/depthai/pipeline/datatype/ImgFrame.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,12 @@ class ImgFrame : public Buffer, public ProtoSerializable {
* @returns serialized schema
*/
ProtoSerializable::SchemaPair serializeSchema() const override;
#endif

protected:
void deserializeProtoMessage(const std::vector<std::uint8_t>& bytes, bool metadataOnly) override;

public:
#endif
// getters
/**
* Retrieves image timestamp (at the specified offset of exposure) related to dai::Clock::now()
Expand Down Expand Up @@ -768,7 +772,6 @@ class ImgFrame : public Buffer, public ProtoSerializable {
dai::FrameEvent event = dai::FrameEvent::NONE;
ImgTransformation transformation;

public:
DEPTHAI_SERIALIZE(ImgFrame, Buffer::ts, Buffer::tsDevice, Buffer::tsSystem, Buffer::sequenceNum, fb, sourceFb, cam, category, instanceNum, transformation);
};

Expand Down
5 changes: 5 additions & 0 deletions include/depthai/pipeline/datatype/PointCloudData.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,11 @@ class PointCloudData : public Buffer, public ProtoSerializable, public Transform
* @returns serialized schema
*/
ProtoSerializable::SchemaPair serializeSchema() const override;

protected:
void deserializeProtoMessage(const std::vector<std::uint8_t>& bytes, bool metadataOnly) override;

public:
#endif

#ifdef DEPTHAI_HAVE_PCL_SUPPORT
Expand Down
5 changes: 5 additions & 0 deletions include/depthai/pipeline/datatype/RGBDData.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ class RGBDData : public Buffer, public ProtoSerializable {
* @returns serialized schema
*/
ProtoSerializable::SchemaPair serializeSchema() const override;

protected:
void deserializeProtoMessage(const std::vector<std::uint8_t>& bytes, bool metadataOnly) override;

public:
#endif

DEPTHAI_SERIALIZE(RGBDData, colorFrame, depthFrame, Buffer::ts, Buffer::tsDevice, Buffer::tsSystem, Buffer::sequenceNum);
Expand Down
18 changes: 18 additions & 0 deletions include/depthai/utility/ProtoSerializable.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <cstdint>
#include <filesystem>
#include <string>
#include <vector>

Expand All @@ -16,6 +17,20 @@ class ProtoSerializable {
virtual ~ProtoSerializable();

#ifdef DEPTHAI_ENABLE_PROTOBUF
/**
* @brief Serialize this object and write it to disk
* @param path Output path. If it has no extension, the final on-disk filename is resolved to `<path>.dai`.
* @param metadataOnly If true, serialize only metadata and omit payload data where supported by the concrete type.
*/
void save(const std::filesystem::path& path, bool metadataOnly = false) const;

/**
* @brief Load this object from a serialized protobuf file on disk
* @param path Input path. If it has no extension, the file is resolved as `<path>.dai`.
* @param metadataOnly If true, load only metadata and omit payload data where supported by the concrete type.
*/
void load(const std::filesystem::path& path, bool metadataOnly = false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the actual deserialization/message-loading helper referenced by deserializeProtoMessage overrides
fd -i 'ProtoFileIO' 
rg -n -A15 'loadProtoMessageFromBytes' --type=cpp

Repository: luxonis/depthai-core

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant header and locate callers/overrides.
git ls-files 'include/depthai/utility/ProtoSerializable.hpp' 'src/**' 'include/**' | sed -n '1,200p'
echo
ast-grep outline include/depthai/utility/ProtoSerializable.hpp --view expanded || true
echo
rg -n --hidden --glob '!**/.git/**' 'deserializeProtoMessage|metadataOnly|load\(const std::filesystem::path& path, bool metadataOnly = false\)' include src

Repository: luxonis/depthai-core

Length of output: 21097


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' src/utility/ProtoSerializable.cpp
echo
sed -n '360,420p' src/pipeline/datatype/ImgFrame.cpp
echo
sed -n '150,190p' src/pipeline/datatype/EncodedFrame.cpp
echo
sed -n '240,270p' src/pipeline/datatype/PointCloudData.cpp
echo
sed -n '1,90p' src/pipeline/datatype/RGBDData.cpp
echo
sed -n '1,60p' src/pipeline/datatype/IMUData.cpp

Repository: luxonis/depthai-core

Length of output: 8552


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1050,1365p' src/utility/ProtoSerialize.cpp

Repository: luxonis/depthai-core

Length of output: 13503


Clear payload state on metadata-only reloads

load(..., metadataOnly=true) updates metadata in place but skips resetting payload members. Reusing an existing ImgFrame, EncodedFrame, PointCloudData, or RGBDData can leave old buffers/frames attached to the newly loaded metadata. Clear or replace the payload state before applying a metadata-only load.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/depthai/utility/ProtoSerializable.hpp` at line 21, The metadata-only
path in ProtoSerializable::load leaves existing payload state attached when
reusing objects like ImgFrame, EncodedFrame, PointCloudData, or RGBDData. Update
the load(const std::filesystem::path&, bool metadataOnly) flow so that when
metadataOnly is true it first clears or replaces the current payload members
before applying the new metadata, ensuring stale buffers/frames are not
preserved across reloads.


Comment thread
coderabbitai[bot] marked this conversation as resolved.
/**
* @brief Serialize the protobuf message of this object
* @return serialized protobuf message
Expand All @@ -28,6 +43,9 @@ class ProtoSerializable {
*/
virtual SchemaPair serializeSchema() const = 0;

protected:
virtual void deserializeProtoMessage(const std::vector<std::uint8_t>& bytes, bool metadataOnly);

#else
// Helper struct for compile-time check
template <typename... T>
Expand Down
1 change: 1 addition & 0 deletions src/opencv/ImgFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// #include "spdlog/spdlog.h"

namespace dai {

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
ImgFrame& ImgFrame::setFrame(cv::Mat frame) {
Expand Down
5 changes: 5 additions & 0 deletions src/pipeline/datatype/EncodedFrame.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "depthai/pipeline/datatype/EncodedFrame.hpp"
#ifdef DEPTHAI_ENABLE_PROTOBUF
#include "depthai/schemas/EncodedFrame.pb.h"
#include "utility/ProtoFileIO.hpp"
#include "utility/ProtoSerialize.hpp"
#endif

Expand Down Expand Up @@ -161,6 +162,10 @@ ImgFrame EncodedFrame::getImgFrameMeta() const {
}

#ifdef DEPTHAI_ENABLE_PROTOBUF
void EncodedFrame::deserializeProtoMessage(const std::vector<std::uint8_t>& bytes, bool metadataOnly) {
utility::loadProtoMessageFromBytes(*this, bytes, metadataOnly);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could deserializeProtoMessage be skipped and loadProtoMessageFromBytes be called directly where needed? I'm not sure since the implementation is missing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deserializeProtoMessage is the virtual hook that ProtoSerializable::load() uses for type-specific deserialization, so the EncodedFrame override is part of the current design, not redundant. Removing it would require redesigning the base ProtoSerializable::load() flow, not a minimal cleanup.

}

ProtoSerializable::SchemaPair EncodedFrame::serializeSchema() const {
return utility::serializeSchema(utility::getProtoMessage(this));
}
Expand Down
4 changes: 4 additions & 0 deletions src/pipeline/datatype/IMUData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#if DEPTHAI_ENABLE_PROTOBUF
#include "depthai/schemas/IMUData.pb.h"
#include "depthai/schemas/common.pb.h"
#include "utility/ProtoFileIO.hpp"
#include "utility/ProtoSerialize.hpp"
#endif

Expand All @@ -16,6 +17,9 @@ void IMUData::serialize(std::vector<std::uint8_t>& metadata, DatatypeEnum& datat
}

#ifdef DEPTHAI_ENABLE_PROTOBUF
void IMUData::deserializeProtoMessage(const std::vector<std::uint8_t>& bytes, bool metadataOnly) {
utility::loadProtoMessageFromBytes(*this, bytes, metadataOnly);
}

ProtoSerializable::SchemaPair IMUData::serializeSchema() const {
return utility::serializeSchema(utility::getProtoMessage(this));
Expand Down
5 changes: 5 additions & 0 deletions src/pipeline/datatype/ImgFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "depthai/utility/SharedMemory.hpp"
#ifdef DEPTHAI_ENABLE_PROTOBUF
#include "depthai/schemas/ImgFrame.pb.h"
#include "utility/ProtoFileIO.hpp"
#include "utility/ProtoSerialize.hpp"
#endif
namespace dai {
Expand Down Expand Up @@ -361,6 +362,10 @@ Rect ImgFrame::remapRectBetweenFrames(const Rect& originRect, const ImgFrame& or
}

#ifdef DEPTHAI_ENABLE_PROTOBUF
void ImgFrame::deserializeProtoMessage(const std::vector<std::uint8_t>& bytes, bool metadataOnly) {
utility::loadProtoMessageFromBytes(*this, bytes, metadataOnly);
}

ProtoSerializable::SchemaPair ImgFrame::serializeSchema() const {
return utility::serializeSchema(utility::getProtoMessage(this));
}
Expand Down
5 changes: 5 additions & 0 deletions src/pipeline/datatype/PointCloudData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "depthai/common/Point3f.hpp"
#ifdef DEPTHAI_ENABLE_PROTOBUF
#include "depthai/schemas/PointCloudData.pb.h"
#include "utility/ProtoFileIO.hpp"
#include "utility/ProtoSerialize.hpp"
#endif
namespace dai {
Expand Down Expand Up @@ -244,6 +245,10 @@ PointCloudData& PointCloudData::setColor(bool val) {
}

#ifdef DEPTHAI_ENABLE_PROTOBUF
void PointCloudData::deserializeProtoMessage(const std::vector<std::uint8_t>& bytes, bool metadataOnly) {
utility::loadProtoMessageFromBytes(*this, bytes, metadataOnly);
}

std::vector<std::uint8_t> PointCloudData::serializeProto(bool metadataOnly) const {
return utility::serializeProto(utility::getProtoMessage(this, metadataOnly));
}
Expand Down
5 changes: 5 additions & 0 deletions src/pipeline/datatype/RGBDData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#ifdef DEPTHAI_ENABLE_PROTOBUF
#include "depthai/schemas/RGBDData.pb.h"
#include "utility/ProtoFileIO.hpp"
#include "utility/ProtoSerialize.hpp"
#endif

Expand Down Expand Up @@ -62,6 +63,10 @@ std::optional<RGBDData::FrameVariant> RGBDData::getDepthFrame() const {
}

#ifdef DEPTHAI_ENABLE_PROTOBUF
void RGBDData::deserializeProtoMessage(const std::vector<std::uint8_t>& bytes, bool metadataOnly) {
utility::loadProtoMessageFromBytes(*this, bytes, metadataOnly);
}

std::vector<std::uint8_t> RGBDData::serializeProto(bool metadataOnly) const {
return utility::serializeProto(utility::getProtoMessage(this, metadataOnly));
}
Expand Down
Loading