From 8c05663d734f346de5e5856255406850541de931 Mon Sep 17 00:00:00 2001 From: aljazdu Date: Thu, 25 Sep 2025 15:26:30 +0200 Subject: [PATCH 01/33] Added WIP version of Snaps&Events V2 update --- .../src/utility/EventsManagerBindings.cpp | 13 +- examples/cpp/Events/events.cpp | 34 +- examples/python/Events/events.py | 37 +- include/depthai/utility/EventsManager.hpp | 115 ++-- protos/Event.proto | 136 +++-- src/utility/EventsManager.cpp | 497 ++++++++++++------ 6 files changed, 540 insertions(+), 292 deletions(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 19e3dfc209..7c5e7990cd 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -21,7 +21,7 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { #ifdef DEPTHAI_ENABLE_EVENTS_MANAGER using namespace dai::utility; - py::class_>(m, "EventData") + py::class_>(m, "FileData") .def(py::init(), py::arg("data"), py::arg("fileName"), py::arg("mimeType")) .def(py::init(), py::arg("fileUrl")) .def(py::init&, std::string>(), py::arg("imgFrame"), py::arg("fileName")) @@ -50,25 +50,22 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { &EventsManager::setCacheIfCannotSend, py::arg("cacheIfCannotUpload"), DOC(dai, utility, EventsManager, setCacheIfCannotSend)) - .def("checkConnection", &EventsManager::checkConnection, DOC(dai, utility, EventsManager, checkConnection)) .def("uploadCachedData", &EventsManager::uploadCachedData, DOC(dai, utility, EventsManager, uploadCachedData)) .def("sendEvent", &EventsManager::sendEvent, py::arg("name"), - py::arg("imgFrame").none(true) = nullptr, - py::arg("data") = std::vector>(), py::arg("tags") = std::vector(), - py::arg("extraData") = std::unordered_map(), + py::arg("extras") = std::unordered_map(), py::arg("deviceSerialNo") = "", + py::arg("associateFiles") = std::vector(), DOC(dai, utility, EventsManager, sendEvent)) .def("sendSnap", &EventsManager::sendSnap, py::arg("name"), - py::arg("imgFrame").none(true) = nullptr, - py::arg("data") = std::vector>(), py::arg("tags") = std::vector(), - py::arg("extraData") = std::unordered_map(), + py::arg("extras") = std::unordered_map(), py::arg("deviceSerialNo") = "", + py::arg("fileGroup") = std::vector>(), DOC(dai, utility, EventsManager, sendSnap)); #endif } diff --git a/examples/cpp/Events/events.cpp b/examples/cpp/Events/events.cpp index dba3019a50..c962f286fd 100644 --- a/examples/cpp/Events/events.cpp +++ b/examples/cpp/Events/events.cpp @@ -9,7 +9,11 @@ int main(int argc, char* argv[]) { dai::Pipeline pipeline(true); auto eventsManager = std::make_shared(); - eventsManager->setLogResponse(true); + + // Enter your hubs api-key + eventsManager->setUrl("https://events.cloud-stg.luxonis.com"); + eventsManager->setToken(""); + // Color camera node auto camRgb = pipeline.create()->build(); auto* preview = camRgb->requestOutput(std::make_pair(256, 256)); @@ -17,27 +21,27 @@ int main(int argc, char* argv[]) { auto previewQ = preview->createOutputQueue(); pipeline.start(); - bool sent = false; - eventsManager->sendEvent("test", nullptr, {}, {"tag1", "tag2"}, {{"key1", "value1"}}); - std::this_thread::sleep_for(std::chrono::milliseconds(7000)); + std::vector> data; - auto fileData = std::make_shared("abc", "test_bin.txt", "text/plain"); - std::vector> data; - data.emplace_back(fileData); - eventsManager->sendEvent("testdata", nullptr, data, {"tag3", "tag4"}, {{"key8", "value8"}}); while(pipeline.isRunning()) { auto rgb = previewQ->get(); - // Do something with the data - // ... + std::string str = "image_"; + std::stringstream ss; + ss << str << data.size(); - if(!sent) { - eventsManager->sendSnap("rgb", rgb, {}, {"tag11", "tag12"}, {{"key", "value"}}); - sent = true; + auto rgbData = std::make_shared(rgb, ss.str()); + data.emplace_back(rgbData); + + if (data.size() == 5) + { + eventsManager->sendSnap("ImgFrame", {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, "", data); + data.clear(); + std::this_thread::sleep_for(std::chrono::milliseconds(3000)); } - // - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + std::this_thread::sleep_for(std::chrono::milliseconds(400)); } return EXIT_SUCCESS; diff --git a/examples/python/Events/events.py b/examples/python/Events/events.py index fdedb29ca1..eb6cf16ab8 100644 --- a/examples/python/Events/events.py +++ b/examples/python/Events/events.py @@ -10,35 +10,28 @@ with dai.Pipeline() as pipeline: # Define sources and outputs camRgb = pipeline.create(dai.node.Camera).build() - # Properties - qRgb = camRgb.requestOutput((256,256)).createOutputQueue() + # Enter your hubs api-key eventMan = dai.EventsManager() - eventMan.setLogResponse(True) + eventMan.setUrl("https://events.cloud-stg.luxonis.com") + eventMan.setToken("") - eventMan.sendEvent("test1", None, [], ["tag1", "tag2"], {"key1": "value1"}) - time.sleep(2) - fileData = dai.EventData(b'Hello, world!', "hello.txt", "text/plain") - eventMan.sendEvent("test2", None, [fileData], ["tag1", "tag2"], {"key1": "value1"}) pipeline.start() - frame = None - counter = 0 - + data = [] - eventSent = False while pipeline.isRunning(): inRgb: dai.ImgFrame = qRgb.get() - if inRgb is not None: - frame = inRgb.getCvFrame() - if not eventSent: - eventMan.sendSnap("rgb", inRgb, [], ["tag1", "tag2"], {"key1": "value1"}) - eventSent = True - if frame is not None: - cv2.imshow("rgb", frame) - - if cv2.waitKey(1) == ord("q"): - pipeline.stop() - break + name = f"image_{len(data)}" + if inRgb is not None: + rgbData = dai.FileData(inRgb, name) + data.append(rgbData) + + if len(data) == 5: + eventMan.sendSnap("ImgFrame", ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1"}, "", data) + data.clear() + time.sleep(3) + + time.sleep(0.4) \ No newline at end of file diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 17b0d4410d..311a856c8d 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -18,67 +18,78 @@ namespace dai { namespace proto { namespace event { class Event; +enum PrepareFileUploadClass : int; } // namespace event } // namespace proto + namespace utility { -enum class EventDataType { DATA, FILE_URL, IMG_FRAME, ENCODED_FRAME, NN_DATA }; -class EventData { + +class FileData { public: - EventData(const std::string& data, const std::string& fileName, const std::string& mimeType); - explicit EventData(std::string fileUrl); - explicit EventData(const std::shared_ptr& imgFrame, std::string fileName); - explicit EventData(const std::shared_ptr& encodedFrame, std::string fileName); - explicit EventData(const std::shared_ptr& nnData, std::string fileName); + FileData(const std::string& data, const std::string& fileName, const std::string& mimeType); + explicit FileData(std::string fileUrl); + explicit FileData(const std::shared_ptr& imgFrame, std::string fileName); + explicit FileData(const std::shared_ptr& encodedFrame, std::string fileName); + explicit FileData(const std::shared_ptr& nnData, std::string fileName); bool toFile(const std::string& path); private: - std::string fileName; + /** + * Calculate SHA256 checksum for the given data + */ + std::string CalculateSHA256Checksum(const std::string& data); + + std::string checksum; std::string mimeType; + uint64_t size; + std::string fileName; + proto::event::PrepareFileUploadClass classification; std::string data; - EventDataType type; friend class EventsManager; }; + class EventsManager { public: - explicit EventsManager(std::string url = "https://events-ingest.cloud.luxonis.com", bool uploadCachedOnStart = false, float publishInterval = 10.0); + explicit EventsManager(std::string url = "https://events.cloud.luxonis.com", bool uploadCachedOnStart = false, float publishInterval = 10.0); ~EventsManager(); + /** + * Fetch configuration limits and quotas for snaps, through the api + * @return bool + */ + bool fetchConfigurationLimits(); /** * Send an event to the events service * @param name Name of the event - * @param imgFrame Image frame to send - * @param data List of EventData objects to send * @param tags List of tags to send - * @param extraData Extra data to send + * @param extras Extra data to send * @param deviceSerialNo Device serial number + * @param associateFiles List of associate files with ids * @return bool */ bool sendEvent(const std::string& name, - const std::shared_ptr& imgFrame = nullptr, - std::vector> data = {}, const std::vector& tags = {}, - const std::unordered_map& extraData = {}, - const std::string& deviceSerialNo = ""); + const std::unordered_map& extras = {}, + const std::string& deviceSerialNo = "", + const std::vector& associateFiles = {}); /** - * Send a snap to the events service. Snaps should be used for sending images and other large files. + * Send a snap to the events service. Snaps should be used for sending images and other files. * @param name Name of the snap - * @param imgFrame Image frame to send - * @param data List of EventData objects to send * @param tags List of tags to send - * @param extraData Extra data to send + * @param extras Extra data to send * @param deviceSerialNo Device serial number + * @param fileGroup List of FileData objects to send * @return bool */ bool sendSnap(const std::string& name, - const std::shared_ptr& imgFrame = nullptr, - std::vector> data = {}, const std::vector& tags = {}, - const std::unordered_map& extraData = {}, - const std::string& deviceSerialNo = ""); + const std::unordered_map& extras = {}, + const std::string& deviceSerialNo = "", + const std::vector>& fileGroup = {}); void setDeviceSerialNumber(const std::string& deviceSerialNumber); /** - * Set the URL of the events service. By default, the URL is set to https://events-ingest.cloud.luxonis.com + * Set the URL of the events service. By default, the URL is set to https://events.cloud.luxonis.com * @param url URL of the events service * @return void */ @@ -113,6 +124,12 @@ class EventsManager { * @return void */ void setLogResponse(bool logResponse); + /** + * Set whether to log the results of uploads to the server. By default, logUploadResults is set to false + * @param logUploadResults bool + * @return void + */ + void setLogUploadResults(bool logUploadResults); /** * Set whether to verify the SSL certificate. By default, verifySsl is set to false * @param verifySsl bool @@ -120,12 +137,6 @@ class EventsManager { */ void setVerifySsl(bool verifySsl); - /** - * Check if the device is connected to Hub. Performs a simple GET request to the URL/health endpoint - * @return bool - */ - bool checkConnection(); - /** * Upload cached data to the events service * @return void @@ -147,33 +158,53 @@ class EventsManager { void setCacheIfCannotSend(bool cacheIfCannotSend); private: - struct EventMessage { + struct SnapData { std::shared_ptr event; - std::vector> data; + std::vector> fileGroup; std::string cachePath; }; - static std::string createUUID(); - void sendEventBuffer(); - void sendFile(const std::shared_ptr& file, const std::string& url); + + /** + * Prepare and upload files from snapBuffer in batch + */ + void uploadFileBatch(); + /** + * Upload events from eventBuffer in batch + */ + void uploadEventBatch(); + /** + * Upload a file using the chosen url + */ + void uploadFile(const std::shared_ptr& file, const std::string& url); + /** + * // TO DO: Add description + */ void cacheEvents(); + /** + * // TO DO: Add description + */ bool checkForCachedData(); + std::string token; std::string deviceSerialNumber; std::string url; std::string sourceAppId; std::string sourceAppIdentifier; uint64_t queueSize; - std::unique_ptr eventBufferThread; - std::vector> eventBuffer; - std::mutex eventBufferMutex; float publishInterval; bool logResponse; + bool logUploadResults; bool verifySsl; std::string cacheDir; bool cacheIfCannotSend; - std::atomic stopEventBuffer; + std::unique_ptr uploadThread; + std::vector> eventBuffer; + std::vector> snapBuffer; + std::mutex eventBufferMutex; + std::mutex snapBufferMutex; + std::mutex stopThreadConditionMutex; + std::atomic stopUploadThread; std::condition_variable eventBufferCondition; - std::mutex eventBufferConditionMutex; }; } // namespace utility } // namespace dai diff --git a/protos/Event.proto b/protos/Event.proto index e3c99b995a..468484c762 100644 --- a/protos/Event.proto +++ b/protos/Event.proto @@ -2,57 +2,131 @@ syntax = "proto3"; package dai.proto.event; -message BatchUploadEvents { - repeated Event events = 1; +message BatchPrepareFileUpload { + repeated PrepareFileUploadGroup groups = 1; } -message Event { - // prevents dual uploads, can be a local ID for example - optional string nonce = 1; +message PrepareFileUploadGroup { + repeated PrepareFileUpload files = 1; +} + +message PrepareFileUpload { + string checksum = 1; + string mime_type = 2; + int64 size = 3; + string filename = 4; + PrepareFileUploadClass classification = 5; +} + +enum PrepareFileUploadClass { + UNKNOWN_FILE = 0; + IMAGE_COLOR = 1; + IMAGE_STEREO_LEFT = 2; + IMAGE_STEREO_RIGHT = 3; + DISPARITY = 4; + VIDEO = 5; + POINTCLOUD = 6; + ANNOTATION = 7; +} + +message BatchFileUploadResult { + repeated FileUploadGroupResult groups = 1; +} + +message FileUploadGroupResult { + optional RejectedFileGroup rejected = 1; + repeated FileUploadResult files = 2; +} + +message RejectedFileGroup { + RejectedFileGroupReason reason = 1; +} - // timestamp (seconds since 1970) when event originated (useful especially with offline/later upload) - int64 created_at = 2; +enum RejectedFileGroupReason { + GROUP_UNEXPECTED_ERROR = 0; + FILE_INPUT_VALIDATION = 1; + STORAGE_QUOTA_EXCEEDED = 2; +} - // name to identify event - string name = 3; +message FileUploadResult { + oneof result { + AcceptedFile accepted = 1; + RejectedFile rejected = 2; + } +} - // arbitrary tags, include tag "snap" for event to be processed snap - repeated string tags = 4; +message AcceptedFile { + string upload_url = 1; + string id = 2; +} - // arbitrary key/value data - map extras = 5; +message RejectedFile { + RejectedFileReason reason = 1; + string message = 2; +} - // how many files to wait to upload, before event is considered complete, - // notified about, snap created, ... - int32 expect_files_num = 6; +enum RejectedFileReason { + FILE_UNEXPECTED_ERROR = 0; + INPUT_VALIDATION = 1; +} - // serial number of source device - optional string source_serial_number = 7; +message BatchUploadEvents { + repeated Event events = 1; +} - // ID of sending Hub application - // (to be provided by agent as ENV - `AGENT_APP_ID` - eg `01916edb-3ded-793a-b6ad-cd4395768425`) - optional string source_app_id = 8; +message Event { + int64 created_at = 1; + string name = 2; + repeated string tags = 3; + map extras = 4; + optional string source_serial_number = 5; + optional string source_app_id = 6; + optional string source_app_identifier = 7; + repeated AssociateFile associate_files = 8; +} - // Identifier of sending Hub application - // (to be provided by agent as ENV - `AGENT_APP_IDENTIFIER` - eg `com.luxonis.counter-app`) - optional string source_app_identifier = 9; +message AssociateFile { + string id = 1; } + message BatchUploadEventsResult { - repeated EventResult events = 1; + repeated EventResult events = 1; } message EventResult { - string nonce = 1; oneof result { - AcceptedEvent accepted = 2; - IngestError error = 3; + AcceptedEvent accepted = 1; + RejectedEvent rejected = 2; } } message AcceptedEvent { - repeated string file_upload_urls = 1; + string id = 1; +} + +message RejectedEvent { + RejectedEventReason reason = 1; + string message = 2; +} + +enum RejectedEventReason { + GENERIC_EVENT_REJECTED = 0; + EVENT_VALIDATION = 1; +} + +message FileLimits { + uint64 max_file_size_bytes = 1; + uint64 remaining_storage_bytes = 2; + uint64 bytes_per_hour_rate = 3; + uint32 uploads_per_hour_rate = 4; } -message IngestError { - string message = 1; +message EventLimits { + uint32 events_per_hour_rate = 1; + uint32 snaps_per_hour_rate = 2; } + +message ApiUsage { + FileLimits files = 1; + EventLimits events = 2; +} \ No newline at end of file diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index a42bd8062b..9a4e294143 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "Environment.hpp" #include "Logging.hpp" @@ -17,10 +18,19 @@ namespace dai { namespace utility { using std::move; -EventData::EventData(const std::string& data, const std::string& fileName, const std::string& mimeType) - : fileName(fileName), mimeType(mimeType), data(data), type(EventDataType::DATA) {} +// TO DO: +// 1. ADD CHECKS FOR STUFF AS VALIDATION RULES IN CLICKUP DOCS +// 2. Add the newly updated file limits, event limits, api usage) +// 3. FileData should be determined, streamlined and wrapped for the final user -EventData::EventData(std::string fileUrl) : data(std::move(fileUrl)), type(EventDataType::FILE_URL) { +FileData::FileData(const std::string& data, const std::string& fileName, const std::string& mimeType) + : mimeType(mimeType), fileName(fileName), data(data) { + size = data.size(); + classification = proto::event::PrepareFileUploadClass::UNKNOWN_FILE; + checksum = CalculateSHA256Checksum(data); +} + +FileData::FileData(std::string fileUrl) : data(std::move(fileUrl)) { fileName = std::filesystem::path(data).filename().string(); static std::map mimeTypes = {{".html", "text/html"}, {".htm", "text/html"}, @@ -41,19 +51,22 @@ EventData::EventData(std::string fileUrl) : data(std::move(fileUrl)), type(Event } } -EventData::EventData(const std::shared_ptr& imgFrame, std::string fileName) - : fileName(std::move(fileName)), mimeType("image/jpeg"), type(EventDataType::IMG_FRAME) { +FileData::FileData(const std::shared_ptr& imgFrame, std::string fileName) + : mimeType("image/jpeg"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::IMAGE_COLOR) { // Convert ImgFrame to bytes cv::Mat cvFrame = imgFrame->getCvFrame(); std::vector buf; cv::imencode(".jpg", cvFrame, buf); + std::stringstream ss; ss.write((const char*)buf.data(), buf.size()); data = ss.str(); + size = data.size(); + checksum = CalculateSHA256Checksum(data); } -EventData::EventData(const std::shared_ptr& encodedFrame, std::string fileName) - : fileName(std::move(fileName)), type(EventDataType::ENCODED_FRAME) { +FileData::FileData(const std::shared_ptr& encodedFrame, std::string fileName) + : fileName(std::move(fileName)) {//, type(EventDataType::ENCODED_FRAME) { // Convert EncodedFrame to bytes if(encodedFrame->getProfile() != EncodedFrame::Profile::JPEG) { logger::error("Only JPEG encoded frames are supported"); @@ -65,22 +78,22 @@ EventData::EventData(const std::shared_ptr& encodedFrame, std::str mimeType = "image/jpeg"; } -EventData::EventData(const std::shared_ptr& nnData, std::string fileName) - : fileName(std::move(fileName)), mimeType("application/octet-stream"), type(EventDataType::NN_DATA) { +FileData::FileData(const std::shared_ptr& nnData, std::string fileName) + : mimeType("application/octet-stream"), fileName(std::move(fileName)) {//, type(EventDataType::NN_DATA) { // Convert NNData to bytes std::stringstream ss; ss.write((const char*)nnData->data->getData().data(), nnData->data->getData().size()); data = ss.str(); } -bool EventData::toFile(const std::string& path) { +bool FileData::toFile(const std::string& path) { // check if filename is not empty if(fileName.empty()) { logger::error("Filename is empty"); return false; } std::filesystem::path p(path); - if(type == EventDataType::FILE_URL) { + if(true) {//type == EventDataType::FILE_URL) { // get the filename from the url std::filesystem::copy(data, p / fileName); } else { @@ -98,70 +111,244 @@ bool EventData::toFile(const std::string& path) { } return true; } + +std::string FileData::CalculateSHA256Checksum(const std::string& data) { + unsigned char digest[SHA256_DIGEST_LENGTH]; + SHA256(reinterpret_cast(data.data()), data.size(), digest); + + std::ostringstream oss; + for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { + oss << std::hex << std::setw(2) << std::setfill('0') + << static_cast(digest[i]); + } + return oss.str(); +} + + EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float publishInterval) : url(std::move(url)), queueSize(10), publishInterval(publishInterval), logResponse(false), + logUploadResults(false), verifySsl(true), cacheDir("/internal/private"), cacheIfCannotSend(false), - stopEventBuffer(false) { + stopUploadThread(false) { sourceAppId = utility::getEnvAs("OAKAGENT_APP_VERSION", ""); sourceAppIdentifier = utility::getEnvAs("OAKAGENT_APP_IDENTIFIER", ""); token = utility::getEnvAs("DEPTHAI_HUB_API_KEY", ""); - eventBufferThread = std::make_unique([this]() { - while(!stopEventBuffer) { - sendEventBuffer(); - std::unique_lock lock(eventBufferMutex); - eventBufferCondition.wait_for(lock, std::chrono::seconds(static_cast(this->publishInterval))); + std::cout << "LOGGER LEVEL" << logger::get_level() << "\n"; + dai::Logging::getInstance().logger.set_level(spdlog::level::info); + std::cout << "LOGGER LEVEL" << logger::get_level() << "\n"; + uploadThread = std::make_unique([this]() { + while(!stopUploadThread) { + uploadFileBatch(); + uploadEventBatch(); + std::unique_lock lock(stopThreadConditionMutex); + eventBufferCondition.wait_for(lock, + std::chrono::seconds(static_cast(this->publishInterval)), + [this]() { + return stopUploadThread.load(); + }); } }); - checkConnection(); if(uploadCachedOnStart) { uploadCachedData(); } } EventsManager::~EventsManager() { - stopEventBuffer = true; + stopUploadThread = true; { - std::unique_lock lock(eventBufferMutex); + std::unique_lock lock(stopThreadConditionMutex); eventBufferCondition.notify_one(); } - if(eventBufferThread->joinable()) { - eventBufferThread->join(); + if(uploadThread && uploadThread->joinable()) { + uploadThread->join(); + } +} + +bool EventsManager::fetchConfigurationLimits() { + auto apiUsage = std::make_unique(); + cpr::Url requestUrl = static_cast(this->url + "/v2/api-usage"); + cpr::Response response = cpr::Get( + cpr::Url{requestUrl}, + //cpr::Body{serializedBatch}, + //cpr::Header{{"Authorization", "Bearer " + token}}, + cpr::VerifySsl(verifySsl), + cpr::ProgressCallback( + [&](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, intptr_t userdata) -> bool { + (void)userdata; + (void)downloadTotal; + (void)downloadNow; + (void)uploadTotal; + (void)uploadNow; + if(stopUploadThread) { + return false; + } + return true; + })); + if(response.status_code != cpr::status::HTTP_OK) { + logger::error("Failed to fetch configuration limits: {} {}", response.text, response.status_code); + return false; + } else { + logger::info("Configuration limits fetched successfully"); + if(logResponse) { + logger::info("Response: {}", response.text); + } } + return true; } -void EventsManager::sendEventBuffer() { - auto batchEvent = std::make_unique(); +void EventsManager::uploadFileBatch() { + // Prepare files for upload + auto fileGroupBatchPrepare = std::make_unique(); { - std::lock_guard lock(eventBufferMutex); - if(eventBuffer.empty()) { + std::lock_guard lock(snapBufferMutex); + if (snapBuffer.empty()) { return; } if(token.empty()) { logger::warn("Missing token, please set DEPTHAI_HUB_API_KEY environment variable or use setToken method"); return; } - if(!checkConnection()) { - if(cacheIfCannotSend) { - cacheEvents(); + //if(!checkConnection()) { + // TO DO: Caching is ignored for now. Fix this later + //if(cacheIfCannotSend) { + // cacheEvents(); + //} + // return; + //} + // Fill the batch with the groups from snapBuffer and their corresponding files + for (auto& snapData : snapBuffer) { + auto fileGroup = std::make_unique(); + for (auto& file : snapData->fileGroup) { + auto addedFile = fileGroup->add_files(); + addedFile->set_checksum(file->checksum); + addedFile->set_mime_type(file->mimeType); + addedFile->set_size(file->size); + addedFile->set_filename(file->fileName); + addedFile->set_classification(file->classification); + } + fileGroupBatchPrepare->add_groups()->Swap(fileGroup.get()); + } + } + + std::string serializedBatch; + fileGroupBatchPrepare->SerializeToString(&serializedBatch); + cpr::Url requestUrl = static_cast(this->url + "/v2/files/prepare-batch"); + cpr::Response response = cpr::Post( + cpr::Url{requestUrl}, + cpr::Body{serializedBatch}, + cpr::Header{{"Authorization", "Bearer " + token}}, + cpr::VerifySsl(verifySsl), + cpr::ProgressCallback( + [&](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, intptr_t userdata) -> bool { + (void)userdata; + (void)downloadTotal; + (void)downloadNow; + (void)uploadTotal; + (void)uploadNow; + if(stopUploadThread) { + return false; + } + return true; + })); + if (response.status_code != cpr::status::HTTP_CREATED) { + logger::error("Failed to prepare a batch of file groups: {}; status code: {}", response.text, response.status_code); + } + else { + logger::info("Batch of file groups has been successfully prepared"); + if (logResponse) { + logger::info("Response: {}", response.text); + } + + // Upload accepted files + auto fileGroupBatchUpload = std::make_unique(); + fileGroupBatchUpload->ParseFromString(response.text); + for (int i = 0; i < fileGroupBatchUpload->groups_size(); i++) { + auto snapData = snapBuffer.at(i); + auto uploadGroupResult = fileGroupBatchUpload->groups(i); + // Rejected group + if (uploadGroupResult.has_rejected()) { + if (!logUploadResults) { + continue; + } + auto reason = uploadGroupResult.rejected().reason(); + const auto* desc = proto::event::RejectedFileGroupReason_descriptor(); + std::string reasonName = "Unknown RejectedFileGroupReason"; + if (const auto* value = desc->FindValueByNumber(static_cast(reason))) { + reasonName = value->name(); + } + logger::info("File group rejected because of: {}", reasonName); + + for (int j = 0; j < uploadGroupResult.files_size(); j++) { + auto uploadFileResult = uploadGroupResult.files(j); + if(uploadFileResult.result_case() == proto::event::FileUploadResult::kAccepted) { + logger::info("File with id: {} accepted", uploadFileResult.accepted().id()); + } + else if (uploadFileResult.result_case() == proto::event::FileUploadResult::kRejected) { + auto reason = uploadFileResult.rejected().reason(); + const auto* desc = proto::event::RejectedFileReason_descriptor(); + std::string reasonName = "Unknown RejectedFileReason"; + if (const auto* value = desc->FindValueByNumber(static_cast(reason))) { + reasonName = value->name(); + } + logger::info("File rejected because of: {}; message: {}", reasonName, uploadFileResult.rejected().message()); + } + } + } else { + for (int j = 0; j < uploadGroupResult.files_size(); j++) { + auto uploadFileResult = uploadGroupResult.files(j); + if(uploadFileResult.result_case() == proto::event::FileUploadResult::kAccepted) { + // TO DO: Make this parallel upload + auto addedFile = snapData->event->add_associate_files(); + addedFile->set_id(uploadFileResult.accepted().id()); + uploadFile(snapData->fileGroup.at(j), uploadFileResult.accepted().upload_url()); + } + } + if(eventBuffer.size() <= queueSize) { + std::lock_guard lock(eventBufferMutex); + eventBuffer.push_back(std::move(snapData->event)); + } else { + logger::warn("Event buffer is full, dropping event"); + } } + } + + snapBuffer.clear(); + } +} + +void EventsManager::uploadEventBatch() { + auto eventBatch = std::make_unique(); + { + std::lock_guard lock(eventBufferMutex); + if(eventBuffer.empty()) { return; } - for(auto& eventM : eventBuffer) { - auto& event = eventM->event; - batchEvent->add_events()->Swap(event.get()); + if(token.empty()) { + logger::warn("Missing token, please set DEPTHAI_HUB_API_KEY environment variable or use setToken method"); + return; + } + //if(!checkConnection()) { + // TO DO: Caching is ignored for now. Fix this later + //if(cacheIfCannotSend) { + // cacheEvents(); + //} + // return; + //} + for(auto& event : eventBuffer) { + eventBatch->add_events()->Swap(event.get()); } } - std::string serializedEvent; - batchEvent->SerializeToString(&serializedEvent); - cpr::Url reqUrl = static_cast(this->url + "/v1/events"); - cpr::Response r = cpr::Post( - cpr::Url{reqUrl}, - cpr::Body{serializedEvent}, + std::string serializedBatch; + eventBatch->SerializeToString(&serializedBatch); + cpr::Url requestUrl = static_cast(this->url + "/v2/events"); + cpr::Response response = cpr::Post( + cpr::Url{requestUrl}, + cpr::Body{serializedBatch}, cpr::Header{{"Authorization", "Bearer " + token}}, cpr::VerifySsl(verifySsl), cpr::ProgressCallback( @@ -171,75 +358,77 @@ void EventsManager::sendEventBuffer() { (void)downloadNow; (void)uploadTotal; (void)uploadNow; - if(stopEventBuffer) { + if(stopUploadThread) { return false; } return true; })); - if(r.status_code != cpr::status::HTTP_OK) { - logger::error("Failed to send event: {} {}", r.text, r.status_code); + if(response.status_code != cpr::status::HTTP_OK) { + logger::error("Failed to send event: {} {}", response.text, response.status_code); } else { logger::info("Event sent successfully"); if(logResponse) { - logger::info("Response: {}", r.text); + logger::info("Response: {}", response.text); } - // upload files - auto batchUploadEventResult = std::make_unique(); - batchUploadEventResult->ParseFromString(r.text); - for(int i = 0; i < batchUploadEventResult->events_size(); i++) { - auto eventResult = batchUploadEventResult->events(i); - if(eventResult.accepted().file_upload_urls_size() > 0) { - for(int j = 0; j < eventResult.accepted().file_upload_urls().size(); j++) { - cpr::Url fileUrl = static_cast(this->url + eventResult.accepted().file_upload_urls(j)); - - sendFile(eventBuffer[i]->data[j], fileUrl.str()); + + if (logUploadResults) { + auto eventBatchUploadResults = std::make_unique(); + eventBatchUploadResults->ParseFromString(response.text); + for(int i = 0; i < eventBatchUploadResults->events_size(); i++) { + auto eventUploadResult = eventBatchUploadResults->events(i); + if(eventUploadResult.result_case() == proto::event::EventResult::kAccepted) { + logger::info("Event with id: {} accepted", eventUploadResult.accepted().id()); + } + else if (eventUploadResult.result_case() == proto::event::EventResult::kRejected) { + auto reason = eventUploadResult.rejected().reason(); + const auto* desc = proto::event::RejectedEventReason_descriptor(); + std::string reasonName = "Unknown RejectedEventReason"; + if (const auto* value = desc->FindValueByNumber(static_cast(reason))) { + reasonName = value->name(); + } + logger::info("Event rejected because of: {}; message: {}", reasonName, eventUploadResult.rejected().message()); } } } - for(auto& eventM : eventBuffer) { - if(!eventM->cachePath.empty() && std::filesystem::exists(eventM->cachePath)) { - std::filesystem::remove_all(eventM->cachePath); - } - } + + // TO DO: Caching is ignored for now. Fix this later + //for(auto& eventM : eventBuffer) { + // if(!eventM->cachePath.empty() && std::filesystem::exists(eventM->cachePath)) { + // std::filesystem::remove_all(eventM->cachePath); + // } + //} + eventBuffer.clear(); } } bool EventsManager::sendEvent(const std::string& name, - const std::shared_ptr& imgFrame, - std::vector> data, const std::vector& tags, - const std::unordered_map& extraData, - const std::string& deviceSerialNo) { - // Create event + const std::unordered_map& extras, + const std::string& deviceSerialNo, + const std::vector& associateFiles) { + // Create an event auto event = std::make_unique(); - event->set_nonce(createUUID()); - event->set_name(name); event->set_created_at(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); + event->set_name(name); for(const auto& tag : tags) { event->add_tags(tag); } auto* extrasData = event->mutable_extras(); - for(const auto& entry : extraData) { + for(const auto& entry : extras) { extrasData->insert({entry.first, entry.second}); } - - if(imgFrame != nullptr) { - auto fileData = std::make_shared(imgFrame, "img.jpg"); - data.push_back(fileData); - } - event->set_expect_files_num(data.size()); - event->set_source_serial_number(deviceSerialNo.empty() ? deviceSerialNumber : deviceSerialNo); event->set_source_app_id(sourceAppId); event->set_source_app_identifier(sourceAppIdentifier); - // Add event to buffer + for (const auto& file : associateFiles) { + auto addedFile = event->add_associate_files(); + addedFile->set_id(file); + } + // Add event to eventBuffer if(eventBuffer.size() <= queueSize) { std::lock_guard lock(eventBufferMutex); - auto eventMessage = std::make_unique(); - eventMessage->data = std::move(data); - eventMessage->event = std::move(event); - eventBuffer.push_back(std::move(eventMessage)); + eventBuffer.push_back(std::move(event)); } else { logger::warn("Event buffer is full, dropping event"); return false; @@ -248,64 +437,50 @@ bool EventsManager::sendEvent(const std::string& name, } bool EventsManager::sendSnap(const std::string& name, - const std::shared_ptr& imgFrame, - std::vector> data, const std::vector& tags, - const std::unordered_map& extraData, - const std::string& deviceSerialNo) { - std::vector tagsTmp = tags; - tagsTmp.emplace_back("snap"); - // exactly one image needs to be sent, either from imgFrame or from data - bool send = false; - if(imgFrame != nullptr && !data.empty()) { - logger::error("For sending snap, provide either imgFrame or single image in data list, not both. Use sendEvent for multiple files"); - return false; - } else if(imgFrame == nullptr && data.empty()) { - logger::error("No image or data provided"); - return false; - } else if(imgFrame == nullptr && !data.empty()) { - if(data.size() > 1) { - logger::error("More than one file provided in data. For sendings snaps, only one image file is allowed. Use sendEvent for multiple files"); - return false; - } - if(data[0]->mimeType == "image/jpeg" || data[0]->mimeType == "image/png" || data[0]->mimeType == "image/webp") { - send = true; - } - if(send == false) { - logger::error("Only image files are allowed for snaps"); - return false; - } - } else { - send = true; + const std::unordered_map& extras, + const std::string& deviceSerialNo, + const std::vector>& fileGroup) { + // Prepare snapData + auto snapData = std::make_unique(); + snapData->fileGroup = fileGroup; + // Create an event + snapData->event = std::make_unique(); + snapData->event->set_created_at(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); + snapData->event->set_name(name); + snapData->event->add_tags("snap"); + for(const auto& tag : tags) { + snapData->event->add_tags(tag); } - if(send) { - return sendEvent(name, imgFrame, data, tagsTmp, extraData, deviceSerialNo); + auto* extrasData = snapData->event->mutable_extras(); + for(const auto& entry : extras) { + extrasData->insert({entry.first, entry.second}); } - return false; + snapData->event->set_source_serial_number(deviceSerialNo.empty() ? deviceSerialNumber : deviceSerialNo); + snapData->event->set_source_app_id(sourceAppId); + snapData->event->set_source_app_identifier(sourceAppIdentifier); + // Add the snap to snapBuffer + // TO DO: Should a snap buffer be limited by size like eventbuffer + if(snapBuffer.size() <= queueSize) { + std::lock_guard lock(snapBufferMutex); + snapBuffer.push_back(std::move(snapData)); + } else { + logger::warn("Snap buffer is full, dropping snap"); + return false; + } + return true; } -void EventsManager::sendFile(const std::shared_ptr& file, const std::string& url) { - // if file struct contains byte data, send it, along with filename and mime type - // if it file url, send it directly via url +void EventsManager::uploadFile(const std::shared_ptr& file, const std::string& url) { logger::info("Uploading file: to {}", url); - auto header = cpr::Header{{"Authorization", "Bearer " + token}}; - cpr::Multipart fileM{}; - if(file->type != EventDataType::FILE_URL) { - fileM = cpr::Multipart{{"file", cpr::Buffer{file->data.begin(), file->data.end(), file->fileName}, file->mimeType}}; - header["File-Size"] = std::to_string(std::size(file->data)); - } else { - fileM = cpr::Multipart{{ - "file", - cpr::File{file->data}, - }}; - header["File-Size"] = std::to_string(std::filesystem::file_size(file->data)); - } - cpr::Response r = cpr::Post( + auto header = cpr::Header(); + header["File-Size"] = file->size; + header["Content-Type"] = file->mimeType; + cpr::Response response = cpr::Put( cpr::Url{url}, - cpr::Multipart{fileM}, + cpr::Body{file->data}, cpr::Header{header}, cpr::VerifySsl(verifySsl), - cpr::ProgressCallback( [&](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, intptr_t userdata) -> bool { (void)userdata; @@ -313,23 +488,26 @@ void EventsManager::sendFile(const std::shared_ptr& file, const std:: (void)downloadNow; (void)uploadTotal; (void)uploadNow; - if(stopEventBuffer) { + if(stopUploadThread) { return false; } return true; })); - if(r.status_code != cpr::status::HTTP_OK) { - logger::error("Failed to upload file: {} error code {}", r.text, r.status_code); - } - if(logResponse) { - logger::info("Response: {}", r.text); + if(response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { + logger::error("Failed to upload file: {} ; error code: {}", response.text, response.status_code); + } else { + if(logResponse) { + logger::info("Response: {}", response.text); + } } } void EventsManager::cacheEvents() { + /* logger::info("Caching events"); // for each event, create a unique directory, save protobuf message and associated files - std::lock_guard lock(eventBufferMutex); + // TO DO: Make this using associate file field of proto::event::Event + std::lock_guard lock(uploadMutex); for(auto& eventM : eventBuffer) { auto& event = eventM->event; auto& data = eventM->data; @@ -348,9 +526,11 @@ void EventsManager::cacheEvents() { } } eventBuffer.clear(); + */ } void EventsManager::uploadCachedData() { + /* // iterate over all directories in cacheDir, read event.pb and associated files, and send them logger::info("Uploading cached data"); if(!checkConnection()) { @@ -383,10 +563,10 @@ void EventsManager::uploadCachedData() { eventBuffer.push_back(eventMessage); } } + */ } bool EventsManager::checkForCachedData() { - // check if cacheDir exists if(!std::filesystem::exists(cacheDir)) { logger::warn("Cache directory does not exist"); return false; @@ -415,60 +595,29 @@ void EventsManager::setToken(const std::string& token) { this->token = token; } -bool EventsManager::checkConnection() { - cpr::Response r = cpr::Get(cpr::Url{url + "/health"}, cpr::VerifySsl(verifySsl)); - if(r.status_code != cpr::status::HTTP_OK) { - logger::error("Failed to connect to events service: {} {}", r.text, r.status_code); - return false; - } - logger::info("Connected to events service"); - return true; -} -std::string EventsManager::createUUID() { - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> dis(0, 15); - std::uniform_int_distribution<> dis2(8, 11); - - std::stringstream ss; - int i = 0; - ss << std::hex; - for(i = 0; i < 8; i++) { - ss << dis(gen); - } - ss << "-"; - for(i = 0; i < 4; i++) { - ss << dis(gen); - } - ss << "-4"; - for(i = 0; i < 3; i++) { - ss << dis(gen); - } - ss << "-"; - ss << dis2(gen); - for(i = 0; i < 3; i++) { - ss << dis(gen); - } - ss << "-"; - for(i = 0; i < 12; i++) { - ss << dis(gen); - }; - return ss.str(); -} void EventsManager::setQueueSize(uint64_t queueSize) { this->queueSize = queueSize; } + void EventsManager::setLogResponse(bool logResponse) { this->logResponse = logResponse; } + +void EventsManager::setLogUploadResults(bool logUploadResults) { + this->logUploadResults = logUploadResults; +} + void EventsManager::setDeviceSerialNumber(const std::string& deviceSerialNumber) { this->deviceSerialNumber = deviceSerialNumber; } + void EventsManager::setVerifySsl(bool verifySsl) { this->verifySsl = verifySsl; } + void EventsManager::setCacheIfCannotSend(bool cacheIfCannotSend) { this->cacheIfCannotSend = cacheIfCannotSend; } + } // namespace utility } // namespace dai From 83543c5a2f500d9877548ea4565493557207e535 Mon Sep 17 00:00:00 2001 From: aljazdu Date: Fri, 26 Sep 2025 16:02:03 +0200 Subject: [PATCH 02/33] Added FileData ImgDetections constructor --- include/depthai/utility/EventsManager.hpp | 2 ++ src/utility/EventsManager.cpp | 28 +++++++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 311a856c8d..7fdcafebc2 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -13,6 +13,7 @@ #include "depthai/pipeline/datatype/EncodedFrame.hpp" #include "depthai/pipeline/datatype/ImgFrame.hpp" #include "depthai/pipeline/datatype/NNData.hpp" +#include "depthai/pipeline/datatype/ImgDetections.hpp" namespace dai { namespace proto { @@ -31,6 +32,7 @@ class FileData { explicit FileData(const std::shared_ptr& imgFrame, std::string fileName); explicit FileData(const std::shared_ptr& encodedFrame, std::string fileName); explicit FileData(const std::shared_ptr& nnData, std::string fileName); + explicit FileData(const std::shared_ptr& imgDetections, std::string fileName); bool toFile(const std::string& path); private: diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 9a4e294143..78805d57a2 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -86,6 +86,17 @@ FileData::FileData(const std::shared_ptr& nnData, std::string fileName) data = ss.str(); } +FileData::FileData(const std::shared_ptr& imgDetections, std::string fileName) + : mimeType("application/x-protobuf"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::ANNOTATION) { + // Serialize ImgDetections + std::vector imgDetectionsSerialized = imgDetections->serializeProto(); + std::stringstream ss; + ss.write((const char*)imgDetectionsSerialized.data(), imgDetectionsSerialized.size()); + data = ss.str(); + size = data.size(); + checksum = CalculateSHA256Checksum(data); +} + bool FileData::toFile(const std::string& path) { // check if filename is not empty if(fileName.empty()) { @@ -138,9 +149,6 @@ EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float pu sourceAppId = utility::getEnvAs("OAKAGENT_APP_VERSION", ""); sourceAppIdentifier = utility::getEnvAs("OAKAGENT_APP_IDENTIFIER", ""); token = utility::getEnvAs("DEPTHAI_HUB_API_KEY", ""); - std::cout << "LOGGER LEVEL" << logger::get_level() << "\n"; - dai::Logging::getInstance().logger.set_level(spdlog::level::info); - std::cout << "LOGGER LEVEL" << logger::get_level() << "\n"; uploadThread = std::make_unique([this]() { while(!stopUploadThread) { uploadFileBatch(); @@ -170,12 +178,15 @@ EventsManager::~EventsManager() { } bool EventsManager::fetchConfigurationLimits() { - auto apiUsage = std::make_unique(); + // TO DO: Determine and add when and how often this fetch should happen ! + logger::info("Fetching configuration limits"); + auto header = cpr::Header(); + header["Authorization"] = "Bearer " + token; + header["Content-Type"] = "application/x-protobuf"; cpr::Url requestUrl = static_cast(this->url + "/v2/api-usage"); cpr::Response response = cpr::Get( cpr::Url{requestUrl}, - //cpr::Body{serializedBatch}, - //cpr::Header{{"Authorization", "Bearer " + token}}, + cpr::Header{header}, cpr::VerifySsl(verifySsl), cpr::ProgressCallback( [&](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, intptr_t userdata) -> bool { @@ -197,6 +208,9 @@ bool EventsManager::fetchConfigurationLimits() { if(logResponse) { logger::info("Response: {}", response.text); } + auto apiUsage = std::make_unique(); + apiUsage->ParseFromString(response.text); + // TO DO: Use this data to set the limits } return true; } @@ -472,7 +486,7 @@ bool EventsManager::sendSnap(const std::string& name, } void EventsManager::uploadFile(const std::shared_ptr& file, const std::string& url) { - logger::info("Uploading file: to {}", url); + logger::info("Uploading file to: {}", url); auto header = cpr::Header(); header["File-Size"] = file->size; header["Content-Type"] = file->mimeType; From c74b28c8e4ccdc9869fbfa99d73b95ac0893249e Mon Sep 17 00:00:00 2001 From: aljazdu Date: Fri, 26 Sep 2025 16:21:03 +0200 Subject: [PATCH 03/33] Updated EventsManager bindings --- bindings/python/src/utility/EventsManagerBindings.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 7c5e7990cd..74bb0dac2b 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -26,7 +26,8 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { .def(py::init(), py::arg("fileUrl")) .def(py::init&, std::string>(), py::arg("imgFrame"), py::arg("fileName")) .def(py::init&, std::string>(), py::arg("encodedFrame"), py::arg("fileName")) - .def(py::init&, std::string>(), py::arg("nnData"), py::arg("fileName")); + .def(py::init&, std::string>(), py::arg("nnData"), py::arg("fileName")) + .def(py::init&, std::string>(), py::arg("imgDetections"), py::arg("fileName")); py::class_(m, "EventsManager") .def(py::init<>()) From ba513841a17f1bdfe974ab59af78440dfd7586d8 Mon Sep 17 00:00:00 2001 From: aljazdu <74094620+aljazdu@users.noreply.github.com> Date: Sun, 28 Sep 2025 15:22:07 +0200 Subject: [PATCH 04/33] Updated events manager bindings --- bindings/python/src/utility/EventsManagerBindings.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 74bb0dac2b..6d2b81d258 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -41,6 +41,7 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { .def("setToken", &EventsManager::setToken, py::arg("token"), DOC(dai, utility, EventsManager, setToken)) .def("setQueueSize", &EventsManager::setQueueSize, py::arg("queueSize"), DOC(dai, utility, EventsManager, setQueueSize)) .def("setLogResponse", &EventsManager::setLogResponse, py::arg("logResponse"), DOC(dai, utility, EventsManager, setLogResponse)) + .def("setLogUploadResults", &EventsManager::setLogUploadResults, py::arg("logUploadResults"), DOC(dai, utility, EventsManager, setLogUploadResults)) .def("setDeviceSerialNumber", &EventsManager::setDeviceSerialNumber, py::arg("deviceSerialNumber"), From bd9471ed7a7b1caaeded67b430d9cf652d2eb55d Mon Sep 17 00:00:00 2001 From: aljazdu Date: Mon, 29 Sep 2025 10:59:53 +0200 Subject: [PATCH 05/33] Updated FileData constructors --- include/depthai/utility/EventsManager.hpp | 12 +-- src/utility/EventsManager.cpp | 126 +++++++++++++--------- 2 files changed, 82 insertions(+), 56 deletions(-) diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 7fdcafebc2..b355789685 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -28,25 +28,25 @@ namespace utility { class FileData { public: FileData(const std::string& data, const std::string& fileName, const std::string& mimeType); - explicit FileData(std::string fileUrl); + explicit FileData(const std::string& filePath, std::string fileName); explicit FileData(const std::shared_ptr& imgFrame, std::string fileName); explicit FileData(const std::shared_ptr& encodedFrame, std::string fileName); explicit FileData(const std::shared_ptr& nnData, std::string fileName); explicit FileData(const std::shared_ptr& imgDetections, std::string fileName); - bool toFile(const std::string& path); + bool toFile(const std::string& inputPath); private: /** * Calculate SHA256 checksum for the given data */ - std::string CalculateSHA256Checksum(const std::string& data); + std::string calculateSHA256Checksum(const std::string& data); - std::string checksum; std::string mimeType; - uint64_t size; std::string fileName; - proto::event::PrepareFileUploadClass classification; std::string data; + uint64_t size; + std::string checksum; + proto::event::PrepareFileUploadClass classification; friend class EventsManager; }; diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 78805d57a2..02449b90e8 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -21,33 +21,56 @@ using std::move; // TO DO: // 1. ADD CHECKS FOR STUFF AS VALIDATION RULES IN CLICKUP DOCS // 2. Add the newly updated file limits, event limits, api usage) -// 3. FileData should be determined, streamlined and wrapped for the final user +// 3. FileData should be determined, streamlined and wrapped for the final user. Add API FileData::FileData(const std::string& data, const std::string& fileName, const std::string& mimeType) - : mimeType(mimeType), fileName(fileName), data(data) { + : mimeType(mimeType), + fileName(fileName), + data(data), + size(data.size()), + checksum(calculateSHA256Checksum(data)), + classification(proto::event::PrepareFileUploadClass::UNKNOWN_FILE) {} + +FileData::FileData(const std::string& filePath, std::string fileName) + : fileName(std::move(fileName)) { + static const std::unordered_map mimeTypeExtensionMap = { + {".html", "text/html"}, + {".htm", "text/html"}, + {".css", "text/css"}, + {".js", "text/javascript"}, + {".png", "image/png"}, + {".jpg", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".gif", "image/gif"}, + {".svg", "image/svg+xml"}, + {".json", "application/json"}, + {".txt", "text/plain"} + }; + // Read the data + std::ifstream fileStream(filePath, std::ios::binary | std::ios::ate); + if (!fileStream) { + return; + } + std::streamsize fileSize = fileStream.tellg(); + data.resize(static_cast(fileSize)); + fileStream.seekg(0, std::ios::beg); + fileStream.read(data.data(), fileSize); size = data.size(); - classification = proto::event::PrepareFileUploadClass::UNKNOWN_FILE; - checksum = CalculateSHA256Checksum(data); -} - -FileData::FileData(std::string fileUrl) : data(std::move(fileUrl)) { - fileName = std::filesystem::path(data).filename().string(); - static std::map mimeTypes = {{".html", "text/html"}, - {".htm", "text/html"}, - {".css", "text/css"}, - {".js", "application/javascript"}, - {".png", "image/png"}, - {".jpg", "image/jpeg"}, - {".jpeg", "image/jpeg"}, - {".gif", "image/gif"}, - {".svg", "image/svg+xml"}, - {".json", "application/json"}, - {".txt", "text/plain"}}; - auto ext = std::filesystem::path(data).extension().string(); - auto it = mimeTypes.find(ext); - mimeType = "application/octet-stream"; - if(it != mimeTypes.end()) { + checksum = calculateSHA256Checksum(data); + // Determine the mime type + auto it = mimeTypeExtensionMap.find(std::filesystem::path(filePath).extension().string()); + if (it != mimeTypeExtensionMap.end()) { mimeType = it->second; + } else { + mimeType = "application/octet-stream"; + } + static const std::unordered_set imageMimeTypes = { + "image/jpeg", "image/png", "image/webp", "image/bmp", "image/tiff" + }; + if (imageMimeTypes.find(mimeType) != imageMimeTypes.end()) { + classification = proto::event::PrepareFileUploadClass::IMAGE_COLOR; + } else { + classification = proto::event::PrepareFileUploadClass::UNKNOWN_FILE; } } @@ -55,18 +78,18 @@ FileData::FileData(const std::shared_ptr& imgFrame, std::string fileNa : mimeType("image/jpeg"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::IMAGE_COLOR) { // Convert ImgFrame to bytes cv::Mat cvFrame = imgFrame->getCvFrame(); - std::vector buf; - cv::imencode(".jpg", cvFrame, buf); + std::vector buffer; + cv::imencode(".jpg", cvFrame, buffer); std::stringstream ss; - ss.write((const char*)buf.data(), buf.size()); + ss.write((const char*)buffer.data(), buffer.size()); data = ss.str(); size = data.size(); - checksum = CalculateSHA256Checksum(data); + checksum = calculateSHA256Checksum(data); } FileData::FileData(const std::shared_ptr& encodedFrame, std::string fileName) - : fileName(std::move(fileName)) {//, type(EventDataType::ENCODED_FRAME) { + : mimeType("image/jpeg"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::IMAGE_COLOR) { // Convert EncodedFrame to bytes if(encodedFrame->getProfile() != EncodedFrame::Profile::JPEG) { logger::error("Only JPEG encoded frames are supported"); @@ -75,15 +98,18 @@ FileData::FileData(const std::shared_ptr& encodedFrame, std::strin std::stringstream ss; ss.write((const char*)encodedFrame->getData().data(), encodedFrame->getData().size()); data = ss.str(); - mimeType = "image/jpeg"; + size = data.size(); + checksum = calculateSHA256Checksum(data); } FileData::FileData(const std::shared_ptr& nnData, std::string fileName) - : mimeType("application/octet-stream"), fileName(std::move(fileName)) {//, type(EventDataType::NN_DATA) { + : mimeType("application/octet-stream"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::UNKNOWN_FILE) { // Convert NNData to bytes std::stringstream ss; ss.write((const char*)nnData->data->getData().data(), nnData->data->getData().size()); data = ss.str(); + size = data.size(); + checksum = calculateSHA256Checksum(data); } FileData::FileData(const std::shared_ptr& imgDetections, std::string fileName) @@ -94,36 +120,36 @@ FileData::FileData(const std::shared_ptr& imgDetections, std::str ss.write((const char*)imgDetectionsSerialized.data(), imgDetectionsSerialized.size()); data = ss.str(); size = data.size(); - checksum = CalculateSHA256Checksum(data); + checksum = calculateSHA256Checksum(data); } -bool FileData::toFile(const std::string& path) { - // check if filename is not empty +bool FileData::toFile(const std::string& inputPath) { if(fileName.empty()) { logger::error("Filename is empty"); return false; } - std::filesystem::path p(path); - if(true) {//type == EventDataType::FILE_URL) { - // get the filename from the url - std::filesystem::copy(data, p / fileName); - } else { - std::string extension = mimeType == "image/jpeg" ? ".jpg" : ".txt"; - // check if file exists, if yes, append a number to the filename - std::string fileNameTmp = fileName; - int i = 0; - while(std::filesystem::exists(p / (fileNameTmp + extension))) { - logger::warn("File {} already exists, appending number to filename", fileNameTmp); - fileNameTmp = fileName + "_" + std::to_string(i); - i++; - } - std::ofstream fileStream(p / (fileNameTmp + extension), std::ios::binary); - fileStream.write(data.data(), data.size()); + std::filesystem::path path(inputPath); + std::string extension = mimeType == "image/jpeg" ? ".jpg" : ".txt"; + // Choose a unique filename + std::filesystem::path target = path / (fileName + extension); + for (int i = 1; std::filesystem::exists(target); ++i) { + logger::warn("File {} exists, trying a new name", target.string()); + target = path / (fileName + "_" + std::to_string(i) + extension); + } + std::ofstream fileStream(target, std::ios::binary); + if (!fileStream) { + logger::error("Failed to open file for writing: {}", target.string()); + return false; + } + fileStream.write(data.data(), static_cast(data.size())); + if (!fileStream) { + logger::error("Failed to write all data to: {}", target.string()); + return false; } return true; } -std::string FileData::CalculateSHA256Checksum(const std::string& data) { +std::string FileData::calculateSHA256Checksum(const std::string& data) { unsigned char digest[SHA256_DIGEST_LENGTH]; SHA256(reinterpret_cast(data.data()), data.size(), digest); From 46d164e29483e1fe40f00b49fcc9b2ee98e99ba2 Mon Sep 17 00:00:00 2001 From: aljazdu Date: Mon, 29 Sep 2025 14:51:14 +0200 Subject: [PATCH 06/33] Fixed response logging, removed redundant logUploadResults --- .../src/utility/EventsManagerBindings.cpp | 3 +- include/depthai/utility/EventsManager.hpp | 7 -- src/utility/EventsManager.cpp | 80 +++---------------- 3 files changed, 14 insertions(+), 76 deletions(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 6d2b81d258..3e1e024c99 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -23,7 +23,7 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { using namespace dai::utility; py::class_>(m, "FileData") .def(py::init(), py::arg("data"), py::arg("fileName"), py::arg("mimeType")) - .def(py::init(), py::arg("fileUrl")) + .def(py::init(), py::arg("filePath"), py::arg("fileName")) .def(py::init&, std::string>(), py::arg("imgFrame"), py::arg("fileName")) .def(py::init&, std::string>(), py::arg("encodedFrame"), py::arg("fileName")) .def(py::init&, std::string>(), py::arg("nnData"), py::arg("fileName")) @@ -41,7 +41,6 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { .def("setToken", &EventsManager::setToken, py::arg("token"), DOC(dai, utility, EventsManager, setToken)) .def("setQueueSize", &EventsManager::setQueueSize, py::arg("queueSize"), DOC(dai, utility, EventsManager, setQueueSize)) .def("setLogResponse", &EventsManager::setLogResponse, py::arg("logResponse"), DOC(dai, utility, EventsManager, setLogResponse)) - .def("setLogUploadResults", &EventsManager::setLogUploadResults, py::arg("logUploadResults"), DOC(dai, utility, EventsManager, setLogUploadResults)) .def("setDeviceSerialNumber", &EventsManager::setDeviceSerialNumber, py::arg("deviceSerialNumber"), diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index b355789685..79b05612bd 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -126,12 +126,6 @@ class EventsManager { * @return void */ void setLogResponse(bool logResponse); - /** - * Set whether to log the results of uploads to the server. By default, logUploadResults is set to false - * @param logUploadResults bool - * @return void - */ - void setLogUploadResults(bool logUploadResults); /** * Set whether to verify the SSL certificate. By default, verifySsl is set to false * @param verifySsl bool @@ -195,7 +189,6 @@ class EventsManager { uint64_t queueSize; float publishInterval; bool logResponse; - bool logUploadResults; bool verifySsl; std::string cacheDir; bool cacheIfCannotSend; diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 02449b90e8..3686364aa6 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -167,7 +167,6 @@ EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float pu queueSize(10), publishInterval(publishInterval), logResponse(false), - logUploadResults(false), verifySsl(true), cacheDir("/internal/private"), cacheIfCannotSend(false), @@ -227,15 +226,15 @@ bool EventsManager::fetchConfigurationLimits() { return true; })); if(response.status_code != cpr::status::HTTP_OK) { - logger::error("Failed to fetch configuration limits: {} {}", response.text, response.status_code); + logger::error("Failed to fetch configuration limits, status code: {}", response.status_code); return false; } else { logger::info("Configuration limits fetched successfully"); - if(logResponse) { - logger::info("Response: {}", response.text); - } auto apiUsage = std::make_unique(); apiUsage->ParseFromString(response.text); + if(logResponse) { + logger::info("ApiUsage response: \n{}", apiUsage->DebugString()); + } // TO DO: Use this data to set the limits } return true; @@ -295,50 +294,23 @@ void EventsManager::uploadFileBatch() { } return true; })); - if (response.status_code != cpr::status::HTTP_CREATED) { - logger::error("Failed to prepare a batch of file groups: {}; status code: {}", response.text, response.status_code); + if (response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { + logger::error("Failed to prepare a batch of file groups, status code: {}", response.status_code); } else { logger::info("Batch of file groups has been successfully prepared"); + auto fileGroupBatchUpload = std::make_unique(); + fileGroupBatchUpload->ParseFromString(response.text); if (logResponse) { - logger::info("Response: {}", response.text); + logger::info("BatchFileUploadResult response: \n{}", fileGroupBatchUpload->DebugString()); } // Upload accepted files - auto fileGroupBatchUpload = std::make_unique(); - fileGroupBatchUpload->ParseFromString(response.text); for (int i = 0; i < fileGroupBatchUpload->groups_size(); i++) { auto snapData = snapBuffer.at(i); auto uploadGroupResult = fileGroupBatchUpload->groups(i); // Rejected group - if (uploadGroupResult.has_rejected()) { - if (!logUploadResults) { - continue; - } - auto reason = uploadGroupResult.rejected().reason(); - const auto* desc = proto::event::RejectedFileGroupReason_descriptor(); - std::string reasonName = "Unknown RejectedFileGroupReason"; - if (const auto* value = desc->FindValueByNumber(static_cast(reason))) { - reasonName = value->name(); - } - logger::info("File group rejected because of: {}", reasonName); - - for (int j = 0; j < uploadGroupResult.files_size(); j++) { - auto uploadFileResult = uploadGroupResult.files(j); - if(uploadFileResult.result_case() == proto::event::FileUploadResult::kAccepted) { - logger::info("File with id: {} accepted", uploadFileResult.accepted().id()); - } - else if (uploadFileResult.result_case() == proto::event::FileUploadResult::kRejected) { - auto reason = uploadFileResult.rejected().reason(); - const auto* desc = proto::event::RejectedFileReason_descriptor(); - std::string reasonName = "Unknown RejectedFileReason"; - if (const auto* value = desc->FindValueByNumber(static_cast(reason))) { - reasonName = value->name(); - } - logger::info("File rejected because of: {}; message: {}", reasonName, uploadFileResult.rejected().message()); - } - } - } else { + if (!uploadGroupResult.has_rejected()) { for (int j = 0; j < uploadGroupResult.files_size(); j++) { auto uploadFileResult = uploadGroupResult.files(j); if(uploadFileResult.result_case() == proto::event::FileUploadResult::kAccepted) { @@ -404,31 +376,13 @@ void EventsManager::uploadEventBatch() { return true; })); if(response.status_code != cpr::status::HTTP_OK) { - logger::error("Failed to send event: {} {}", response.text, response.status_code); + logger::error("Failed to send event, status code: {}", response.status_code); } else { logger::info("Event sent successfully"); if(logResponse) { - logger::info("Response: {}", response.text); - } - - if (logUploadResults) { auto eventBatchUploadResults = std::make_unique(); eventBatchUploadResults->ParseFromString(response.text); - for(int i = 0; i < eventBatchUploadResults->events_size(); i++) { - auto eventUploadResult = eventBatchUploadResults->events(i); - if(eventUploadResult.result_case() == proto::event::EventResult::kAccepted) { - logger::info("Event with id: {} accepted", eventUploadResult.accepted().id()); - } - else if (eventUploadResult.result_case() == proto::event::EventResult::kRejected) { - auto reason = eventUploadResult.rejected().reason(); - const auto* desc = proto::event::RejectedEventReason_descriptor(); - std::string reasonName = "Unknown RejectedEventReason"; - if (const auto* value = desc->FindValueByNumber(static_cast(reason))) { - reasonName = value->name(); - } - logger::info("Event rejected because of: {}; message: {}", reasonName, eventUploadResult.rejected().message()); - } - } + logger::info("BatchUploadEvents response: \n{}", eventBatchUploadResults->DebugString()); } // TO DO: Caching is ignored for now. Fix this later @@ -534,11 +488,7 @@ void EventsManager::uploadFile(const std::shared_ptr& file, const std: return true; })); if(response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { - logger::error("Failed to upload file: {} ; error code: {}", response.text, response.status_code); - } else { - if(logResponse) { - logger::info("Response: {}", response.text); - } + logger::error("Failed to upload file, status code: {}", response.status_code); } } @@ -643,10 +593,6 @@ void EventsManager::setLogResponse(bool logResponse) { this->logResponse = logResponse; } -void EventsManager::setLogUploadResults(bool logUploadResults) { - this->logUploadResults = logUploadResults; -} - void EventsManager::setDeviceSerialNumber(const std::string& deviceSerialNumber) { this->deviceSerialNumber = deviceSerialNumber; } From 3aa210a2d54c32e0f75f8ac6b4a81f80b6230f2f Mon Sep 17 00:00:00 2001 From: aljazdu Date: Mon, 29 Sep 2025 17:09:55 +0200 Subject: [PATCH 07/33] Updated logging level --- src/utility/EventsManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 3686364aa6..eb6da5a35f 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -174,6 +174,7 @@ EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float pu sourceAppId = utility::getEnvAs("OAKAGENT_APP_VERSION", ""); sourceAppIdentifier = utility::getEnvAs("OAKAGENT_APP_IDENTIFIER", ""); token = utility::getEnvAs("DEPTHAI_HUB_API_KEY", ""); + dai::Logging::getInstance().logger.set_level(spdlog::level::info); uploadThread = std::make_unique([this]() { while(!stopUploadThread) { uploadFileBatch(); From 903b41b1a0b27aacfebbfd5c54c28825bcee896a Mon Sep 17 00:00:00 2001 From: aljazdu Date: Tue, 30 Sep 2025 10:07:48 +0200 Subject: [PATCH 08/33] Updated ImgDetections mimeType, sourceAppId --- src/utility/EventsManager.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index eb6da5a35f..04453d0418 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -113,7 +113,7 @@ FileData::FileData(const std::shared_ptr& nnData, std::string fileName) } FileData::FileData(const std::shared_ptr& imgDetections, std::string fileName) - : mimeType("application/x-protobuf"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::ANNOTATION) { + : mimeType("application/x-protobuf; proto=ImgDetections"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::ANNOTATION) { // Serialize ImgDetections std::vector imgDetectionsSerialized = imgDetections->serializeProto(); std::stringstream ss; @@ -171,7 +171,9 @@ EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float pu cacheDir("/internal/private"), cacheIfCannotSend(false), stopUploadThread(false) { - sourceAppId = utility::getEnvAs("OAKAGENT_APP_VERSION", ""); + auto appId = utility::getEnvAs("OAKAGENT_APP_ID", ""); + auto containerId = utility::getEnvAs("OAKAGENT_CONTAINER_ID", ""); + sourceAppId = appId == "" ? containerId : appId; sourceAppIdentifier = utility::getEnvAs("OAKAGENT_APP_IDENTIFIER", ""); token = utility::getEnvAs("DEPTHAI_HUB_API_KEY", ""); dai::Logging::getInstance().logger.set_level(spdlog::level::info); From f3e290a21a9e52f2ee1bce16c98ee38d7c14839f Mon Sep 17 00:00:00 2001 From: aljazdu Date: Wed, 1 Oct 2025 13:47:24 +0200 Subject: [PATCH 09/33] Added events validation rules --- include/depthai/utility/EventsManager.hpp | 32 ++++++- src/utility/EventsManager.cpp | 106 ++++++++++++++++++++-- 2 files changed, 125 insertions(+), 13 deletions(-) diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 79b05612bd..bf23ccf394 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -55,11 +55,6 @@ class EventsManager { explicit EventsManager(std::string url = "https://events.cloud.luxonis.com", bool uploadCachedOnStart = false, float publishInterval = 10.0); ~EventsManager(); - /** - * Fetch configuration limits and quotas for snaps, through the api - * @return bool - */ - bool fetchConfigurationLimits(); /** * Send an event to the events service * @param name Name of the event @@ -180,6 +175,17 @@ class EventsManager { * // TO DO: Add description */ bool checkForCachedData(); + /** + * Validate the input event by checking that its fields adhere to defined limitations + * @param inputEvent Input event to be validated + * @return bool + */ + bool validateEvent(const proto::event::Event& inputEvent); + /** + * Fetch configuration limits and quotas for snaps, through the api + * @return bool + */ + bool fetchConfigurationLimits(); std::string token; std::string deviceSerialNumber; @@ -200,6 +206,22 @@ class EventsManager { std::mutex stopThreadConditionMutex; std::atomic stopUploadThread; std::condition_variable eventBufferCondition; + + uint64_t maximumFileSize; + uint64_t remainingStorage; + uint64_t warningStorage; + uint64_t bytesPerHour; + uint32_t uploadsPerHour; + uint32_t eventsPerHour; + uint32_t snapsPerHour; + + static constexpr int eventValidationNameLength = 56; + static constexpr int eventValidationMaxTags = 20; + static constexpr int eventValidationTagLength = 56; + static constexpr int eventValidationMaxExtras = 25; + static constexpr int eventValidationExtraKeyLength = 40; + static constexpr int eventValidationExtraValueLength = 100; + static constexpr int eventValidationMaxAssociateFiles = 20; }; } // namespace utility } // namespace dai diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 04453d0418..f7061e0e63 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -18,10 +18,6 @@ namespace dai { namespace utility { using std::move; -// TO DO: -// 1. ADD CHECKS FOR STUFF AS VALIDATION RULES IN CLICKUP DOCS -// 2. Add the newly updated file limits, event limits, api usage) -// 3. FileData should be determined, streamlined and wrapped for the final user. Add API FileData::FileData(const std::string& data, const std::string& fileName, const std::string& mimeType) : mimeType(mimeType), @@ -49,6 +45,7 @@ FileData::FileData(const std::string& filePath, std::string fileName) // Read the data std::ifstream fileStream(filePath, std::ios::binary | std::ios::ate); if (!fileStream) { + logger::error("File: {} doesn't exist", filePath); return; } std::streamsize fileSize = fileStream.tellg(); @@ -170,15 +167,28 @@ EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float pu verifySsl(true), cacheDir("/internal/private"), cacheIfCannotSend(false), - stopUploadThread(false) { + stopUploadThread(false), + warningStorage(52428800) { auto appId = utility::getEnvAs("OAKAGENT_APP_ID", ""); auto containerId = utility::getEnvAs("OAKAGENT_CONTAINER_ID", ""); sourceAppId = appId == "" ? containerId : appId; sourceAppIdentifier = utility::getEnvAs("OAKAGENT_APP_IDENTIFIER", ""); token = utility::getEnvAs("DEPTHAI_HUB_API_KEY", ""); - dai::Logging::getInstance().logger.set_level(spdlog::level::info); + dai::Logging::getInstance().logger.set_level(spdlog::level::info); // TO DO: Remove + // Separate thread handling uploads uploadThread = std::make_unique([this]() { + auto nextTime = std::chrono::steady_clock::now(); while(!stopUploadThread) { + // Hourly check for fetching configuration and limits + auto currentTime = std::chrono::steady_clock::now(); + if (currentTime >= nextTime) { + fetchConfigurationLimits(); + nextTime += std::chrono::hours(1); + if (remainingStorage <= warningStorage) { + logger::warn("Current remaining storage is running low: {} MB", remainingStorage / (1024*1024)); + } + } + uploadFileBatch(); uploadEventBatch(); std::unique_lock lock(stopThreadConditionMutex); @@ -206,7 +216,6 @@ EventsManager::~EventsManager() { } bool EventsManager::fetchConfigurationLimits() { - // TO DO: Determine and add when and how often this fetch should happen ! logger::info("Fetching configuration limits"); auto header = cpr::Header(); header["Authorization"] = "Bearer " + token; @@ -239,6 +248,8 @@ bool EventsManager::fetchConfigurationLimits() { logger::info("ApiUsage response: \n{}", apiUsage->DebugString()); } // TO DO: Use this data to set the limits + maximumFileSize = apiUsage->files().max_file_size_bytes(); + remainingStorage = apiUsage->files().remaining_storage_bytes(); } return true; } @@ -422,6 +433,11 @@ bool EventsManager::sendEvent(const std::string& name, auto addedFile = event->add_associate_files(); addedFile->set_id(file); } + if (!validateEvent(*event)) { + logger::error("Failed to send event, validation failed"); + return false; + } + // Add event to eventBuffer if(eventBuffer.size() <= queueSize) { std::lock_guard lock(eventBufferMutex); @@ -445,7 +461,6 @@ bool EventsManager::sendSnap(const std::string& name, snapData->event = std::make_unique(); snapData->event->set_created_at(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); snapData->event->set_name(name); - snapData->event->add_tags("snap"); for(const auto& tag : tags) { snapData->event->add_tags(tag); } @@ -456,6 +471,17 @@ bool EventsManager::sendSnap(const std::string& name, snapData->event->set_source_serial_number(deviceSerialNo.empty() ? deviceSerialNumber : deviceSerialNo); snapData->event->set_source_app_id(sourceAppId); snapData->event->set_source_app_identifier(sourceAppIdentifier); + if (!validateEvent(*snapData->event)) { + logger::error("Failed to send snap, validation failed"); + return false; + } + snapData->event->add_tags("snap"); + for (const auto& file : fileGroup) { + if (file->size >= maximumFileSize) { + logger::error("Failed to send snap, file: {} is bigger then the configured maximum size: {}", file->fileName, maximumFileSize); + return false; + } + } // Add the snap to snapBuffer // TO DO: Should a snap buffer be limited by size like eventbuffer if(snapBuffer.size() <= queueSize) { @@ -468,6 +494,70 @@ bool EventsManager::sendSnap(const std::string& name, return true; } +bool EventsManager::validateEvent(const proto::event::Event& inputEvent) { + // Name + const auto& name = inputEvent.name(); + if (name.empty()) { + logger::error("Invalid event name: empty string"); + return false; + } + if (name.length() > eventValidationNameLength) { + logger::error("Invalid event name: length {} exceeds {}", name.length(), eventValidationNameLength); + return false; + } + + // Tags + if (inputEvent.tags_size() > eventValidationMaxTags) { + logger::error("Invalid event tags: number of tags {} exceeds {}", inputEvent.tags_size(), eventValidationMaxTags); + return false; + } + for (int i = 0; i < inputEvent.tags_size(); ++i) { + const auto& tag = inputEvent.tags(i); + if (tag.empty()) { + logger::error("Invalid event tags: tag[{}] empty string", i); + return false; + } + if (tag.length() > 56) { + logger::error("Invalid event tags: tag[{}] length {} exceeds {}", i, tag.length(), eventValidationTagLength); + return false; + } + } + + // Event extras + if (inputEvent.extras_size() > eventValidationMaxExtras) { + logger::error("Invalid event extras: number of extras {} exceeds {}", inputEvent.extras_size(), eventValidationMaxExtras); + return false; + } + int index = 0; + for (const auto& extra : inputEvent.extras()) { + const auto& key = extra.first; + const auto& value = extra.second; + if (key.empty()) { + logger::error("Invalid event extras: extra[{}] key empty string", index); + return false; + } + if (key.length() > eventValidationExtraKeyLength) { + logger::error("Invalid event extras: extra[{}] key length {} exceeds {}", index, key.length(), eventValidationExtraKeyLength); + return false; + } + if (value.length() > eventValidationExtraValueLength) { + logger::error("Invalid event extras: extra[{}] value length {} exceeds {}", + index, value.length(), eventValidationExtraValueLength); + return false; + } + index++; + } + + // Associate files + if (inputEvent.associate_files_size() > eventValidationMaxAssociateFiles) { + logger::error("Invalid associate files: number of associate files {} exceeds {}", + inputEvent.associate_files_size(), eventValidationMaxAssociateFiles); + return false; + } + + return true; +} + void EventsManager::uploadFile(const std::shared_ptr& file, const std::string& url) { logger::info("Uploading file to: {}", url); auto header = cpr::Header(); From 3380fad04c5670a1fe652965e2ec3d526fc76eba Mon Sep 17 00:00:00 2001 From: aljazdu Date: Sun, 5 Oct 2025 10:01:55 +0200 Subject: [PATCH 10/33] Batch preparation and upload of files is now async, added updated proto limits and quotas --- include/depthai/utility/EventsManager.hpp | 27 +- protos/Event.proto | 3 + src/utility/EventsManager.cpp | 304 ++++++++++++---------- 3 files changed, 191 insertions(+), 143 deletions(-) diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index bf23ccf394..77a88ac444 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "depthai/pipeline/datatype/ADatatype.hpp" #include "depthai/pipeline/datatype/EncodedFrame.hpp" @@ -155,10 +156,16 @@ class EventsManager { std::string cachePath; }; + struct FileUploadRetryPolicy { + int maxAttempts = 10; + float factor = 2.0f; + std::chrono::milliseconds baseDelay{100}; + }; + /** * Prepare and upload files from snapBuffer in batch */ - void uploadFileBatch(); + void uploadFileBatch(std::deque> inputSnapBatch); /** * Upload events from eventBuffer in batch */ @@ -166,7 +173,7 @@ class EventsManager { /** * Upload a file using the chosen url */ - void uploadFile(const std::shared_ptr& file, const std::string& url); + bool uploadFile(std::shared_ptr file, std::string url); /** * // TO DO: Add description */ @@ -199,21 +206,27 @@ class EventsManager { std::string cacheDir; bool cacheIfCannotSend; std::unique_ptr uploadThread; - std::vector> eventBuffer; - std::vector> snapBuffer; + std::deque> eventBuffer; + std::deque> snapBuffer; + std::vector> uploadFileBatchFutures; std::mutex eventBufferMutex; std::mutex snapBufferMutex; std::mutex stopThreadConditionMutex; std::atomic stopUploadThread; std::condition_variable eventBufferCondition; - uint64_t maximumFileSize; - uint64_t remainingStorage; - uint64_t warningStorage; + uint64_t maxFileSizeBytes; + uint64_t remainingStorageBytes; + uint64_t warningStorageBytes; uint64_t bytesPerHour; uint32_t uploadsPerHour; + uint32_t maxGroupsPerBatch; + uint32_t maxFilesPerGroup; uint32_t eventsPerHour; uint32_t snapsPerHour; + uint32_t eventsPerRequest; + + FileUploadRetryPolicy fileUploadRetryPolicy; static constexpr int eventValidationNameLength = 56; static constexpr int eventValidationMaxTags = 20; diff --git a/protos/Event.proto b/protos/Event.proto index 468484c762..6ecb55f843 100644 --- a/protos/Event.proto +++ b/protos/Event.proto @@ -119,11 +119,14 @@ message FileLimits { uint64 remaining_storage_bytes = 2; uint64 bytes_per_hour_rate = 3; uint32 uploads_per_hour_rate = 4; + uint32 groups_per_allocation = 5; + uint32 files_per_group_in_allocation = 6; } message EventLimits { uint32 events_per_hour_rate = 1; uint32 snaps_per_hour_rate = 2; + uint32 events_per_request = 3; } message ApiUsage { diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index f7061e0e63..b8e5ccfc60 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -18,7 +18,7 @@ namespace dai { namespace utility { using std::move; - +// TO DO: Keyboard shutdown and stuff, and killing threads must be handled! FileData::FileData(const std::string& data, const std::string& fileName, const std::string& mimeType) : mimeType(mimeType), fileName(fileName), @@ -161,35 +161,50 @@ std::string FileData::calculateSHA256Checksum(const std::string& data) { EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float publishInterval) : url(std::move(url)), - queueSize(10), + queueSize(100), publishInterval(publishInterval), logResponse(false), verifySsl(true), cacheDir("/internal/private"), cacheIfCannotSend(false), stopUploadThread(false), - warningStorage(52428800) { + warningStorageBytes(52428800) { auto appId = utility::getEnvAs("OAKAGENT_APP_ID", ""); auto containerId = utility::getEnvAs("OAKAGENT_CONTAINER_ID", ""); sourceAppId = appId == "" ? containerId : appId; sourceAppIdentifier = utility::getEnvAs("OAKAGENT_APP_IDENTIFIER", ""); token = utility::getEnvAs("DEPTHAI_HUB_API_KEY", ""); dai::Logging::getInstance().logger.set_level(spdlog::level::info); // TO DO: Remove - // Separate thread handling uploads + // Thread handling preparation and uploads uploadThread = std::make_unique([this]() { auto nextTime = std::chrono::steady_clock::now(); while(!stopUploadThread) { + // TO DO: remove + logger::info("Currently there are {} events in the buffer", eventBuffer.size()); + logger::info("Currently there are {} snaps in the buffer", snapBuffer.size()); // Hourly check for fetching configuration and limits auto currentTime = std::chrono::steady_clock::now(); if (currentTime >= nextTime) { + // TO DO: Fetching should be done for as long as needed, otherwise empty limits and config values fetchConfigurationLimits(); nextTime += std::chrono::hours(1); - if (remainingStorage <= warningStorage) { - logger::warn("Current remaining storage is running low: {} MB", remainingStorage / (1024*1024)); + if (remainingStorageBytes <= warningStorageBytes) { + logger::warn("Current remaining storage is running low: {} MB", remainingStorageBytes / (1024*1024)); } } + // Prepare the batch first to reduce contention + std::deque> snapBatch; + { + std::lock_guard lock(snapBufferMutex); + const std::size_t size = std::min(snapBuffer.size(), maxGroupsPerBatch); + snapBatch.insert(snapBatch.end(), std::make_move_iterator(snapBuffer.begin()), std::make_move_iterator(snapBuffer.begin() + size)); + snapBuffer.erase(snapBuffer.begin(), snapBuffer.begin() + size); + } + // TO DO: Handle the clearing of these futures + uploadFileBatchFutures.emplace_back(std::async(std::launch::async, [&]() { + uploadFileBatch(std::move(snapBatch)); + })); - uploadFileBatch(); uploadEventBatch(); std::unique_lock lock(stopThreadConditionMutex); eventBufferCondition.wait_for(lock, @@ -247,104 +262,157 @@ bool EventsManager::fetchConfigurationLimits() { if(logResponse) { logger::info("ApiUsage response: \n{}", apiUsage->DebugString()); } - // TO DO: Use this data to set the limits - maximumFileSize = apiUsage->files().max_file_size_bytes(); - remainingStorage = apiUsage->files().remaining_storage_bytes(); + // TO DO: Use this data + maxFileSizeBytes = apiUsage->files().max_file_size_bytes(); // + remainingStorageBytes = apiUsage->files().remaining_storage_bytes(); // + bytesPerHour = apiUsage->files().bytes_per_hour_rate(); + uploadsPerHour = apiUsage->files().uploads_per_hour_rate(); + maxGroupsPerBatch = apiUsage->files().groups_per_allocation(); + maxFilesPerGroup = apiUsage->files().files_per_group_in_allocation(); // + eventsPerHour = apiUsage->events().events_per_hour_rate(); + snapsPerHour = apiUsage->events().snaps_per_hour_rate(); + eventsPerRequest = apiUsage->events().events_per_request(); } return true; } -void EventsManager::uploadFileBatch() { - // Prepare files for upload +void EventsManager::uploadFileBatch(std::deque> inputSnapBatch) { + // Prepare files for upload auto fileGroupBatchPrepare = std::make_unique(); - { - std::lock_guard lock(snapBufferMutex); - if (snapBuffer.empty()) { - return; + if (inputSnapBatch.empty()) { + return; + } + if(token.empty()) { + logger::warn("Missing token, please set DEPTHAI_HUB_API_KEY environment variable or use setToken method"); + return; + } + // Fill the batch with the groups from inputSnapBatch and their corresponding files + for (size_t i = 0; i < inputSnapBatch.size(); ++i) { + auto fileGroup = std::make_unique(); + for (auto& file : inputSnapBatch.at(i)->fileGroup) { + auto addedFile = fileGroup->add_files(); + addedFile->set_checksum(file->checksum); + addedFile->set_mime_type(file->mimeType); + addedFile->set_size(file->size); + addedFile->set_filename(file->fileName); + addedFile->set_classification(file->classification); } - if(token.empty()) { - logger::warn("Missing token, please set DEPTHAI_HUB_API_KEY environment variable or use setToken method"); - return; + fileGroupBatchPrepare->add_groups()->Swap(fileGroup.get()); + } + + while (!stopUploadThread) { + std::string serializedBatch; + fileGroupBatchPrepare->SerializeToString(&serializedBatch); + cpr::Url requestUrl = static_cast(this->url + "/v2/files/prepare-batch"); + cpr::Response response = cpr::Post( + cpr::Url{requestUrl}, + cpr::Body{serializedBatch}, + cpr::Header{{"Authorization", "Bearer " + token}}, + cpr::VerifySsl(verifySsl), + cpr::ProgressCallback( + [&](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, intptr_t userdata) -> bool { + (void)userdata; + (void)downloadTotal; + (void)downloadNow; + (void)uploadTotal; + (void)uploadNow; + if(stopUploadThread) { + return false; + } + return true; + })); + if (response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { + logger::error("Failed to prepare a batch of file groups, status code: {}", response.status_code); + // TO DO: After a few tries, we can determine that the connection is not established. + // We can then check if caching is chosen. and do it } - //if(!checkConnection()) { - // TO DO: Caching is ignored for now. Fix this later - //if(cacheIfCannotSend) { - // cacheEvents(); - //} - // return; - //} - // Fill the batch with the groups from snapBuffer and their corresponding files - for (auto& snapData : snapBuffer) { - auto fileGroup = std::make_unique(); - for (auto& file : snapData->fileGroup) { - auto addedFile = fileGroup->add_files(); - addedFile->set_checksum(file->checksum); - addedFile->set_mime_type(file->mimeType); - addedFile->set_size(file->size); - addedFile->set_filename(file->fileName); - addedFile->set_classification(file->classification); + else { + logger::info("Batch of file groups has been successfully prepared"); + auto fileGroupBatchUpload = std::make_unique(); + fileGroupBatchUpload->ParseFromString(response.text); + if (logResponse) { + logger::info("BatchFileUploadResult response: \n{}", fileGroupBatchUpload->DebugString()); } - fileGroupBatchPrepare->add_groups()->Swap(fileGroup.get()); - } - } - std::string serializedBatch; - fileGroupBatchPrepare->SerializeToString(&serializedBatch); - cpr::Url requestUrl = static_cast(this->url + "/v2/files/prepare-batch"); - cpr::Response response = cpr::Post( - cpr::Url{requestUrl}, - cpr::Body{serializedBatch}, - cpr::Header{{"Authorization", "Bearer " + token}}, - cpr::VerifySsl(verifySsl), - cpr::ProgressCallback( - [&](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, intptr_t userdata) -> bool { - (void)userdata; - (void)downloadTotal; - (void)downloadNow; - (void)uploadTotal; - (void)uploadNow; - if(stopUploadThread) { - return false; + // Upload accepted files + for (int i = 0; i < fileGroupBatchUpload->groups_size(); i++) { + auto snapData = inputSnapBatch.at(i); + auto uploadGroupResult = fileGroupBatchUpload->groups(i); + // Rejected group + if (!uploadGroupResult.has_rejected()) { + std::vector> fileUploadResponses; + for (int j = 0; j < uploadGroupResult.files_size(); j++) { + auto uploadFileResult = uploadGroupResult.files(j); + if(uploadFileResult.result_case() == proto::event::FileUploadResult::kAccepted) { + auto addedFile = snapData->event->add_associate_files(); + addedFile->set_id(uploadFileResult.accepted().id()); + fileUploadResponses.emplace_back(std::async(std::launch::async, [=]() { // TO DO: Determine & vs = + return uploadFile(std::move(snapData->fileGroup.at(j)), std::move(uploadFileResult.accepted().upload_url())); + })); + } + } + // Wait for all the reponses, indicating the finish of file uploads + bool skipGroup = false; + for (auto& responseFuture : fileUploadResponses) { + if (!responseFuture.valid() || !responseFuture.get()) { + logger::info("Failed to upload all of the files in a group, skipping this group"); + skipGroup = true; + } + } + if (skipGroup) { + continue; + } + // Once all of the files are uploaded, the event can be sent + if(eventBuffer.size() <= queueSize) { + std::lock_guard lock(eventBufferMutex); + eventBuffer.push_back(std::move(snapData->event)); + } else { + logger::warn("Event buffer is full, dropping event"); + } } - return true; - })); - if (response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { - logger::error("Failed to prepare a batch of file groups, status code: {}", response.status_code); - } - else { - logger::info("Batch of file groups has been successfully prepared"); - auto fileGroupBatchUpload = std::make_unique(); - fileGroupBatchUpload->ParseFromString(response.text); - if (logResponse) { - logger::info("BatchFileUploadResult response: \n{}", fileGroupBatchUpload->DebugString()); + } + return; } + } +} - // Upload accepted files - for (int i = 0; i < fileGroupBatchUpload->groups_size(); i++) { - auto snapData = snapBuffer.at(i); - auto uploadGroupResult = fileGroupBatchUpload->groups(i); - // Rejected group - if (!uploadGroupResult.has_rejected()) { - for (int j = 0; j < uploadGroupResult.files_size(); j++) { - auto uploadFileResult = uploadGroupResult.files(j); - if(uploadFileResult.result_case() == proto::event::FileUploadResult::kAccepted) { - // TO DO: Make this parallel upload - auto addedFile = snapData->event->add_associate_files(); - addedFile->set_id(uploadFileResult.accepted().id()); - uploadFile(snapData->fileGroup.at(j), uploadFileResult.accepted().upload_url()); +bool EventsManager::uploadFile(std::shared_ptr file, std::string url) { + logger::info("Uploading file {} to: {}", file->fileName, url); + auto header = cpr::Header(); + header["File-Size"] = file->size; + header["Content-Type"] = file->mimeType; + for (int i = 0; i < fileUploadRetryPolicy.maxAttempts; ++i) { + cpr::Response response = cpr::Put( + cpr::Url{url}, + cpr::Body{file->data}, + cpr::Header{header}, + cpr::VerifySsl(verifySsl), + cpr::ProgressCallback( + [&](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, intptr_t userdata) -> bool { + (void)userdata; + (void)downloadTotal; + (void)downloadNow; + (void)uploadTotal; + (void)uploadNow; + if(stopUploadThread) { + return false; } - } - if(eventBuffer.size() <= queueSize) { - std::lock_guard lock(eventBufferMutex); - eventBuffer.push_back(std::move(snapData->event)); - } else { - logger::warn("Event buffer is full, dropping event"); - } + return true; + })); + if(response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { + logger::error("Failed to upload file {}, status code: {}", file->fileName, response.status_code); + if (logResponse) { + logger::info("Response {}", response.text); } + auto factor = std::pow(fileUploadRetryPolicy.factor, i+1); + std::chrono::milliseconds duration = std::chrono::milliseconds(fileUploadRetryPolicy.baseDelay.count() * static_cast(factor)); + logger::info("Retrying upload of file {}, (attempt {}/{}) in {} ms", file->fileName, i+1, fileUploadRetryPolicy.maxAttempts, duration.count()); + std::this_thread::sleep_for(duration); + } else { + return true; } - - snapBuffer.clear(); } + return false; } void EventsManager::uploadEventBatch() { @@ -358,15 +426,8 @@ void EventsManager::uploadEventBatch() { logger::warn("Missing token, please set DEPTHAI_HUB_API_KEY environment variable or use setToken method"); return; } - //if(!checkConnection()) { - // TO DO: Caching is ignored for now. Fix this later - //if(cacheIfCannotSend) { - // cacheEvents(); - //} - // return; - //} - for(auto& event : eventBuffer) { - eventBatch->add_events()->Swap(event.get()); + for (size_t i = 0; i < eventBuffer.size() && i < eventsPerRequest; ++i) { + eventBatch->add_events()->Swap(eventBuffer.at(i).get()); } } std::string serializedBatch; @@ -391,6 +452,7 @@ void EventsManager::uploadEventBatch() { })); if(response.status_code != cpr::status::HTTP_OK) { logger::error("Failed to send event, status code: {}", response.status_code); + // TO DO: In case of repeated errors, caching of the events could be added if needed } else { logger::info("Event sent successfully"); if(logResponse) { @@ -398,15 +460,8 @@ void EventsManager::uploadEventBatch() { eventBatchUploadResults->ParseFromString(response.text); logger::info("BatchUploadEvents response: \n{}", eventBatchUploadResults->DebugString()); } - - // TO DO: Caching is ignored for now. Fix this later - //for(auto& eventM : eventBuffer) { - // if(!eventM->cachePath.empty() && std::filesystem::exists(eventM->cachePath)) { - // std::filesystem::remove_all(eventM->cachePath); - // } - //} - - eventBuffer.clear(); + std::lock_guard lock(eventBufferMutex); + eventBuffer.erase(eventBuffer.begin(), eventBuffer.begin() + eventBatch->events_size()); } } @@ -476,9 +531,13 @@ bool EventsManager::sendSnap(const std::string& name, return false; } snapData->event->add_tags("snap"); + if (fileGroup.size() > maxFilesPerGroup) { + logger::error("Failed to send snap, the number of files in a file group {} exceeds {}", fileGroup.size(), maxFilesPerGroup); + return false; + } for (const auto& file : fileGroup) { - if (file->size >= maximumFileSize) { - logger::error("Failed to send snap, file: {} is bigger then the configured maximum size: {}", file->fileName, maximumFileSize); + if (file->size >= maxFileSizeBytes) { + logger::error("Failed to send snap, file: {} is bigger then the configured maximum size: {}", file->fileName, maxFileSizeBytes); return false; } } @@ -558,33 +617,6 @@ bool EventsManager::validateEvent(const proto::event::Event& inputEvent) { return true; } -void EventsManager::uploadFile(const std::shared_ptr& file, const std::string& url) { - logger::info("Uploading file to: {}", url); - auto header = cpr::Header(); - header["File-Size"] = file->size; - header["Content-Type"] = file->mimeType; - cpr::Response response = cpr::Put( - cpr::Url{url}, - cpr::Body{file->data}, - cpr::Header{header}, - cpr::VerifySsl(verifySsl), - cpr::ProgressCallback( - [&](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, intptr_t userdata) -> bool { - (void)userdata; - (void)downloadTotal; - (void)downloadNow; - (void)uploadTotal; - (void)uploadNow; - if(stopUploadThread) { - return false; - } - return true; - })); - if(response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { - logger::error("Failed to upload file, status code: {}", response.status_code); - } -} - void EventsManager::cacheEvents() { /* logger::info("Caching events"); From 30d403dcd7e459dcf2a0f8627fd977e292181228 Mon Sep 17 00:00:00 2001 From: aljazdu Date: Sun, 5 Oct 2025 19:15:38 +0200 Subject: [PATCH 11/33] Updated the asynchronous groups and files upload, removed queueSize --- include/depthai/utility/EventsManager.hpp | 22 ++-- src/utility/EventsManager.cpp | 150 +++++++++++----------- 2 files changed, 86 insertions(+), 86 deletions(-) diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 77a88ac444..cab58326df 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -20,6 +20,7 @@ namespace dai { namespace proto { namespace event { class Event; +class FileUploadGroupResult; enum PrepareFileUploadClass : int; } // namespace event } // namespace proto @@ -110,12 +111,6 @@ class EventsManager { * @return void */ void setToken(const std::string& token); - /** - * Set the queue size for the amount of events that can be added and sent. By default, the queue size is set to 10 - * @param queueSize Queue size - * @return void - */ - void setQueueSize(uint64_t queuSize); /** * Set whether to log the responses from the server. By default, logResponse is set to false * @param logResponse bool @@ -163,17 +158,21 @@ class EventsManager { }; /** - * Prepare and upload files from snapBuffer in batch + * Prepare a batch of file groups from inputSnapBatch */ void uploadFileBatch(std::deque> inputSnapBatch); /** - * Upload events from eventBuffer in batch + * Upload a prepared group of files from snapData, using prepareGroupResult */ - void uploadEventBatch(); + bool uploadGroup(std::shared_ptr snapData, dai::proto::event::FileUploadGroupResult prepareGroupResult); /** - * Upload a file using the chosen url + * Upload a file from fileData using the chosen uploadUrl */ - bool uploadFile(std::shared_ptr file, std::string url); + bool uploadFile(std::shared_ptr fileData, std::string uploadUrl); + /** + * Upload events from eventBuffer in batch + */ + void uploadEventBatch(); /** * // TO DO: Add description */ @@ -199,7 +198,6 @@ class EventsManager { std::string url; std::string sourceAppId; std::string sourceAppIdentifier; - uint64_t queueSize; float publishInterval; bool logResponse; bool verifySsl; diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index b8e5ccfc60..1e5ec17164 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -18,7 +18,6 @@ namespace dai { namespace utility { using std::move; -// TO DO: Keyboard shutdown and stuff, and killing threads must be handled! FileData::FileData(const std::string& data, const std::string& fileName, const std::string& mimeType) : mimeType(mimeType), fileName(fileName), @@ -161,7 +160,6 @@ std::string FileData::calculateSHA256Checksum(const std::string& data) { EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float publishInterval) : url(std::move(url)), - queueSize(100), publishInterval(publishInterval), logResponse(false), verifySsl(true), @@ -201,8 +199,8 @@ EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float pu snapBuffer.erase(snapBuffer.begin(), snapBuffer.begin() + size); } // TO DO: Handle the clearing of these futures - uploadFileBatchFutures.emplace_back(std::async(std::launch::async, [&]() { - uploadFileBatch(std::move(snapBatch)); + uploadFileBatchFutures.emplace_back(std::async(std::launch::async, [&, inputSnapBatch = std::move(snapBatch)]() mutable { + uploadFileBatch(std::move(inputSnapBatch)); })); uploadEventBatch(); @@ -221,10 +219,7 @@ EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float pu EventsManager::~EventsManager() { stopUploadThread = true; - { - std::unique_lock lock(stopThreadConditionMutex); - eventBufferCondition.notify_one(); - } + eventBufferCondition.notify_all(); if(uploadThread && uploadThread->joinable()) { uploadThread->join(); } @@ -328,47 +323,34 @@ void EventsManager::uploadFileBatch(std::deque> inputS } else { logger::info("Batch of file groups has been successfully prepared"); - auto fileGroupBatchUpload = std::make_unique(); - fileGroupBatchUpload->ParseFromString(response.text); + auto prepareBatchResults = std::make_unique(); + prepareBatchResults->ParseFromString(response.text); if (logResponse) { - logger::info("BatchFileUploadResult response: \n{}", fileGroupBatchUpload->DebugString()); + logger::info("BatchFileUploadResult response: \n{}", prepareBatchResults->DebugString()); } - // Upload accepted files - for (int i = 0; i < fileGroupBatchUpload->groups_size(); i++) { + // Upload groups of files + std::vector> groupUploadResults; + for (int i = 0; i < prepareBatchResults->groups_size(); i++) { auto snapData = inputSnapBatch.at(i); - auto uploadGroupResult = fileGroupBatchUpload->groups(i); - // Rejected group - if (!uploadGroupResult.has_rejected()) { - std::vector> fileUploadResponses; - for (int j = 0; j < uploadGroupResult.files_size(); j++) { - auto uploadFileResult = uploadGroupResult.files(j); - if(uploadFileResult.result_case() == proto::event::FileUploadResult::kAccepted) { - auto addedFile = snapData->event->add_associate_files(); - addedFile->set_id(uploadFileResult.accepted().id()); - fileUploadResponses.emplace_back(std::async(std::launch::async, [=]() { // TO DO: Determine & vs = - return uploadFile(std::move(snapData->fileGroup.at(j)), std::move(uploadFileResult.accepted().upload_url())); - })); - } - } - // Wait for all the reponses, indicating the finish of file uploads - bool skipGroup = false; - for (auto& responseFuture : fileUploadResponses) { - if (!responseFuture.valid() || !responseFuture.get()) { - logger::info("Failed to upload all of the files in a group, skipping this group"); - skipGroup = true; - } - } - if (skipGroup) { - continue; - } - // Once all of the files are uploaded, the event can be sent - if(eventBuffer.size() <= queueSize) { - std::lock_guard lock(eventBufferMutex); - eventBuffer.push_back(std::move(snapData->event)); - } else { - logger::warn("Event buffer is full, dropping event"); - } + auto prepareGroupResult = prepareBatchResults->groups(i); + // Skip rejected groups + if (prepareGroupResult.has_rejected()) { + std::string rejectionReason = dai::proto::event::RejectedFileGroupReason_descriptor() + ->FindValueByNumber(static_cast(prepareGroupResult.rejected().reason()))->name(); + logger::info("A group has been rejected because of {}", rejectionReason); + continue; + } + // Handle groups asynchronously + groupUploadResults.emplace_back(std::async(std::launch::async, + [&, snap = std::move(snapData), group = std::move(prepareGroupResult)]() mutable { + return uploadGroup(std::move(snap), std::move(group)); + })); + } + // Wait for all of the reponses, indicating the finish of group uploads + for (auto& uploadResult : groupUploadResults) { + if (!uploadResult.valid() || !uploadResult.get()) { + logger::info("Failed to upload all of the groups in the given batch"); } } return; @@ -376,15 +358,45 @@ void EventsManager::uploadFileBatch(std::deque> inputS } } -bool EventsManager::uploadFile(std::shared_ptr file, std::string url) { - logger::info("Uploading file {} to: {}", file->fileName, url); +bool EventsManager::uploadGroup(std::shared_ptr snapData, dai::proto::event::FileUploadGroupResult prepareGroupResult) { + std::vector> fileUploadResults; + for (int i = 0; i < prepareGroupResult.files_size(); i++) { + auto prepareFileResult = prepareGroupResult.files(i); + if(prepareFileResult.result_case() == proto::event::FileUploadResult::kAccepted) { + // Add an associate file to the event + auto associateFile = snapData->event->add_associate_files(); + associateFile->set_id(prepareFileResult.accepted().id()); + // Upload files asynchronously + fileUploadResults.emplace_back(std::async(std::launch::async, + [&, fileData = std::move(snapData->fileGroup.at(i)), uploadUrl = std::move(prepareFileResult.accepted().upload_url())]() mutable { + return uploadFile(std::move(fileData), std::move(uploadUrl)); + })); + } else { + return false; + } + } + // Wait for all of the results, indicating the finish of file uploads + for (auto& uploadResult : fileUploadResults) { + if (!uploadResult.valid() || !uploadResult.get()) { + logger::info("Failed to upload all of the files in the given group"); + return false; + } + } + // Once all of the files are uploaded, the event can be sent + std::lock_guard lock(eventBufferMutex); + eventBuffer.push_back(std::move(snapData->event)); + return true; +} + +bool EventsManager::uploadFile(std::shared_ptr fileData, std::string uploadUrl) { + logger::info("Uploading file {} to: {}", fileData->fileName, uploadUrl); auto header = cpr::Header(); - header["File-Size"] = file->size; - header["Content-Type"] = file->mimeType; - for (int i = 0; i < fileUploadRetryPolicy.maxAttempts; ++i) { + header["File-Size"] = fileData->size; // TO DO: Remove this fix + header["Content-Type"] = fileData->mimeType; + for (int i = 0; i < fileUploadRetryPolicy.maxAttempts && !stopUploadThread; ++i) { cpr::Response response = cpr::Put( - cpr::Url{url}, - cpr::Body{file->data}, + cpr::Url{uploadUrl}, + cpr::Body{fileData->data}, cpr::Header{header}, cpr::VerifySsl(verifySsl), cpr::ProgressCallback( @@ -400,14 +412,19 @@ bool EventsManager::uploadFile(std::shared_ptr file, std::string url) return true; })); if(response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { - logger::error("Failed to upload file {}, status code: {}", file->fileName, response.status_code); + logger::error("Failed to upload file {}, status code: {}", fileData->fileName, response.status_code); if (logResponse) { logger::info("Response {}", response.text); } + // Apply exponential backoff auto factor = std::pow(fileUploadRetryPolicy.factor, i+1); std::chrono::milliseconds duration = std::chrono::milliseconds(fileUploadRetryPolicy.baseDelay.count() * static_cast(factor)); - logger::info("Retrying upload of file {}, (attempt {}/{}) in {} ms", file->fileName, i+1, fileUploadRetryPolicy.maxAttempts, duration.count()); - std::this_thread::sleep_for(duration); + logger::info("Retrying upload of file {}, (attempt {}/{}) in {} ms", fileData->fileName, i+1, fileUploadRetryPolicy.maxAttempts, duration.count()); + + std::unique_lock lock(stopThreadConditionMutex); + eventBufferCondition.wait_for(lock, duration, [this]() { + return stopUploadThread.load(); + }); } else { return true; } @@ -494,13 +511,8 @@ bool EventsManager::sendEvent(const std::string& name, } // Add event to eventBuffer - if(eventBuffer.size() <= queueSize) { - std::lock_guard lock(eventBufferMutex); - eventBuffer.push_back(std::move(event)); - } else { - logger::warn("Event buffer is full, dropping event"); - return false; - } + std::lock_guard lock(eventBufferMutex); + eventBuffer.push_back(std::move(event)); return true; } @@ -542,14 +554,8 @@ bool EventsManager::sendSnap(const std::string& name, } } // Add the snap to snapBuffer - // TO DO: Should a snap buffer be limited by size like eventbuffer - if(snapBuffer.size() <= queueSize) { - std::lock_guard lock(snapBufferMutex); - snapBuffer.push_back(std::move(snapData)); - } else { - logger::warn("Snap buffer is full, dropping snap"); - return false; - } + std::lock_guard lock(snapBufferMutex); + snapBuffer.push_back(std::move(snapData)); return true; } @@ -710,10 +716,6 @@ void EventsManager::setToken(const std::string& token) { this->token = token; } -void EventsManager::setQueueSize(uint64_t queueSize) { - this->queueSize = queueSize; -} - void EventsManager::setLogResponse(bool logResponse) { this->logResponse = logResponse; } From 96f3e24d80645a310c18ad8b174ef1842cd22979 Mon Sep 17 00:00:00 2001 From: aljazdu Date: Tue, 7 Oct 2025 09:37:13 +0200 Subject: [PATCH 12/33] Updated fetching of configuration limits and preparing of group batches. Source app identifiers are now set using env variables --- .../src/utility/EventsManagerBindings.cpp | 10 ---- include/depthai/utility/EventsManager.hpp | 36 ++++--------- src/utility/EventsManager.cpp | 52 +++++++++++-------- 3 files changed, 42 insertions(+), 56 deletions(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 3e1e024c99..036eeadfad 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -33,18 +33,8 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { .def(py::init<>()) .def(py::init(), py::arg("url"), py::arg("uploadCachedOnStart") = false, py::arg("publishInterval") = 10.0) .def("setUrl", &EventsManager::setUrl, py::arg("url"), DOC(dai, utility, EventsManager, setUrl)) - .def("setSourceAppId", &EventsManager::setSourceAppId, py::arg("sourceAppId"), DOC(dai, utility, EventsManager, setSourceAppId)) - .def("setSourceAppIdentifier", - &EventsManager::setSourceAppIdentifier, - py::arg("sourceAppIdentifier"), - DOC(dai, utility, EventsManager, setSourceAppIdentifier)) .def("setToken", &EventsManager::setToken, py::arg("token"), DOC(dai, utility, EventsManager, setToken)) - .def("setQueueSize", &EventsManager::setQueueSize, py::arg("queueSize"), DOC(dai, utility, EventsManager, setQueueSize)) .def("setLogResponse", &EventsManager::setLogResponse, py::arg("logResponse"), DOC(dai, utility, EventsManager, setLogResponse)) - .def("setDeviceSerialNumber", - &EventsManager::setDeviceSerialNumber, - py::arg("deviceSerialNumber"), - DOC(dai, utility, EventsManager, setDeviceSerialNumber)) .def("setVerifySsl", &EventsManager::setVerifySsl, py::arg("verifySsl"), DOC(dai, utility, EventsManager, setVerifySsl)) .def("setCacheDir", &EventsManager::setCacheDir, py::arg("cacheDir"), DOC(dai, utility, EventsManager, setCacheDir)) .def("setCacheIfCannotSend", diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index cab58326df..4ac88236b5 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -86,25 +86,12 @@ class EventsManager { const std::string& deviceSerialNo = "", const std::vector>& fileGroup = {}); - void setDeviceSerialNumber(const std::string& deviceSerialNumber); /** * Set the URL of the events service. By default, the URL is set to https://events.cloud.luxonis.com * @param url URL of the events service * @return void */ void setUrl(const std::string& url); - /** - * Set the source app ID. By default, the source app ID is taken from the environment variable AGENT_APP_ID - * @param sourceAppId Source app ID - * @return void - */ - void setSourceAppId(const std::string& sourceAppId); - /** - * Set the source app identifier. By default, the source app identifier is taken from the environment variable AGENT_APP_IDENTIFIER - * @param sourceAppIdentifier Source app identifier - * @return void - */ - void setSourceAppIdentifier(const std::string& sourceAppIdentifier); /** * Set the token for the events service. By default, the token is taken from the environment variable DEPTHAI_HUB_API_KEY * @param token Token for the events service @@ -157,6 +144,11 @@ class EventsManager { std::chrono::milliseconds baseDelay{100}; }; + /** + * Fetch the configuration limits and quotas for snaps & events + * @return bool + */ + bool fetchConfigurationLimits(); /** * Prepare a batch of file groups from inputSnapBatch */ @@ -173,14 +165,6 @@ class EventsManager { * Upload events from eventBuffer in batch */ void uploadEventBatch(); - /** - * // TO DO: Add description - */ - void cacheEvents(); - /** - * // TO DO: Add description - */ - bool checkForCachedData(); /** * Validate the input event by checking that its fields adhere to defined limitations * @param inputEvent Input event to be validated @@ -188,13 +172,15 @@ class EventsManager { */ bool validateEvent(const proto::event::Event& inputEvent); /** - * Fetch configuration limits and quotas for snaps, through the api - * @return bool + * // TO DO: Add description */ - bool fetchConfigurationLimits(); + void cacheEvents(); + /** + * // TO DO: Add description + */ + bool checkForCachedData(); std::string token; - std::string deviceSerialNumber; std::string url; std::string sourceAppId; std::string sourceAppIdentifier; diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 1e5ec17164..7b4a9dc15b 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -177,13 +177,9 @@ EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float pu uploadThread = std::make_unique([this]() { auto nextTime = std::chrono::steady_clock::now(); while(!stopUploadThread) { - // TO DO: remove - logger::info("Currently there are {} events in the buffer", eventBuffer.size()); - logger::info("Currently there are {} snaps in the buffer", snapBuffer.size()); // Hourly check for fetching configuration and limits auto currentTime = std::chrono::steady_clock::now(); if (currentTime >= nextTime) { - // TO DO: Fetching should be done for as long as needed, otherwise empty limits and config values fetchConfigurationLimits(); nextTime += std::chrono::hours(1); if (remainingStorageBytes <= warningStorageBytes) { @@ -231,6 +227,8 @@ bool EventsManager::fetchConfigurationLimits() { header["Authorization"] = "Bearer " + token; header["Content-Type"] = "application/x-protobuf"; cpr::Url requestUrl = static_cast(this->url + "/v2/api-usage"); + // Might change to infinte retrying in the future + for (int i = 0; i < fileUploadRetryPolicy.maxAttempts && !stopUploadThread; i++) { cpr::Response response = cpr::Get( cpr::Url{requestUrl}, cpr::Header{header}, @@ -249,7 +247,16 @@ bool EventsManager::fetchConfigurationLimits() { })); if(response.status_code != cpr::status::HTTP_OK) { logger::error("Failed to fetch configuration limits, status code: {}", response.status_code); - return false; + + // Apply exponential backoff + auto factor = std::pow(fileUploadRetryPolicy.factor, i+1); + std::chrono::milliseconds duration = std::chrono::milliseconds(fileUploadRetryPolicy.baseDelay.count() * static_cast(factor)); + logger::info("Retrying to fetch configuration limits, (attempt {}/{}) in {} ms", i+1, fileUploadRetryPolicy.maxAttempts, duration.count()); + + std::unique_lock lock(stopThreadConditionMutex); + eventBufferCondition.wait_for(lock, duration, [this]() { + return stopUploadThread.load(); + }); } else { logger::info("Configuration limits fetched successfully"); auto apiUsage = std::make_unique(); @@ -262,13 +269,16 @@ bool EventsManager::fetchConfigurationLimits() { remainingStorageBytes = apiUsage->files().remaining_storage_bytes(); // bytesPerHour = apiUsage->files().bytes_per_hour_rate(); uploadsPerHour = apiUsage->files().uploads_per_hour_rate(); - maxGroupsPerBatch = apiUsage->files().groups_per_allocation(); + maxGroupsPerBatch = apiUsage->files().groups_per_allocation(); // maxFilesPerGroup = apiUsage->files().files_per_group_in_allocation(); // eventsPerHour = apiUsage->events().events_per_hour_rate(); snapsPerHour = apiUsage->events().snaps_per_hour_rate(); - eventsPerRequest = apiUsage->events().events_per_request(); + eventsPerRequest = apiUsage->events().events_per_request(); // + + return true; + } } - return true; + return false; } void EventsManager::uploadFileBatch(std::deque> inputSnapBatch) { @@ -295,6 +305,7 @@ void EventsManager::uploadFileBatch(std::deque> inputS fileGroupBatchPrepare->add_groups()->Swap(fileGroup.get()); } + int retryAttempt = 0; while (!stopUploadThread) { std::string serializedBatch; fileGroupBatchPrepare->SerializeToString(&serializedBatch); @@ -318,6 +329,15 @@ void EventsManager::uploadFileBatch(std::deque> inputS })); if (response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { logger::error("Failed to prepare a batch of file groups, status code: {}", response.status_code); + // Apply exponential backoff + auto factor = std::pow(fileUploadRetryPolicy.factor, ++retryAttempt); + std::chrono::milliseconds duration = std::chrono::milliseconds(fileUploadRetryPolicy.baseDelay.count() * static_cast(factor)); + logger::info("Retrying to prepare a batch of file groups (attempt {} in {} ms)", retryAttempt, duration.count()); + + std::unique_lock lock(stopThreadConditionMutex); + eventBufferCondition.wait_for(lock, duration, [this]() { + return stopUploadThread.load(); + }); // TO DO: After a few tries, we can determine that the connection is not established. // We can then check if caching is chosen. and do it } @@ -391,7 +411,6 @@ bool EventsManager::uploadGroup(std::shared_ptr snapData, dai::proto:: bool EventsManager::uploadFile(std::shared_ptr fileData, std::string uploadUrl) { logger::info("Uploading file {} to: {}", fileData->fileName, uploadUrl); auto header = cpr::Header(); - header["File-Size"] = fileData->size; // TO DO: Remove this fix header["Content-Type"] = fileData->mimeType; for (int i = 0; i < fileUploadRetryPolicy.maxAttempts && !stopUploadThread; ++i) { cpr::Response response = cpr::Put( @@ -546,6 +565,9 @@ bool EventsManager::sendSnap(const std::string& name, if (fileGroup.size() > maxFilesPerGroup) { logger::error("Failed to send snap, the number of files in a file group {} exceeds {}", fileGroup.size(), maxFilesPerGroup); return false; + } else if (fileGroup.empty()) { + logger::error("Failed to send snap, the file group is empty"); + return false; } for (const auto& file : fileGroup) { if (file->size >= maxFileSizeBytes) { @@ -704,14 +726,6 @@ void EventsManager::setUrl(const std::string& url) { this->url = url; } -void EventsManager::setSourceAppId(const std::string& sourceAppId) { - this->sourceAppId = sourceAppId; -} - -void EventsManager::setSourceAppIdentifier(const std::string& sourceAppIdentifier) { - this->sourceAppIdentifier = sourceAppIdentifier; -} - void EventsManager::setToken(const std::string& token) { this->token = token; } @@ -720,10 +734,6 @@ void EventsManager::setLogResponse(bool logResponse) { this->logResponse = logResponse; } -void EventsManager::setDeviceSerialNumber(const std::string& deviceSerialNumber) { - this->deviceSerialNumber = deviceSerialNumber; -} - void EventsManager::setVerifySsl(bool verifySsl) { this->verifySsl = verifySsl; } From f4bca4ab0ebab170bb0715019c1b923fdd6e287b Mon Sep 17 00:00:00 2001 From: aljazdu Date: Tue, 7 Oct 2025 16:16:11 +0200 Subject: [PATCH 13/33] Updated uploadRetryPolicy --- include/depthai/utility/EventsManager.hpp | 6 ++++-- src/utility/EventsManager.cpp | 24 +++++++++++------------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 4ac88236b5..ea3e4bd022 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -40,6 +40,8 @@ class FileData { private: /** * Calculate SHA256 checksum for the given data + * @param data Data for checksum calculation + * @return checksum string */ std::string calculateSHA256Checksum(const std::string& data); @@ -138,7 +140,7 @@ class EventsManager { std::string cachePath; }; - struct FileUploadRetryPolicy { + struct UploadRetryPolicy { int maxAttempts = 10; float factor = 2.0f; std::chrono::milliseconds baseDelay{100}; @@ -210,7 +212,7 @@ class EventsManager { uint32_t snapsPerHour; uint32_t eventsPerRequest; - FileUploadRetryPolicy fileUploadRetryPolicy; + UploadRetryPolicy uploadRetryPolicy; static constexpr int eventValidationNameLength = 56; static constexpr int eventValidationMaxTags = 20; diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 7b4a9dc15b..742601a098 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -228,7 +228,7 @@ bool EventsManager::fetchConfigurationLimits() { header["Content-Type"] = "application/x-protobuf"; cpr::Url requestUrl = static_cast(this->url + "/v2/api-usage"); // Might change to infinte retrying in the future - for (int i = 0; i < fileUploadRetryPolicy.maxAttempts && !stopUploadThread; i++) { + for (int i = 0; i < uploadRetryPolicy.maxAttempts && !stopUploadThread; i++) { cpr::Response response = cpr::Get( cpr::Url{requestUrl}, cpr::Header{header}, @@ -249,9 +249,9 @@ bool EventsManager::fetchConfigurationLimits() { logger::error("Failed to fetch configuration limits, status code: {}", response.status_code); // Apply exponential backoff - auto factor = std::pow(fileUploadRetryPolicy.factor, i+1); - std::chrono::milliseconds duration = std::chrono::milliseconds(fileUploadRetryPolicy.baseDelay.count() * static_cast(factor)); - logger::info("Retrying to fetch configuration limits, (attempt {}/{}) in {} ms", i+1, fileUploadRetryPolicy.maxAttempts, duration.count()); + auto factor = std::pow(uploadRetryPolicy.factor, i+1); + std::chrono::milliseconds duration = std::chrono::milliseconds(uploadRetryPolicy.baseDelay.count() * static_cast(factor)); + logger::info("Retrying to fetch configuration limits, (attempt {}/{}) in {} ms", i+1, uploadRetryPolicy.maxAttempts, duration.count()); std::unique_lock lock(stopThreadConditionMutex); eventBufferCondition.wait_for(lock, duration, [this]() { @@ -330,8 +330,8 @@ void EventsManager::uploadFileBatch(std::deque> inputS if (response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { logger::error("Failed to prepare a batch of file groups, status code: {}", response.status_code); // Apply exponential backoff - auto factor = std::pow(fileUploadRetryPolicy.factor, ++retryAttempt); - std::chrono::milliseconds duration = std::chrono::milliseconds(fileUploadRetryPolicy.baseDelay.count() * static_cast(factor)); + auto factor = std::pow(uploadRetryPolicy.factor, ++retryAttempt); + std::chrono::milliseconds duration = std::chrono::milliseconds(uploadRetryPolicy.baseDelay.count() * static_cast(factor)); logger::info("Retrying to prepare a batch of file groups (attempt {} in {} ms)", retryAttempt, duration.count()); std::unique_lock lock(stopThreadConditionMutex); @@ -412,7 +412,7 @@ bool EventsManager::uploadFile(std::shared_ptr fileData, std::string u logger::info("Uploading file {} to: {}", fileData->fileName, uploadUrl); auto header = cpr::Header(); header["Content-Type"] = fileData->mimeType; - for (int i = 0; i < fileUploadRetryPolicy.maxAttempts && !stopUploadThread; ++i) { + for (int i = 0; i < uploadRetryPolicy.maxAttempts && !stopUploadThread; ++i) { cpr::Response response = cpr::Put( cpr::Url{uploadUrl}, cpr::Body{fileData->data}, @@ -436,9 +436,9 @@ bool EventsManager::uploadFile(std::shared_ptr fileData, std::string u logger::info("Response {}", response.text); } // Apply exponential backoff - auto factor = std::pow(fileUploadRetryPolicy.factor, i+1); - std::chrono::milliseconds duration = std::chrono::milliseconds(fileUploadRetryPolicy.baseDelay.count() * static_cast(factor)); - logger::info("Retrying upload of file {}, (attempt {}/{}) in {} ms", fileData->fileName, i+1, fileUploadRetryPolicy.maxAttempts, duration.count()); + auto factor = std::pow(uploadRetryPolicy.factor, i+1); + std::chrono::milliseconds duration = std::chrono::milliseconds(uploadRetryPolicy.baseDelay.count() * static_cast(factor)); + logger::info("Retrying upload of file {}, (attempt {}/{}) in {} ms", fileData->fileName, i+1, uploadRetryPolicy.maxAttempts, duration.count()); std::unique_lock lock(stopThreadConditionMutex); eventBufferCondition.wait_for(lock, duration, [this]() { @@ -517,7 +517,7 @@ bool EventsManager::sendEvent(const std::string& name, for(const auto& entry : extras) { extrasData->insert({entry.first, entry.second}); } - event->set_source_serial_number(deviceSerialNo.empty() ? deviceSerialNumber : deviceSerialNo); + event->set_source_serial_number(deviceSerialNo); event->set_source_app_id(sourceAppId); event->set_source_app_identifier(sourceAppIdentifier); for (const auto& file : associateFiles) { @@ -554,7 +554,7 @@ bool EventsManager::sendSnap(const std::string& name, for(const auto& entry : extras) { extrasData->insert({entry.first, entry.second}); } - snapData->event->set_source_serial_number(deviceSerialNo.empty() ? deviceSerialNumber : deviceSerialNo); + snapData->event->set_source_serial_number(deviceSerialNo); snapData->event->set_source_app_id(sourceAppId); snapData->event->set_source_app_identifier(sourceAppIdentifier); if (!validateEvent(*snapData->event)) { From c1133d4fefa598fd051e29fcf28392f6a3d614ef Mon Sep 17 00:00:00 2001 From: aljazdu Date: Wed, 8 Oct 2025 07:31:07 +0200 Subject: [PATCH 14/33] Code cleanup --- include/depthai/utility/EventsManager.hpp | 1 + src/utility/EventsManager.cpp | 66 +++++++++++------------ 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index ea3e4bd022..d6745f5060 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -113,6 +113,7 @@ class EventsManager { */ void setVerifySsl(bool verifySsl); + // TO DO: Should be private? /** * Upload cached data to the events service * @return void diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 742601a098..02bfb38271 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -229,24 +229,24 @@ bool EventsManager::fetchConfigurationLimits() { cpr::Url requestUrl = static_cast(this->url + "/v2/api-usage"); // Might change to infinte retrying in the future for (int i = 0; i < uploadRetryPolicy.maxAttempts && !stopUploadThread; i++) { - cpr::Response response = cpr::Get( - cpr::Url{requestUrl}, - cpr::Header{header}, - cpr::VerifySsl(verifySsl), - cpr::ProgressCallback( - [&](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, intptr_t userdata) -> bool { - (void)userdata; - (void)downloadTotal; - (void)downloadNow; - (void)uploadTotal; - (void)uploadNow; - if(stopUploadThread) { - return false; - } - return true; - })); - if(response.status_code != cpr::status::HTTP_OK) { - logger::error("Failed to fetch configuration limits, status code: {}", response.status_code); + cpr::Response response = cpr::Get( + cpr::Url{requestUrl}, + cpr::Header{header}, + cpr::VerifySsl(verifySsl), + cpr::ProgressCallback( + [&](cpr::cpr_off_t downloadTotal, cpr::cpr_off_t downloadNow, cpr::cpr_off_t uploadTotal, cpr::cpr_off_t uploadNow, intptr_t userdata) -> bool { + (void)userdata; + (void)downloadTotal; + (void)downloadNow; + (void)uploadTotal; + (void)uploadNow; + if(stopUploadThread) { + return false; + } + return true; + })); + if(response.status_code != cpr::status::HTTP_OK) { + logger::error("Failed to fetch configuration limits, status code: {}", response.status_code); // Apply exponential backoff auto factor = std::pow(uploadRetryPolicy.factor, i+1); @@ -257,22 +257,22 @@ bool EventsManager::fetchConfigurationLimits() { eventBufferCondition.wait_for(lock, duration, [this]() { return stopUploadThread.load(); }); - } else { - logger::info("Configuration limits fetched successfully"); - auto apiUsage = std::make_unique(); - apiUsage->ParseFromString(response.text); - if(logResponse) { - logger::info("ApiUsage response: \n{}", apiUsage->DebugString()); - } - // TO DO: Use this data - maxFileSizeBytes = apiUsage->files().max_file_size_bytes(); // - remainingStorageBytes = apiUsage->files().remaining_storage_bytes(); // - bytesPerHour = apiUsage->files().bytes_per_hour_rate(); - uploadsPerHour = apiUsage->files().uploads_per_hour_rate(); + } else { + logger::info("Configuration limits fetched successfully"); + auto apiUsage = std::make_unique(); + apiUsage->ParseFromString(response.text); + if(logResponse) { + logger::info("ApiUsage response: \n{}", apiUsage->DebugString()); + } + // TO DO: Use this data + maxFileSizeBytes = apiUsage->files().max_file_size_bytes(); // + remainingStorageBytes = apiUsage->files().remaining_storage_bytes(); // + bytesPerHour = apiUsage->files().bytes_per_hour_rate(); + uploadsPerHour = apiUsage->files().uploads_per_hour_rate(); maxGroupsPerBatch = apiUsage->files().groups_per_allocation(); // - maxFilesPerGroup = apiUsage->files().files_per_group_in_allocation(); // - eventsPerHour = apiUsage->events().events_per_hour_rate(); - snapsPerHour = apiUsage->events().snaps_per_hour_rate(); + maxFilesPerGroup = apiUsage->files().files_per_group_in_allocation(); // + eventsPerHour = apiUsage->events().events_per_hour_rate(); + snapsPerHour = apiUsage->events().snaps_per_hour_rate(); eventsPerRequest = apiUsage->events().events_per_request(); // return true; From 6c6b5cff48a994363e6a273052779df7a7488d57 Mon Sep 17 00:00:00 2001 From: aljazdu Date: Wed, 8 Oct 2025 09:59:06 +0200 Subject: [PATCH 15/33] Updated events python example --- examples/python/Events/events.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/python/Events/events.py b/examples/python/Events/events.py index eb6cf16ab8..34b14f451d 100644 --- a/examples/python/Events/events.py +++ b/examples/python/Events/events.py @@ -14,7 +14,6 @@ # Enter your hubs api-key eventMan = dai.EventsManager() - eventMan.setUrl("https://events.cloud-stg.luxonis.com") eventMan.setToken("") pipeline.start() From 77debeb65b3ea4dab0605c8f4d6481131ebd8b23 Mon Sep 17 00:00:00 2001 From: AljazD Date: Thu, 9 Oct 2025 11:50:41 +0200 Subject: [PATCH 16/33] Updated events examples, events proto, pythong bindings, ImgDetections, added FileGroup --- .../datatype/ImgDetectionsBindings.cpp | 1 + .../src/utility/EventsManagerBindings.cpp | 39 ++++++-- examples/cpp/Events/events.cpp | 97 ++++++++++++++----- examples/python/Events/events.py | 84 +++++++++++++--- .../pipeline/datatype/ImgDetections.hpp | 2 + include/depthai/utility/EventsManager.hpp | 30 +++++- protos/Event.proto | 5 + src/pipeline/datatype/ImgDetections.cpp | 4 + src/utility/EventsManager.cpp | 89 +++++++++++++---- 9 files changed, 282 insertions(+), 69 deletions(-) diff --git a/bindings/python/src/pipeline/datatype/ImgDetectionsBindings.cpp b/bindings/python/src/pipeline/datatype/ImgDetectionsBindings.cpp index da33b03e88..5d0ef8a2c8 100644 --- a/bindings/python/src/pipeline/datatype/ImgDetectionsBindings.cpp +++ b/bindings/python/src/pipeline/datatype/ImgDetectionsBindings.cpp @@ -82,6 +82,7 @@ void bind_imgdetections(pybind11::module& m, void* pCallstack) { .def("getSequenceNum", &ImgDetections::Buffer::getSequenceNum, DOC(dai, Buffer, getSequenceNum)) .def("getTransformation", [](ImgDetections& msg) { return msg.transformation; }) .def("setTransformation", [](ImgDetections& msg, const std::optional& transformation) { msg.transformation = transformation; }) + .def("addDetection", &ImgDetections::addDetection, DOC(dai, ImgDetections, addDetection)) // .def("setTimestamp", &ImgDetections::setTimestamp, DOC(dai, Buffer, setTimestamp)) // .def("setTimestampDevice", &ImgDetections::setTimestampDevice, DOC(dai, Buffer, setTimestampDevice)) // .def("setSequenceNum", &ImgDetections::setSequenceNum, DOC(dai, ImgDetections, setSequenceNum)) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 036eeadfad..6ef3fb050f 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -21,13 +21,36 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { #ifdef DEPTHAI_ENABLE_EVENTS_MANAGER using namespace dai::utility; - py::class_>(m, "FileData") - .def(py::init(), py::arg("data"), py::arg("fileName"), py::arg("mimeType")) - .def(py::init(), py::arg("filePath"), py::arg("fileName")) - .def(py::init&, std::string>(), py::arg("imgFrame"), py::arg("fileName")) - .def(py::init&, std::string>(), py::arg("encodedFrame"), py::arg("fileName")) - .def(py::init&, std::string>(), py::arg("nnData"), py::arg("fileName")) - .def(py::init&, std::string>(), py::arg("imgDetections"), py::arg("fileName")); + py::class_>(m, "FileGroup") + .def(py::init<>()) + .def("addFile", + static_cast(&FileGroup::addFile), + py::arg("data"), + py::arg("fileName"), + py::arg("mimeType"), + DOC(dai, utility, FileGroup, addFile)) + .def("addFile", static_cast(&FileGroup::addFile), + py::arg("filePath"), py::arg("fileName"), DOC(dai, utility, FileGroup, addFile)) + .def("addFile", static_cast&, std::string)>(&FileGroup::addFile), + py::arg("imgFrame"), py::arg("fileName"), DOC(dai, utility, FileGroup, addFile)) + .def("addFile", static_cast&, std::string)>(&FileGroup::addFile), + py::arg("encodedFrame"), py::arg("fileName"), DOC(dai, utility, FileGroup, addFile)) + .def("addFile", static_cast&, std::string)>(&FileGroup::addFile), + py::arg("nnData"), py::arg("fileName"), DOC(dai, utility, FileGroup, addFile)) + .def("addFile", static_cast&, std::string)>(&FileGroup::addFile), + py::arg("imgDetections"), py::arg("fileName"), DOC(dai, utility, FileGroup, addFile)) + .def("addImageDetectionsPair", + static_cast&, const std::shared_ptr&, std::string)>(&FileGroup::addImageDetectionsPair), + py::arg("imgFrame"), + py::arg("imgDetections"), + py::arg("fileName"), + DOC(dai, utility, FileGroup, addImageDetectionsPair)) + .def("addImageDetectionsPair", + static_cast&, const std::shared_ptr&, std::string)>(&FileGroup::addImageDetectionsPair), + py::arg("encodedFrame"), + py::arg("imgDetections"), + py::arg("fileName"), + DOC(dai, utility, FileGroup, addImageDetectionsPair)); py::class_(m, "EventsManager") .def(py::init<>()) @@ -56,7 +79,7 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { py::arg("tags") = std::vector(), py::arg("extras") = std::unordered_map(), py::arg("deviceSerialNo") = "", - py::arg("fileGroup") = std::vector>(), + py::arg("fileGroup") = std::shared_ptr(), DOC(dai, utility, EventsManager, sendSnap)); #endif } diff --git a/examples/cpp/Events/events.cpp b/examples/cpp/Events/events.cpp index c962f286fd..4a7cbacd6e 100644 --- a/examples/cpp/Events/events.cpp +++ b/examples/cpp/Events/events.cpp @@ -1,48 +1,101 @@ #include #include #include +#include #include "depthai/depthai.hpp" #include "depthai/utility/EventsManager.hpp" -int main(int argc, char* argv[]) { + +// Helper function to normalize frame coordinates +cv::Rect frameNorm(const cv::Mat& frame, const dai::Point2f& topLeft, const dai::Point2f& bottomRight) { + float width = frame.cols, height = frame.rows; + return cv::Rect(cv::Point(topLeft.x * width, topLeft.y * height), cv::Point(bottomRight.x * width, bottomRight.y * height)); +} + +int main() { dai::Pipeline pipeline(true); + // Enter you hub team's api-key auto eventsManager = std::make_shared(); - - // Enter your hubs api-key - eventsManager->setUrl("https://events.cloud-stg.luxonis.com"); eventsManager->setToken(""); + eventsManager->setLogResponse(false); - // Color camera node auto camRgb = pipeline.create()->build(); - auto* preview = camRgb->requestOutput(std::make_pair(256, 256)); + auto detectionNetwork = pipeline.create(); - auto previewQ = preview->createOutputQueue(); + dai::NNModelDescription modelDescription; + modelDescription.model = "yolov6-nano"; + detectionNetwork->build(camRgb, modelDescription); + auto labelMap = detectionNetwork->getClasses(); - pipeline.start(); + // Create output queues + auto qRgb = detectionNetwork->passthrough.createOutputQueue(); + auto qDet = detectionNetwork->out.createOutputQueue(); - std::vector> data; + pipeline.start(); + + int counter = 0; while(pipeline.isRunning()) { - auto rgb = previewQ->get(); + auto inRgb = qRgb->get(); + auto inDet = qDet->get(); + if (inRgb == nullptr || inDet == nullptr) { + continue; + } + + // Display the video stream and detections + cv::Mat frame = inRgb->getCvFrame(); + if(!frame.empty()) { + // Display detections + for(const auto& detection : inDet->detections) { + auto bbox = frameNorm(frame, dai::Point2f(detection.xmin, detection.ymin), dai::Point2f(detection.xmax, detection.ymax)); - std::string str = "image_"; - std::stringstream ss; - ss << str << data.size(); + // Draw label + cv::putText(frame, labelMap.value()[detection.label], cv::Point(bbox.x + 10, bbox.y + 20), cv::FONT_HERSHEY_TRIPLEX, 0.5, cv::Scalar(255,255,255)); - auto rgbData = std::make_shared(rgb, ss.str()); - data.emplace_back(rgbData); + // Draw confidence + cv::putText(frame, + std::to_string(static_cast(detection.confidence * 100)) + "%", + cv::Point(bbox.x + 10, bbox.y + 40), + cv::FONT_HERSHEY_TRIPLEX, + 0.5, + cv::Scalar(255,255,255)); - if (data.size() == 5) - { - eventsManager->sendSnap("ImgFrame", {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, "", data); - data.clear(); - std::this_thread::sleep_for(std::chrono::milliseconds(3000)); + // Draw rectangle + cv::rectangle(frame, bbox, cv::Scalar(255, 0, 0), 2); + } + + // Show the frame + cv::imshow("rgb", frame); + } + + // Suppose we are only interested in the detections with confidence between 50% and 60% + auto borderDetections = std::make_shared(); + for (const auto& detection : inDet->detections) { + if (detection.confidence > 0.5f && detection.confidence < 0.6f) { + borderDetections->detections.emplace_back(detection); + } + } + + // Are there any border detections + if (borderDetections->detections.size() > 0) { + std::string fileName = "ImageDetection_"; + std::stringstream ss; + ss << fileName << counter; + + std::shared_ptr fileGroup = std::make_shared(); + fileGroup->addImageDetectionsPair(inRgb, borderDetections, ss.str()); + eventsManager->sendSnap("ImageDetection", {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, "", fileGroup); + + counter++; } - std::this_thread::sleep_for(std::chrono::milliseconds(400)); + if(cv::waitKey(1) == 'q') { + break; + } } + return EXIT_SUCCESS; -} +} \ No newline at end of file diff --git a/examples/python/Events/events.py b/examples/python/Events/events.py index 34b14f451d..0b8bdb8928 100644 --- a/examples/python/Events/events.py +++ b/examples/python/Events/events.py @@ -8,29 +8,81 @@ # Create pipeline with dai.Pipeline() as pipeline: - # Define sources and outputs - camRgb = pipeline.create(dai.node.Camera).build() - qRgb = camRgb.requestOutput((256,256)).createOutputQueue() - - # Enter your hubs api-key + # Enter you hub team's api-key eventMan = dai.EventsManager() eventMan.setToken("") + eventMan.setLogResponse(False) + + cameraNode = pipeline.create(dai.node.Camera).build() + detectionNetwork = pipeline.create(dai.node.DetectionNetwork).build(cameraNode, dai.NNModelDescription("yolov6-nano")) + labelMap = detectionNetwork.getClasses() + + # Create output queues + qRgb = detectionNetwork.passthrough.createOutputQueue() + qDet = detectionNetwork.out.createOutputQueue() pipeline.start() - data = [] + # nn data, being the bounding box locations, are in <0..1> range - they need to be normalized with frame width/height + def frameNorm(frame, bbox): + normVals = np.full(len(bbox), frame.shape[0]) + normVals[::2] = frame.shape[1] + return (np.clip(np.array(bbox), 0, 1) * normVals).astype(int) + + + counter = 0 while pipeline.isRunning(): inRgb: dai.ImgFrame = qRgb.get() + inDet: dai.ImgDetections = qDet.get() + if inRgb is None or inDet is None: + continue - name = f"image_{len(data)}" - if inRgb is not None: - rgbData = dai.FileData(inRgb, name) - data.append(rgbData) + # Display the video stream and detections + color = (255, 0, 0) + frame = inRgb.getCvFrame() + if frame is not None: + for detection in inDet.detections: + bbox = frameNorm( + frame, + (detection.xmin, detection.ymin, detection.xmax, detection.ymax), + ) + cv2.putText( + frame, + labelMap[detection.label], + (bbox[0] + 10, bbox[1] + 20), + cv2.FONT_HERSHEY_TRIPLEX, + 0.5, + 255, + ) + cv2.putText( + frame, + f"{int(detection.confidence * 100)}%", + (bbox[0] + 10, bbox[1] + 40), + cv2.FONT_HERSHEY_TRIPLEX, + 0.5, + 255, + ) + cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), color, 2) + # Show the frame + cv2.imshow("rgb", frame) - if len(data) == 5: - eventMan.sendSnap("ImgFrame", ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1"}, "", data) - data.clear() - time.sleep(3) - - time.sleep(0.4) \ No newline at end of file + # Suppose we are only interested in the detections with confidence between 50% and 60% + borderDetections = dai.ImgDetections() + for detection in inDet.detections: + if detection.confidence > 0.5 and detection.confidence < 0.6: + borderDetections.addDetection(detection) + + # Are there any border detections + if len(borderDetections.detections) > 0: + fileName = f"ImageDetection_{counter}" + + fileGroup = dai.FileGroup() + fileGroup.addImageDetectionsPair(inRgb, borderDetections, fileName) + eventMan.sendSnap("ImageDetection", ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "", fileGroup) + + counter += 1 + + if cv2.waitKey(1) == ord("q"): + pipeline.stop() + break \ No newline at end of file diff --git a/include/depthai/pipeline/datatype/ImgDetections.hpp b/include/depthai/pipeline/datatype/ImgDetections.hpp index 5c8e003c62..9d0ff6e7b2 100644 --- a/include/depthai/pipeline/datatype/ImgDetections.hpp +++ b/include/depthai/pipeline/datatype/ImgDetections.hpp @@ -36,6 +36,8 @@ class ImgDetections : public Buffer, public ProtoSerializable { std::vector detections; std::optional transformation; + void addDetection(const ImgDetection& detection); + void serialize(std::vector& metadata, DatatypeEnum& datatype) const override { metadata = utility::serialize(*this); datatype = DatatypeEnum::ImgDetections; diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index d6745f5060..e207700a31 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -29,8 +29,8 @@ namespace utility { class FileData { public: - FileData(const std::string& data, const std::string& fileName, const std::string& mimeType); - explicit FileData(const std::string& filePath, std::string fileName); + FileData(std::string data, std::string fileName, std::string mimeType); + explicit FileData(std::string filePath, std::string fileName); explicit FileData(const std::shared_ptr& imgFrame, std::string fileName); explicit FileData(const std::shared_ptr& encodedFrame, std::string fileName); explicit FileData(const std::shared_ptr& nnData, std::string fileName); @@ -54,6 +54,26 @@ class FileData { friend class EventsManager; }; +class FileGroup { + public: + void addFile(std::string data, std::string fileName, std::string mimeType); + void addFile(std::string filePath, std::string fileName); + void addFile(const std::shared_ptr& imgFrame, std::string fileName); + void addFile(const std::shared_ptr& encodedFrame, std::string fileName); + void addFile(const std::shared_ptr& nnData, std::string fileName); + void addFile(const std::shared_ptr& imgDetections, std::string fileName); + void addImageDetectionsPair(const std::shared_ptr& imgFrame, + const std::shared_ptr& imgDetections, + std::string fileName); + void addImageDetectionsPair(const std::shared_ptr& encodedFrame, + const std::shared_ptr& imgDetections, + std::string fileName); + + private: + std::vector> fileData; + friend class EventsManager; +}; + class EventsManager { public: explicit EventsManager(std::string url = "https://events.cloud.luxonis.com", bool uploadCachedOnStart = false, float publishInterval = 10.0); @@ -79,14 +99,14 @@ class EventsManager { * @param tags List of tags to send * @param extras Extra data to send * @param deviceSerialNo Device serial number - * @param fileGroup List of FileData objects to send + * @param fileGroup FileGroup containing FileData objects to send * @return bool */ bool sendSnap(const std::string& name, const std::vector& tags = {}, const std::unordered_map& extras = {}, const std::string& deviceSerialNo = "", - const std::vector>& fileGroup = {}); + const std::shared_ptr fileGroup = nullptr); /** * Set the URL of the events service. By default, the URL is set to https://events.cloud.luxonis.com @@ -137,7 +157,7 @@ class EventsManager { private: struct SnapData { std::shared_ptr event; - std::vector> fileGroup; + std::shared_ptr fileGroup; std::string cachePath; }; diff --git a/protos/Event.proto b/protos/Event.proto index 6ecb55f843..ba5db555af 100644 --- a/protos/Event.proto +++ b/protos/Event.proto @@ -1,5 +1,6 @@ syntax = "proto3"; +import "ImgDetections.proto"; package dai.proto.event; message BatchPrepareFileUpload { @@ -132,4 +133,8 @@ message EventLimits { message ApiUsage { FileLimits files = 1; EventLimits events = 2; +} + +message SnapAnnotations { + optional img_detections.ImgDetections detections = 1; } \ No newline at end of file diff --git a/src/pipeline/datatype/ImgDetections.cpp b/src/pipeline/datatype/ImgDetections.cpp index 9a980bf109..43ab2c0b53 100644 --- a/src/pipeline/datatype/ImgDetections.cpp +++ b/src/pipeline/datatype/ImgDetections.cpp @@ -6,6 +6,10 @@ namespace dai { +void ImgDetections::addDetection(const ImgDetection& detection) { + detections.push_back(detection); +} + #ifdef DEPTHAI_ENABLE_PROTOBUF ProtoSerializable::SchemaPair ImgDetections::serializeSchema() const { return utility::serializeSchema(utility::getProtoMessage(this)); diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 02bfb38271..3a5bc54f1f 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -18,15 +18,56 @@ namespace dai { namespace utility { using std::move; -FileData::FileData(const std::string& data, const std::string& fileName, const std::string& mimeType) - : mimeType(mimeType), - fileName(fileName), - data(data), + +template +void addToFileData(std::vector>& container, Args&&... args) { + container.emplace_back(std::make_shared(std::forward(args)...)); +} + +void FileGroup::addFile(std::string data, std::string fileName, std::string mimeType) { + addToFileData(fileData, std::move(data), std::move(fileName), std::move(mimeType)); +} + +void FileGroup::addFile(std::string filePath, std::string fileName) { + addToFileData(fileData, std::move(filePath), std::move(fileName)); +} + +void FileGroup::addFile(const std::shared_ptr& imgFrame, std::string fileName) { + addToFileData(fileData, imgFrame, std::move(fileName)); +} + +void FileGroup::addFile(const std::shared_ptr& encodedFrame, std::string fileName) { + addToFileData(fileData, encodedFrame, std::move(fileName)); +} + +void FileGroup::addFile(const std::shared_ptr& nnData, std::string fileName) { + addToFileData(fileData, nnData, std::move(fileName)); +} + +void FileGroup::addFile(const std::shared_ptr& imgDetections, std::string fileName) { + addToFileData(fileData, imgDetections, std::move(fileName)); +} + +void FileGroup::addImageDetectionsPair(const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections, std::string fileName) { + addToFileData(fileData, imgFrame, std::move(fileName)); + addToFileData(fileData, imgDetections, std::move(fileName)); +} + +void FileGroup::addImageDetectionsPair(const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections, std::string fileName) { + addToFileData(fileData, encodedFrame, std::move(fileName)); + addToFileData(fileData, imgDetections, std::move(fileName)); +} + + +FileData::FileData(std::string data, std::string fileName, std::string mimeType) + : mimeType(std::move(mimeType)), + fileName(std::move(fileName)), + data(std::move(data)), size(data.size()), checksum(calculateSHA256Checksum(data)), classification(proto::event::PrepareFileUploadClass::UNKNOWN_FILE) {} -FileData::FileData(const std::string& filePath, std::string fileName) +FileData::FileData(std::string filePath, std::string fileName) : fileName(std::move(fileName)) { static const std::unordered_map mimeTypeExtensionMap = { {".html", "text/html"}, @@ -109,12 +150,24 @@ FileData::FileData(const std::shared_ptr& nnData, std::string fileName) } FileData::FileData(const std::shared_ptr& imgDetections, std::string fileName) - : mimeType("application/x-protobuf; proto=ImgDetections"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::ANNOTATION) { - // Serialize ImgDetections - std::vector imgDetectionsSerialized = imgDetections->serializeProto(); - std::stringstream ss; - ss.write((const char*)imgDetectionsSerialized.data(), imgDetectionsSerialized.size()); - data = ss.str(); + : mimeType("application/x-protobuf; proto=SnapAnnotation"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::ANNOTATION) { + // Serialize imgDetections object, add it to SnapAnnotation proto + proto::event::SnapAnnotations snapAnnotation; + proto::img_detections::ImgDetections imgDetectionsProto; + + if (imgDetections) { + std::vector imgDetectionsSerialized = imgDetections->serializeProto(); + if (imgDetectionsProto.ParseFromArray(imgDetectionsSerialized.data(), imgDetectionsSerialized.size())) { + *snapAnnotation.mutable_detections() = imgDetectionsProto; + } else { + logger::error("Failed to parse ImgDetections proto from serialized bytes"); + return; + } + } + if (!snapAnnotation.SerializeToString(&data)) { + logger::error("Failed to serialize SnapAnnotations proto object to string"); + return; + } size = data.size(); checksum = calculateSHA256Checksum(data); } @@ -294,7 +347,7 @@ void EventsManager::uploadFileBatch(std::deque> inputS // Fill the batch with the groups from inputSnapBatch and their corresponding files for (size_t i = 0; i < inputSnapBatch.size(); ++i) { auto fileGroup = std::make_unique(); - for (auto& file : inputSnapBatch.at(i)->fileGroup) { + for (auto& file : inputSnapBatch.at(i)->fileGroup->fileData) { auto addedFile = fileGroup->add_files(); addedFile->set_checksum(file->checksum); addedFile->set_mime_type(file->mimeType); @@ -388,7 +441,7 @@ bool EventsManager::uploadGroup(std::shared_ptr snapData, dai::proto:: associateFile->set_id(prepareFileResult.accepted().id()); // Upload files asynchronously fileUploadResults.emplace_back(std::async(std::launch::async, - [&, fileData = std::move(snapData->fileGroup.at(i)), uploadUrl = std::move(prepareFileResult.accepted().upload_url())]() mutable { + [&, fileData = std::move(snapData->fileGroup->fileData.at(i)), uploadUrl = std::move(prepareFileResult.accepted().upload_url())]() mutable { return uploadFile(std::move(fileData), std::move(uploadUrl)); })); } else { @@ -539,7 +592,7 @@ bool EventsManager::sendSnap(const std::string& name, const std::vector& tags, const std::unordered_map& extras, const std::string& deviceSerialNo, - const std::vector>& fileGroup) { + const std::shared_ptr fileGroup) { // Prepare snapData auto snapData = std::make_unique(); snapData->fileGroup = fileGroup; @@ -562,14 +615,14 @@ bool EventsManager::sendSnap(const std::string& name, return false; } snapData->event->add_tags("snap"); - if (fileGroup.size() > maxFilesPerGroup) { - logger::error("Failed to send snap, the number of files in a file group {} exceeds {}", fileGroup.size(), maxFilesPerGroup); + if (fileGroup->fileData.size() > maxFilesPerGroup) { + logger::error("Failed to send snap, the number of files in a file group {} exceeds {}", fileGroup->fileData.size(), maxFilesPerGroup); return false; - } else if (fileGroup.empty()) { + } else if (fileGroup->fileData.empty()) { logger::error("Failed to send snap, the file group is empty"); return false; } - for (const auto& file : fileGroup) { + for (const auto& file : fileGroup->fileData) { if (file->size >= maxFileSizeBytes) { logger::error("Failed to send snap, file: {} is bigger then the configured maximum size: {}", file->fileName, maxFileSizeBytes); return false; From 81466731dec19acc2f436f2b8a163fab61d6f40d Mon Sep 17 00:00:00 2001 From: AljazD Date: Thu, 9 Oct 2025 13:18:18 +0200 Subject: [PATCH 17/33] Added missing clear for FileGroup class --- bindings/python/src/utility/EventsManagerBindings.cpp | 1 + examples/cpp/Events/events.cpp | 3 ++- examples/python/Events/events.py | 3 ++- include/depthai/utility/EventsManager.hpp | 1 + src/utility/EventsManager.cpp | 4 ++++ 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 6ef3fb050f..f5c249e0ee 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -23,6 +23,7 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { using namespace dai::utility; py::class_>(m, "FileGroup") .def(py::init<>()) + .def("clearFiles", &FileGroup::clearFiles, DOC(dai, utility, FileGroup, clearFiles)) .def("addFile", static_cast(&FileGroup::addFile), py::arg("data"), diff --git a/examples/cpp/Events/events.cpp b/examples/cpp/Events/events.cpp index 4a7cbacd6e..0cfa8a4d24 100644 --- a/examples/cpp/Events/events.cpp +++ b/examples/cpp/Events/events.cpp @@ -20,6 +20,7 @@ int main() { auto eventsManager = std::make_shared(); eventsManager->setToken(""); eventsManager->setLogResponse(false); + auto fileGroup = std::make_shared(); auto camRgb = pipeline.create()->build(); auto detectionNetwork = pipeline.create(); @@ -84,7 +85,7 @@ int main() { std::stringstream ss; ss << fileName << counter; - std::shared_ptr fileGroup = std::make_shared(); + fileGroup->clearFiles(); fileGroup->addImageDetectionsPair(inRgb, borderDetections, ss.str()); eventsManager->sendSnap("ImageDetection", {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, "", fileGroup); diff --git a/examples/python/Events/events.py b/examples/python/Events/events.py index 0b8bdb8928..6cd8561be7 100644 --- a/examples/python/Events/events.py +++ b/examples/python/Events/events.py @@ -12,6 +12,7 @@ eventMan = dai.EventsManager() eventMan.setToken("") eventMan.setLogResponse(False) + fileGroup = dai.FileGroup() cameraNode = pipeline.create(dai.node.Camera).build() detectionNetwork = pipeline.create(dai.node.DetectionNetwork).build(cameraNode, dai.NNModelDescription("yolov6-nano")) @@ -77,7 +78,7 @@ def frameNorm(frame, bbox): if len(borderDetections.detections) > 0: fileName = f"ImageDetection_{counter}" - fileGroup = dai.FileGroup() + fileGroup.clearFiles(); fileGroup.addImageDetectionsPair(inRgb, borderDetections, fileName) eventMan.sendSnap("ImageDetection", ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "", fileGroup) diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index e207700a31..78fe93724b 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -56,6 +56,7 @@ class FileData { class FileGroup { public: + void clearFiles(); void addFile(std::string data, std::string fileName, std::string mimeType); void addFile(std::string filePath, std::string fileName); void addFile(const std::shared_ptr& imgFrame, std::string fileName); diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 3a5bc54f1f..7c0a806477 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -24,6 +24,10 @@ void addToFileData(std::vector>& container, Args&&... container.emplace_back(std::make_shared(std::forward(args)...)); } +void FileGroup::clearFiles() { + fileData.clear(); +} + void FileGroup::addFile(std::string data, std::string fileName, std::string mimeType) { addToFileData(fileData, std::move(data), std::move(fileName), std::move(mimeType)); } From cb4e7bdafa7532de68fd3b98a5895e31908a3b1b Mon Sep 17 00:00:00 2001 From: AljazD Date: Thu, 9 Oct 2025 14:08:07 +0200 Subject: [PATCH 18/33] Reverted ImgDetections changes, updated event example --- .../src/pipeline/datatype/ImgDetectionsBindings.cpp | 1 - examples/python/Events/events.py | 8 +++++--- include/depthai/pipeline/datatype/ImgDetections.hpp | 2 -- src/pipeline/datatype/ImgDetections.cpp | 4 ---- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/bindings/python/src/pipeline/datatype/ImgDetectionsBindings.cpp b/bindings/python/src/pipeline/datatype/ImgDetectionsBindings.cpp index 5d0ef8a2c8..da33b03e88 100644 --- a/bindings/python/src/pipeline/datatype/ImgDetectionsBindings.cpp +++ b/bindings/python/src/pipeline/datatype/ImgDetectionsBindings.cpp @@ -82,7 +82,6 @@ void bind_imgdetections(pybind11::module& m, void* pCallstack) { .def("getSequenceNum", &ImgDetections::Buffer::getSequenceNum, DOC(dai, Buffer, getSequenceNum)) .def("getTransformation", [](ImgDetections& msg) { return msg.transformation; }) .def("setTransformation", [](ImgDetections& msg, const std::optional& transformation) { msg.transformation = transformation; }) - .def("addDetection", &ImgDetections::addDetection, DOC(dai, ImgDetections, addDetection)) // .def("setTimestamp", &ImgDetections::setTimestamp, DOC(dai, Buffer, setTimestamp)) // .def("setTimestampDevice", &ImgDetections::setTimestampDevice, DOC(dai, Buffer, setTimestampDevice)) // .def("setSequenceNum", &ImgDetections::setSequenceNum, DOC(dai, ImgDetections, setSequenceNum)) diff --git a/examples/python/Events/events.py b/examples/python/Events/events.py index 6cd8561be7..f42d91766d 100644 --- a/examples/python/Events/events.py +++ b/examples/python/Events/events.py @@ -69,13 +69,15 @@ def frameNorm(frame, bbox): cv2.imshow("rgb", frame) # Suppose we are only interested in the detections with confidence between 50% and 60% - borderDetections = dai.ImgDetections() + borderDetectionsList = [] for detection in inDet.detections: if detection.confidence > 0.5 and detection.confidence < 0.6: - borderDetections.addDetection(detection) + borderDetectionsList.append(detection) # Are there any border detections - if len(borderDetections.detections) > 0: + if len(borderDetectionsList) > 0: + borderDetections = dai.ImgDetections() + borderDetections.detections = borderDetectionsList fileName = f"ImageDetection_{counter}" fileGroup.clearFiles(); diff --git a/include/depthai/pipeline/datatype/ImgDetections.hpp b/include/depthai/pipeline/datatype/ImgDetections.hpp index 9d0ff6e7b2..5c8e003c62 100644 --- a/include/depthai/pipeline/datatype/ImgDetections.hpp +++ b/include/depthai/pipeline/datatype/ImgDetections.hpp @@ -36,8 +36,6 @@ class ImgDetections : public Buffer, public ProtoSerializable { std::vector detections; std::optional transformation; - void addDetection(const ImgDetection& detection); - void serialize(std::vector& metadata, DatatypeEnum& datatype) const override { metadata = utility::serialize(*this); datatype = DatatypeEnum::ImgDetections; diff --git a/src/pipeline/datatype/ImgDetections.cpp b/src/pipeline/datatype/ImgDetections.cpp index 43ab2c0b53..9a980bf109 100644 --- a/src/pipeline/datatype/ImgDetections.cpp +++ b/src/pipeline/datatype/ImgDetections.cpp @@ -6,10 +6,6 @@ namespace dai { -void ImgDetections::addDetection(const ImgDetection& detection) { - detections.push_back(detection); -} - #ifdef DEPTHAI_ENABLE_PROTOBUF ProtoSerializable::SchemaPair ImgDetections::serializeSchema() const { return utility::serializeSchema(utility::getProtoMessage(this)); From 38ed31ce906bb9e189fc4dbce5e59847f9df1550 Mon Sep 17 00:00:00 2001 From: AljazD Date: Thu, 9 Oct 2025 14:31:59 +0200 Subject: [PATCH 19/33] Updated argument order of FileGroup add functions, fileName is now always first --- .../src/utility/EventsManagerBindings.cpp | 28 +++++++++---------- examples/cpp/Events/events.cpp | 2 +- examples/python/Events/events.py | 2 +- include/depthai/utility/EventsManager.hpp | 24 ++++++++-------- src/utility/EventsManager.cpp | 16 +++++------ 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index f5c249e0ee..18007337e5 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -26,31 +26,31 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { .def("clearFiles", &FileGroup::clearFiles, DOC(dai, utility, FileGroup, clearFiles)) .def("addFile", static_cast(&FileGroup::addFile), - py::arg("data"), py::arg("fileName"), + py::arg("data"), py::arg("mimeType"), DOC(dai, utility, FileGroup, addFile)) .def("addFile", static_cast(&FileGroup::addFile), - py::arg("filePath"), py::arg("fileName"), DOC(dai, utility, FileGroup, addFile)) - .def("addFile", static_cast&, std::string)>(&FileGroup::addFile), - py::arg("imgFrame"), py::arg("fileName"), DOC(dai, utility, FileGroup, addFile)) - .def("addFile", static_cast&, std::string)>(&FileGroup::addFile), - py::arg("encodedFrame"), py::arg("fileName"), DOC(dai, utility, FileGroup, addFile)) - .def("addFile", static_cast&, std::string)>(&FileGroup::addFile), - py::arg("nnData"), py::arg("fileName"), DOC(dai, utility, FileGroup, addFile)) - .def("addFile", static_cast&, std::string)>(&FileGroup::addFile), - py::arg("imgDetections"), py::arg("fileName"), DOC(dai, utility, FileGroup, addFile)) + py::arg("fileName"), py::arg("filePath"), DOC(dai, utility, FileGroup, addFile)) + .def("addFile", static_cast&)>(&FileGroup::addFile), + py::arg("fileName"), py::arg("imgFrame"), DOC(dai, utility, FileGroup, addFile)) + .def("addFile", static_cast&)>(&FileGroup::addFile), + py::arg("fileName"), py::arg("encodedFrame"), DOC(dai, utility, FileGroup, addFile)) + .def("addFile", static_cast&)>(&FileGroup::addFile), + py::arg("fileName"), py::arg("nnData"), DOC(dai, utility, FileGroup, addFile)) + .def("addFile", static_cast&)>(&FileGroup::addFile), + py::arg("fileName"), py::arg("imgDetections"), DOC(dai, utility, FileGroup, addFile)) .def("addImageDetectionsPair", - static_cast&, const std::shared_ptr&, std::string)>(&FileGroup::addImageDetectionsPair), + static_cast&, const std::shared_ptr&)>(&FileGroup::addImageDetectionsPair), + py::arg("fileName"), py::arg("imgFrame"), py::arg("imgDetections"), - py::arg("fileName"), DOC(dai, utility, FileGroup, addImageDetectionsPair)) .def("addImageDetectionsPair", - static_cast&, const std::shared_ptr&, std::string)>(&FileGroup::addImageDetectionsPair), + static_cast&, const std::shared_ptr&)>(&FileGroup::addImageDetectionsPair), + py::arg("fileName"), py::arg("encodedFrame"), py::arg("imgDetections"), - py::arg("fileName"), DOC(dai, utility, FileGroup, addImageDetectionsPair)); py::class_(m, "EventsManager") diff --git a/examples/cpp/Events/events.cpp b/examples/cpp/Events/events.cpp index 0cfa8a4d24..41230d479b 100644 --- a/examples/cpp/Events/events.cpp +++ b/examples/cpp/Events/events.cpp @@ -86,7 +86,7 @@ int main() { ss << fileName << counter; fileGroup->clearFiles(); - fileGroup->addImageDetectionsPair(inRgb, borderDetections, ss.str()); + fileGroup->addImageDetectionsPair(ss.str(), inRgb, borderDetections); eventsManager->sendSnap("ImageDetection", {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, "", fileGroup); counter++; diff --git a/examples/python/Events/events.py b/examples/python/Events/events.py index f42d91766d..a8f138cb9a 100644 --- a/examples/python/Events/events.py +++ b/examples/python/Events/events.py @@ -81,7 +81,7 @@ def frameNorm(frame, bbox): fileName = f"ImageDetection_{counter}" fileGroup.clearFiles(); - fileGroup.addImageDetectionsPair(inRgb, borderDetections, fileName) + fileGroup.addImageDetectionsPair(fileName, inRgb, borderDetections) eventMan.sendSnap("ImageDetection", ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "", fileGroup) counter += 1 diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 78fe93724b..ea559955e8 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -57,18 +57,18 @@ class FileData { class FileGroup { public: void clearFiles(); - void addFile(std::string data, std::string fileName, std::string mimeType); - void addFile(std::string filePath, std::string fileName); - void addFile(const std::shared_ptr& imgFrame, std::string fileName); - void addFile(const std::shared_ptr& encodedFrame, std::string fileName); - void addFile(const std::shared_ptr& nnData, std::string fileName); - void addFile(const std::shared_ptr& imgDetections, std::string fileName); - void addImageDetectionsPair(const std::shared_ptr& imgFrame, - const std::shared_ptr& imgDetections, - std::string fileName); - void addImageDetectionsPair(const std::shared_ptr& encodedFrame, - const std::shared_ptr& imgDetections, - std::string fileName); + void addFile(std::string fileName, std::string data, std::string mimeType); + void addFile(std::string fileName, std::string filePath); + void addFile(std::string fileName, const std::shared_ptr& imgFrame); + void addFile(std::string fileName, const std::shared_ptr& encodedFrame); + void addFile(std::string fileName, const std::shared_ptr& nnData); + void addFile(std::string fileName, const std::shared_ptr& imgDetections); + void addImageDetectionsPair(std::string fileName, + const std::shared_ptr& imgFrame, + const std::shared_ptr& imgDetections); + void addImageDetectionsPair(std::string fileName, + const std::shared_ptr& encodedFrame, + const std::shared_ptr& imgDetections); private: std::vector> fileData; diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 7c0a806477..ce6100423d 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -28,36 +28,36 @@ void FileGroup::clearFiles() { fileData.clear(); } -void FileGroup::addFile(std::string data, std::string fileName, std::string mimeType) { +void FileGroup::addFile(std::string fileName, std::string data, std::string mimeType) { addToFileData(fileData, std::move(data), std::move(fileName), std::move(mimeType)); } -void FileGroup::addFile(std::string filePath, std::string fileName) { +void FileGroup::addFile(std::string fileName, std::string filePath) { addToFileData(fileData, std::move(filePath), std::move(fileName)); } -void FileGroup::addFile(const std::shared_ptr& imgFrame, std::string fileName) { +void FileGroup::addFile(std::string fileName, const std::shared_ptr& imgFrame) { addToFileData(fileData, imgFrame, std::move(fileName)); } -void FileGroup::addFile(const std::shared_ptr& encodedFrame, std::string fileName) { +void FileGroup::addFile(std::string fileName, const std::shared_ptr& encodedFrame) { addToFileData(fileData, encodedFrame, std::move(fileName)); } -void FileGroup::addFile(const std::shared_ptr& nnData, std::string fileName) { +void FileGroup::addFile(std::string fileName, const std::shared_ptr& nnData) { addToFileData(fileData, nnData, std::move(fileName)); } -void FileGroup::addFile(const std::shared_ptr& imgDetections, std::string fileName) { +void FileGroup::addFile(std::string fileName, const std::shared_ptr& imgDetections) { addToFileData(fileData, imgDetections, std::move(fileName)); } -void FileGroup::addImageDetectionsPair(const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections, std::string fileName) { +void FileGroup::addImageDetectionsPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections) { addToFileData(fileData, imgFrame, std::move(fileName)); addToFileData(fileData, imgDetections, std::move(fileName)); } -void FileGroup::addImageDetectionsPair(const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections, std::string fileName) { +void FileGroup::addImageDetectionsPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections) { addToFileData(fileData, encodedFrame, std::move(fileName)); addToFileData(fileData, imgDetections, std::move(fileName)); } From a13351ece6124e2c624ac7e2c364bb23157a8385 Mon Sep 17 00:00:00 2001 From: AljazD Date: Fri, 10 Oct 2025 08:02:59 +0200 Subject: [PATCH 20/33] Added addImageNNDataPair() to FileGroup --- .../python/src/utility/EventsManagerBindings.cpp | 14 +++++++++++++- include/depthai/utility/EventsManager.hpp | 6 ++++++ src/utility/EventsManager.cpp | 10 ++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 18007337e5..75fd6340b0 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -51,7 +51,19 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { py::arg("fileName"), py::arg("encodedFrame"), py::arg("imgDetections"), - DOC(dai, utility, FileGroup, addImageDetectionsPair)); + DOC(dai, utility, FileGroup, addImageDetectionsPair)) + .def("addImageNNDataPair", + static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), + py::arg("fileName"), + py::arg("imgFrame"), + py::arg("nnData"), + DOC(dai, utility, FileGroup, addImageNNDataPair)) + .def("addImageNNDataPair", + static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), + py::arg("fileName"), + py::arg("encodedFrame"), + py::arg("nnData"), + DOC(dai, utility, FileGroup, addImageNNDataPair)); py::class_(m, "EventsManager") .def(py::init<>()) diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index ea559955e8..737729e1cd 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -69,6 +69,12 @@ class FileGroup { void addImageDetectionsPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections); + void addImageNNDataPair(std::string fileName, + const std::shared_ptr& imgFrame, + const std::shared_ptr& imgDetections); + void addImageNNDataPair(std::string fileName, + const std::shared_ptr& encodedFrame, + const std::shared_ptr& imgDetections); private: std::vector> fileData; diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index ce6100423d..40d132c73c 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -62,6 +62,16 @@ void FileGroup::addImageDetectionsPair(std::string fileName, const std::shared_p addToFileData(fileData, imgDetections, std::move(fileName)); } +void FileGroup::addImageNNDataPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& nnData) { + addToFileData(fileData, imgFrame, std::move(fileName)); + addToFileData(fileData, nnData, std::move(fileName)); +} + +void FileGroup::addImageNNDataPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& nnData) { + addToFileData(fileData, encodedFrame, std::move(fileName)); + addToFileData(fileData, nnData, std::move(fileName)); +} + FileData::FileData(std::string data, std::string fileName, std::string mimeType) : mimeType(std::move(mimeType)), From 9c293e7f18d02b2792122972f75da0af03850810 Mon Sep 17 00:00:00 2001 From: AljazD Date: Fri, 10 Oct 2025 10:10:43 +0200 Subject: [PATCH 21/33] Added sendSnap() overload for the most common use case --- .../src/utility/EventsManagerBindings.cpp | 17 +++++++++++-- examples/cpp/Events/events.cpp | 2 +- examples/python/Events/events.py | 2 +- include/depthai/utility/EventsManager.hpp | 25 ++++++++++++++++--- src/utility/EventsManager.cpp | 18 +++++++++++-- 5 files changed, 54 insertions(+), 10 deletions(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 75fd6340b0..efc556fd30 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -87,12 +87,25 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { py::arg("associateFiles") = std::vector(), DOC(dai, utility, EventsManager, sendEvent)) .def("sendSnap", - &EventsManager::sendSnap, + static_cast, const std::vector&, + const std::unordered_map&, const std::string&)>(&EventsManager::sendSnap), py::arg("name"), + py::arg("fileGroup") = std::shared_ptr(), + py::arg("tags") = std::vector(), + py::arg("extras") = std::unordered_map(), + py::arg("deviceSerialNo") = "", + DOC(dai, utility, EventsManager, sendSnap)) + .def("sendSnap", + static_cast, + const std::shared_ptr, const std::vector&, + const std::unordered_map&, const std::string&)>(&EventsManager::sendSnap), + py::arg("name"), + py::arg("fileName"), + py::arg("imgFrame"), + py::arg("imgDetections"), py::arg("tags") = std::vector(), py::arg("extras") = std::unordered_map(), py::arg("deviceSerialNo") = "", - py::arg("fileGroup") = std::shared_ptr(), DOC(dai, utility, EventsManager, sendSnap)); #endif } diff --git a/examples/cpp/Events/events.cpp b/examples/cpp/Events/events.cpp index 41230d479b..f486ba7da9 100644 --- a/examples/cpp/Events/events.cpp +++ b/examples/cpp/Events/events.cpp @@ -87,7 +87,7 @@ int main() { fileGroup->clearFiles(); fileGroup->addImageDetectionsPair(ss.str(), inRgb, borderDetections); - eventsManager->sendSnap("ImageDetection", {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, "", fileGroup); + eventsManager->sendSnap("ImageDetection", fileGroup, {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, ""); counter++; } diff --git a/examples/python/Events/events.py b/examples/python/Events/events.py index a8f138cb9a..075a834516 100644 --- a/examples/python/Events/events.py +++ b/examples/python/Events/events.py @@ -82,7 +82,7 @@ def frameNorm(frame, bbox): fileGroup.clearFiles(); fileGroup.addImageDetectionsPair(fileName, inRgb, borderDetections) - eventMan.sendSnap("ImageDetection", ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "", fileGroup) + eventMan.sendSnap("ImageDetection", fileGroup, ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "") counter += 1 diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 737729e1cd..dc125a3db8 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -103,18 +103,35 @@ class EventsManager { /** * Send a snap to the events service. Snaps should be used for sending images and other files. * @param name Name of the snap + * @param fileGroup FileGroup containing FileData objects to send * @param tags List of tags to send * @param extras Extra data to send * @param deviceSerialNo Device serial number - * @param fileGroup FileGroup containing FileData objects to send * @return bool */ bool sendSnap(const std::string& name, + const std::shared_ptr fileGroup, const std::vector& tags = {}, const std::unordered_map& extras = {}, - const std::string& deviceSerialNo = "", - const std::shared_ptr fileGroup = nullptr); - + const std::string& deviceSerialNo = ""); + /** + * Send a snap to the events service, with an ImgFrame and ImgDetections pair as files + * @param name Name of the snap + * @param fileName File name used to create FileData + * @param imgFrame ImgFrame to send + * @param imgDetections ImgDetections to sent + * @param tags List of tags to send + * @param extras Extra data to send + * @param deviceSerialNo Device serial number + * @return bool + */ + bool sendSnap(const std::string& name, + const std::string& fileName, + const std::shared_ptr imgFrame, + const std::shared_ptr imgDetections, + const std::vector& tags = {}, + const std::unordered_map& extras = {}, + const std::string& deviceSerialNo = ""); /** * Set the URL of the events service. By default, the URL is set to https://events.cloud.luxonis.com * @param url URL of the events service diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 40d132c73c..b62ea1bed8 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -603,10 +603,10 @@ bool EventsManager::sendEvent(const std::string& name, } bool EventsManager::sendSnap(const std::string& name, + const std::shared_ptr fileGroup, const std::vector& tags, const std::unordered_map& extras, - const std::string& deviceSerialNo, - const std::shared_ptr fileGroup) { + const std::string& deviceSerialNo) { // Prepare snapData auto snapData = std::make_unique(); snapData->fileGroup = fileGroup; @@ -648,6 +648,20 @@ bool EventsManager::sendSnap(const std::string& name, return true; } +bool EventsManager::sendSnap(const std::string& name, + const std::string& fileName, + const std::shared_ptr imgFrame, + const std::shared_ptr imgDetections, + const std::vector& tags, + const std::unordered_map& extras, + const std::string& deviceSerialNo) { + // Create a FileGroup and send a snap containing it + auto fileGroup = std::make_shared(); + fileGroup->addImageDetectionsPair(fileName, imgFrame, imgDetections); + + return sendSnap(name, fileGroup, tags, extras, deviceSerialNo); +} + bool EventsManager::validateEvent(const proto::event::Event& inputEvent) { // Name const auto& name = inputEvent.name(); From 3716c1f3d2ae37cb90590c9c778e5c1058865757 Mon Sep 17 00:00:00 2001 From: AljazD Date: Fri, 10 Oct 2025 11:26:00 +0200 Subject: [PATCH 22/33] Hub Url is now determined using an env setting --- README.md | 1 + .../python/src/utility/EventsManagerBindings.cpp | 3 +-- include/depthai/utility/EventsManager.hpp | 8 +------- src/utility/EventsManager.cpp | 14 ++++---------- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 0842dd422a..c39d8c43f1 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,7 @@ The following environment variables can be set to alter default behavior of the | DEPTHAI_CRASHDUMP_TIMEOUT | Specifies the duration in milliseconds to wait for device reboot when obtaining a crash dump. Crash dump retrieval disabled if 0. | | DEPTHAI_ENABLE_ANALYTICS_COLLECTION | Enables automatic analytics collection (pipeline schemas) used to improve the library | | DEPTHAI_DISABLE_CRASHDUMP_COLLECTION | Disables automatic crash dump collection used to improve the library | +| DEPTHAI_HUB_URL | URL for the Luxonis Hub | | DEPTHAI_HUB_API_KEY | API key for the Luxonis Hub | | DEPTHAI_ZOO_INTERNET_CHECK | (Default) 1 - perform internet check, if available, download the newest model version 0 - skip internet check and use cached model | | DEPTHAI_ZOO_INTERNET_CHECK_TIMEOUT | (Default) 1000 - timeout in milliseconds for the internet check | diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index efc556fd30..c373cdb1dc 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -67,8 +67,7 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { py::class_(m, "EventsManager") .def(py::init<>()) - .def(py::init(), py::arg("url"), py::arg("uploadCachedOnStart") = false, py::arg("publishInterval") = 10.0) - .def("setUrl", &EventsManager::setUrl, py::arg("url"), DOC(dai, utility, EventsManager, setUrl)) + .def(py::init(), py::arg("uploadCachedOnStart") = false, py::arg("publishInterval") = 10.0) .def("setToken", &EventsManager::setToken, py::arg("token"), DOC(dai, utility, EventsManager, setToken)) .def("setLogResponse", &EventsManager::setLogResponse, py::arg("logResponse"), DOC(dai, utility, EventsManager, setLogResponse)) .def("setVerifySsl", &EventsManager::setVerifySsl, py::arg("verifySsl"), DOC(dai, utility, EventsManager, setVerifySsl)) diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index dc125a3db8..849e6b22a8 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -83,7 +83,7 @@ class FileGroup { class EventsManager { public: - explicit EventsManager(std::string url = "https://events.cloud.luxonis.com", bool uploadCachedOnStart = false, float publishInterval = 10.0); + explicit EventsManager(bool uploadCachedOnStart = false, float publishInterval = 10.0); ~EventsManager(); /** @@ -132,12 +132,6 @@ class EventsManager { const std::vector& tags = {}, const std::unordered_map& extras = {}, const std::string& deviceSerialNo = ""); - /** - * Set the URL of the events service. By default, the URL is set to https://events.cloud.luxonis.com - * @param url URL of the events service - * @return void - */ - void setUrl(const std::string& url); /** * Set the token for the events service. By default, the token is taken from the environment variable DEPTHAI_HUB_API_KEY * @param token Token for the events service diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index b62ea1bed8..0c66123b41 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -225,9 +225,8 @@ std::string FileData::calculateSHA256Checksum(const std::string& data) { } -EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float publishInterval) - : url(std::move(url)), - publishInterval(publishInterval), +EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) + : publishInterval(publishInterval), logResponse(false), verifySsl(true), cacheDir("/internal/private"), @@ -238,8 +237,8 @@ EventsManager::EventsManager(std::string url, bool uploadCachedOnStart, float pu auto containerId = utility::getEnvAs("OAKAGENT_CONTAINER_ID", ""); sourceAppId = appId == "" ? containerId : appId; sourceAppIdentifier = utility::getEnvAs("OAKAGENT_APP_IDENTIFIER", ""); + url = utility::getEnvAs("DEPTHAI_HUB_URL", "https://events.cloud.luxonis.com"); token = utility::getEnvAs("DEPTHAI_HUB_API_KEY", ""); - dai::Logging::getInstance().logger.set_level(spdlog::level::info); // TO DO: Remove // Thread handling preparation and uploads uploadThread = std::make_unique([this]() { auto nextTime = std::chrono::steady_clock::now(); @@ -292,7 +291,6 @@ bool EventsManager::fetchConfigurationLimits() { logger::info("Fetching configuration limits"); auto header = cpr::Header(); header["Authorization"] = "Bearer " + token; - header["Content-Type"] = "application/x-protobuf"; cpr::Url requestUrl = static_cast(this->url + "/v2/api-usage"); // Might change to infinte retrying in the future for (int i = 0; i < uploadRetryPolicy.maxAttempts && !stopUploadThread; i++) { @@ -658,7 +656,7 @@ bool EventsManager::sendSnap(const std::string& name, // Create a FileGroup and send a snap containing it auto fileGroup = std::make_shared(); fileGroup->addImageDetectionsPair(fileName, imgFrame, imgDetections); - + return sendSnap(name, fileGroup, tags, extras, deviceSerialNo); } @@ -803,10 +801,6 @@ void EventsManager::setCacheDir(const std::string& cacheDir) { this->cacheDir = cacheDir; } -void EventsManager::setUrl(const std::string& url) { - this->url = url; -} - void EventsManager::setToken(const std::string& token) { this->token = token; } From 940ca89777e907df949136f561887ae545fe61c6 Mon Sep 17 00:00:00 2001 From: AljazD Date: Fri, 10 Oct 2025 13:28:00 +0200 Subject: [PATCH 23/33] Fixed formatting --- .../src/utility/EventsManagerBindings.cpp | 115 +++++---- examples/cpp/Events/events.cpp | 24 +- include/depthai/utility/EventsManager.hpp | 22 +- src/utility/EventsManager.cpp | 226 ++++++++---------- 4 files changed, 193 insertions(+), 194 deletions(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index c373cdb1dc..333337227d 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -25,44 +25,62 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { .def(py::init<>()) .def("clearFiles", &FileGroup::clearFiles, DOC(dai, utility, FileGroup, clearFiles)) .def("addFile", - static_cast(&FileGroup::addFile), - py::arg("fileName"), - py::arg("data"), - py::arg("mimeType"), - DOC(dai, utility, FileGroup, addFile)) - .def("addFile", static_cast(&FileGroup::addFile), - py::arg("fileName"), py::arg("filePath"), DOC(dai, utility, FileGroup, addFile)) - .def("addFile", static_cast&)>(&FileGroup::addFile), - py::arg("fileName"), py::arg("imgFrame"), DOC(dai, utility, FileGroup, addFile)) - .def("addFile", static_cast&)>(&FileGroup::addFile), - py::arg("fileName"), py::arg("encodedFrame"), DOC(dai, utility, FileGroup, addFile)) - .def("addFile", static_cast&)>(&FileGroup::addFile), - py::arg("fileName"), py::arg("nnData"), DOC(dai, utility, FileGroup, addFile)) - .def("addFile", static_cast&)>(&FileGroup::addFile), - py::arg("fileName"), py::arg("imgDetections"), DOC(dai, utility, FileGroup, addFile)) - .def("addImageDetectionsPair", - static_cast&, const std::shared_ptr&)>(&FileGroup::addImageDetectionsPair), - py::arg("fileName"), - py::arg("imgFrame"), - py::arg("imgDetections"), - DOC(dai, utility, FileGroup, addImageDetectionsPair)) - .def("addImageDetectionsPair", - static_cast&, const std::shared_ptr&)>(&FileGroup::addImageDetectionsPair), - py::arg("fileName"), - py::arg("encodedFrame"), - py::arg("imgDetections"), - DOC(dai, utility, FileGroup, addImageDetectionsPair)) - .def("addImageNNDataPair", - static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), - py::arg("fileName"), - py::arg("imgFrame"), - py::arg("nnData"), - DOC(dai, utility, FileGroup, addImageNNDataPair)) - .def("addImageNNDataPair", + static_cast(&FileGroup::addFile), + py::arg("fileName"), + py::arg("data"), + py::arg("mimeType"), + DOC(dai, utility, FileGroup, addFile)) + .def("addFile", + static_cast(&FileGroup::addFile), + py::arg("fileName"), + py::arg("filePath"), + DOC(dai, utility, FileGroup, addFile)) + .def("addFile", + static_cast&)>(&FileGroup::addFile), + py::arg("fileName"), + py::arg("imgFrame"), + DOC(dai, utility, FileGroup, addFile)) + .def("addFile", + static_cast&)>(&FileGroup::addFile), + py::arg("fileName"), + py::arg("encodedFrame"), + DOC(dai, utility, FileGroup, addFile)) + .def("addFile", + static_cast&)>(&FileGroup::addFile), + py::arg("fileName"), + py::arg("nnData"), + DOC(dai, utility, FileGroup, addFile)) + .def("addFile", + static_cast&)>(&FileGroup::addFile), + py::arg("fileName"), + py::arg("imgDetections"), + DOC(dai, utility, FileGroup, addFile)) + .def("addImageDetectionsPair", + static_cast&, const std::shared_ptr&)>( + &FileGroup::addImageDetectionsPair), + py::arg("fileName"), + py::arg("imgFrame"), + py::arg("imgDetections"), + DOC(dai, utility, FileGroup, addImageDetectionsPair)) + .def("addImageDetectionsPair", + static_cast&, const std::shared_ptr&)>( + &FileGroup::addImageDetectionsPair), + py::arg("fileName"), + py::arg("encodedFrame"), + py::arg("imgDetections"), + DOC(dai, utility, FileGroup, addImageDetectionsPair)) + .def("addImageNNDataPair", + static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), + py::arg("fileName"), + py::arg("imgFrame"), + py::arg("nnData"), + DOC(dai, utility, FileGroup, addImageNNDataPair)) + .def( + "addImageNNDataPair", static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), - py::arg("fileName"), - py::arg("encodedFrame"), - py::arg("nnData"), + py::arg("fileName"), + py::arg("encodedFrame"), + py::arg("nnData"), DOC(dai, utility, FileGroup, addImageNNDataPair)); py::class_(m, "EventsManager") @@ -86,8 +104,11 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { py::arg("associateFiles") = std::vector(), DOC(dai, utility, EventsManager, sendEvent)) .def("sendSnap", - static_cast, const std::vector&, - const std::unordered_map&, const std::string&)>(&EventsManager::sendSnap), + static_cast, + const std::vector&, + const std::unordered_map&, + const std::string&)>(&EventsManager::sendSnap), py::arg("name"), py::arg("fileGroup") = std::shared_ptr(), py::arg("tags") = std::vector(), @@ -95,13 +116,17 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { py::arg("deviceSerialNo") = "", DOC(dai, utility, EventsManager, sendSnap)) .def("sendSnap", - static_cast, - const std::shared_ptr, const std::vector&, - const std::unordered_map&, const std::string&)>(&EventsManager::sendSnap), + static_cast, + const std::shared_ptr, + const std::vector&, + const std::unordered_map&, + const std::string&)>(&EventsManager::sendSnap), py::arg("name"), - py::arg("fileName"), - py::arg("imgFrame"), - py::arg("imgDetections"), + py::arg("fileName"), + py::arg("imgFrame"), + py::arg("imgDetections"), py::arg("tags") = std::vector(), py::arg("extras") = std::unordered_map(), py::arg("deviceSerialNo") = "", diff --git a/examples/cpp/Events/events.cpp b/examples/cpp/Events/events.cpp index f486ba7da9..98be0ebfec 100644 --- a/examples/cpp/Events/events.cpp +++ b/examples/cpp/Events/events.cpp @@ -1,12 +1,11 @@ #include #include -#include #include +#include #include "depthai/depthai.hpp" #include "depthai/utility/EventsManager.hpp" - // Helper function to normalize frame coordinates cv::Rect frameNorm(const cv::Mat& frame, const dai::Point2f& topLeft, const dai::Point2f& bottomRight) { float width = frame.cols, height = frame.rows; @@ -34,14 +33,13 @@ int main() { auto qRgb = detectionNetwork->passthrough.createOutputQueue(); auto qDet = detectionNetwork->out.createOutputQueue(); - pipeline.start(); - int counter = 0; + int counter = 0; while(pipeline.isRunning()) { auto inRgb = qRgb->get(); auto inDet = qDet->get(); - if (inRgb == nullptr || inDet == nullptr) { + if(inRgb == nullptr || inDet == nullptr) { continue; } @@ -53,7 +51,8 @@ int main() { auto bbox = frameNorm(frame, dai::Point2f(detection.xmin, detection.ymin), dai::Point2f(detection.xmax, detection.ymax)); // Draw label - cv::putText(frame, labelMap.value()[detection.label], cv::Point(bbox.x + 10, bbox.y + 20), cv::FONT_HERSHEY_TRIPLEX, 0.5, cv::Scalar(255,255,255)); + cv::putText( + frame, labelMap.value()[detection.label], cv::Point(bbox.x + 10, bbox.y + 20), cv::FONT_HERSHEY_TRIPLEX, 0.5, cv::Scalar(255, 255, 255)); // Draw confidence cv::putText(frame, @@ -61,7 +60,7 @@ int main() { cv::Point(bbox.x + 10, bbox.y + 40), cv::FONT_HERSHEY_TRIPLEX, 0.5, - cv::Scalar(255,255,255)); + cv::Scalar(255, 255, 255)); // Draw rectangle cv::rectangle(frame, bbox, cv::Scalar(255, 0, 0), 2); @@ -73,18 +72,18 @@ int main() { // Suppose we are only interested in the detections with confidence between 50% and 60% auto borderDetections = std::make_shared(); - for (const auto& detection : inDet->detections) { - if (detection.confidence > 0.5f && detection.confidence < 0.6f) { + for(const auto& detection : inDet->detections) { + if(detection.confidence > 0.5f && detection.confidence < 0.6f) { borderDetections->detections.emplace_back(detection); } } - + // Are there any border detections - if (borderDetections->detections.size() > 0) { + if(borderDetections->detections.size() > 0) { std::string fileName = "ImageDetection_"; std::stringstream ss; ss << fileName << counter; - + fileGroup->clearFiles(); fileGroup->addImageDetectionsPair(ss.str(), inRgb, borderDetections); eventsManager->sendSnap("ImageDetection", fileGroup, {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, ""); @@ -97,6 +96,5 @@ int main() { } } - return EXIT_SUCCESS; } \ No newline at end of file diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 849e6b22a8..f64f165530 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -2,19 +2,19 @@ #include #include +#include #include #include #include #include #include #include -#include #include "depthai/pipeline/datatype/ADatatype.hpp" #include "depthai/pipeline/datatype/EncodedFrame.hpp" +#include "depthai/pipeline/datatype/ImgDetections.hpp" #include "depthai/pipeline/datatype/ImgFrame.hpp" #include "depthai/pipeline/datatype/NNData.hpp" -#include "depthai/pipeline/datatype/ImgDetections.hpp" namespace dai { namespace proto { @@ -63,18 +63,10 @@ class FileGroup { void addFile(std::string fileName, const std::shared_ptr& encodedFrame); void addFile(std::string fileName, const std::shared_ptr& nnData); void addFile(std::string fileName, const std::shared_ptr& imgDetections); - void addImageDetectionsPair(std::string fileName, - const std::shared_ptr& imgFrame, - const std::shared_ptr& imgDetections); - void addImageDetectionsPair(std::string fileName, - const std::shared_ptr& encodedFrame, - const std::shared_ptr& imgDetections); - void addImageNNDataPair(std::string fileName, - const std::shared_ptr& imgFrame, - const std::shared_ptr& imgDetections); - void addImageNNDataPair(std::string fileName, - const std::shared_ptr& encodedFrame, - const std::shared_ptr& imgDetections); + void addImageDetectionsPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections); + void addImageDetectionsPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections); + void addImageNNDataPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections); + void addImageNNDataPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections); private: std::vector> fileData; @@ -117,7 +109,7 @@ class EventsManager { /** * Send a snap to the events service, with an ImgFrame and ImgDetections pair as files * @param name Name of the snap - * @param fileName File name used to create FileData + * @param fileName File name used to create FileData * @param imgFrame ImgFrame to send * @param imgDetections ImgDetections to sent * @param tags List of tags to send diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 0c66123b41..d75056f6e8 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -1,5 +1,7 @@ #include "depthai/utility/EventsManager.hpp" +#include + #include #include #include @@ -7,7 +9,6 @@ #include #include #include -#include #include "Environment.hpp" #include "Logging.hpp" @@ -18,8 +19,7 @@ namespace dai { namespace utility { using std::move; - -template +template void addToFileData(std::vector>& container, Args&&... args) { container.emplace_back(std::make_shared(std::forward(args)...)); } @@ -57,7 +57,9 @@ void FileGroup::addImageDetectionsPair(std::string fileName, const std::shared_p addToFileData(fileData, imgDetections, std::move(fileName)); } -void FileGroup::addImageDetectionsPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections) { +void FileGroup::addImageDetectionsPair(std::string fileName, + const std::shared_ptr& encodedFrame, + const std::shared_ptr& imgDetections) { addToFileData(fileData, encodedFrame, std::move(fileName)); addToFileData(fileData, imgDetections, std::move(fileName)); } @@ -72,7 +74,6 @@ void FileGroup::addImageNNDataPair(std::string fileName, const std::shared_ptr(fileData, nnData, std::move(fileName)); } - FileData::FileData(std::string data, std::string fileName, std::string mimeType) : mimeType(std::move(mimeType)), fileName(std::move(fileName)), @@ -81,24 +82,21 @@ FileData::FileData(std::string data, std::string fileName, std::string mimeType) checksum(calculateSHA256Checksum(data)), classification(proto::event::PrepareFileUploadClass::UNKNOWN_FILE) {} -FileData::FileData(std::string filePath, std::string fileName) - : fileName(std::move(fileName)) { - static const std::unordered_map mimeTypeExtensionMap = { - {".html", "text/html"}, - {".htm", "text/html"}, - {".css", "text/css"}, - {".js", "text/javascript"}, - {".png", "image/png"}, - {".jpg", "image/jpeg"}, - {".jpeg", "image/jpeg"}, - {".gif", "image/gif"}, - {".svg", "image/svg+xml"}, - {".json", "application/json"}, - {".txt", "text/plain"} - }; +FileData::FileData(std::string filePath, std::string fileName) : fileName(std::move(fileName)) { + static const std::unordered_map mimeTypeExtensionMap = {{".html", "text/html"}, + {".htm", "text/html"}, + {".css", "text/css"}, + {".js", "text/javascript"}, + {".png", "image/png"}, + {".jpg", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".gif", "image/gif"}, + {".svg", "image/svg+xml"}, + {".json", "application/json"}, + {".txt", "text/plain"}}; // Read the data std::ifstream fileStream(filePath, std::ios::binary | std::ios::ate); - if (!fileStream) { + if(!fileStream) { logger::error("File: {} doesn't exist", filePath); return; } @@ -110,15 +108,13 @@ FileData::FileData(std::string filePath, std::string fileName) checksum = calculateSHA256Checksum(data); // Determine the mime type auto it = mimeTypeExtensionMap.find(std::filesystem::path(filePath).extension().string()); - if (it != mimeTypeExtensionMap.end()) { + if(it != mimeTypeExtensionMap.end()) { mimeType = it->second; } else { mimeType = "application/octet-stream"; } - static const std::unordered_set imageMimeTypes = { - "image/jpeg", "image/png", "image/webp", "image/bmp", "image/tiff" - }; - if (imageMimeTypes.find(mimeType) != imageMimeTypes.end()) { + static const std::unordered_set imageMimeTypes = {"image/jpeg", "image/png", "image/webp", "image/bmp", "image/tiff"}; + if(imageMimeTypes.find(mimeType) != imageMimeTypes.end()) { classification = proto::event::PrepareFileUploadClass::IMAGE_COLOR; } else { classification = proto::event::PrepareFileUploadClass::UNKNOWN_FILE; @@ -164,21 +160,23 @@ FileData::FileData(const std::shared_ptr& nnData, std::string fileName) } FileData::FileData(const std::shared_ptr& imgDetections, std::string fileName) - : mimeType("application/x-protobuf; proto=SnapAnnotation"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::ANNOTATION) { + : mimeType("application/x-protobuf; proto=SnapAnnotation"), + fileName(std::move(fileName)), + classification(proto::event::PrepareFileUploadClass::ANNOTATION) { // Serialize imgDetections object, add it to SnapAnnotation proto proto::event::SnapAnnotations snapAnnotation; proto::img_detections::ImgDetections imgDetectionsProto; - if (imgDetections) { + if(imgDetections) { std::vector imgDetectionsSerialized = imgDetections->serializeProto(); - if (imgDetectionsProto.ParseFromArray(imgDetectionsSerialized.data(), imgDetectionsSerialized.size())) { + if(imgDetectionsProto.ParseFromArray(imgDetectionsSerialized.data(), imgDetectionsSerialized.size())) { *snapAnnotation.mutable_detections() = imgDetectionsProto; } else { logger::error("Failed to parse ImgDetections proto from serialized bytes"); return; - } + } } - if (!snapAnnotation.SerializeToString(&data)) { + if(!snapAnnotation.SerializeToString(&data)) { logger::error("Failed to serialize SnapAnnotations proto object to string"); return; } @@ -195,17 +193,17 @@ bool FileData::toFile(const std::string& inputPath) { std::string extension = mimeType == "image/jpeg" ? ".jpg" : ".txt"; // Choose a unique filename std::filesystem::path target = path / (fileName + extension); - for (int i = 1; std::filesystem::exists(target); ++i) { + for(int i = 1; std::filesystem::exists(target); ++i) { logger::warn("File {} exists, trying a new name", target.string()); target = path / (fileName + "_" + std::to_string(i) + extension); } std::ofstream fileStream(target, std::ios::binary); - if (!fileStream) { + if(!fileStream) { logger::error("Failed to open file for writing: {}", target.string()); return false; } fileStream.write(data.data(), static_cast(data.size())); - if (!fileStream) { + if(!fileStream) { logger::error("Failed to write all data to: {}", target.string()); return false; } @@ -217,14 +215,12 @@ std::string FileData::calculateSHA256Checksum(const std::string& data) { SHA256(reinterpret_cast(data.data()), data.size(), digest); std::ostringstream oss; - for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { - oss << std::hex << std::setw(2) << std::setfill('0') - << static_cast(digest[i]); + for(int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(digest[i]); } return oss.str(); } - EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) : publishInterval(publishInterval), logResponse(false), @@ -245,11 +241,11 @@ EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) while(!stopUploadThread) { // Hourly check for fetching configuration and limits auto currentTime = std::chrono::steady_clock::now(); - if (currentTime >= nextTime) { + if(currentTime >= nextTime) { fetchConfigurationLimits(); nextTime += std::chrono::hours(1); - if (remainingStorageBytes <= warningStorageBytes) { - logger::warn("Current remaining storage is running low: {} MB", remainingStorageBytes / (1024*1024)); + if(remainingStorageBytes <= warningStorageBytes) { + logger::warn("Current remaining storage is running low: {} MB", remainingStorageBytes / (1024 * 1024)); } } // Prepare the batch first to reduce contention @@ -261,17 +257,12 @@ EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) snapBuffer.erase(snapBuffer.begin(), snapBuffer.begin() + size); } // TO DO: Handle the clearing of these futures - uploadFileBatchFutures.emplace_back(std::async(std::launch::async, [&, inputSnapBatch = std::move(snapBatch)]() mutable { - uploadFileBatch(std::move(inputSnapBatch)); - })); + uploadFileBatchFutures.emplace_back( + std::async(std::launch::async, [&, inputSnapBatch = std::move(snapBatch)]() mutable { uploadFileBatch(std::move(inputSnapBatch)); })); uploadEventBatch(); std::unique_lock lock(stopThreadConditionMutex); - eventBufferCondition.wait_for(lock, - std::chrono::seconds(static_cast(this->publishInterval)), - [this]() { - return stopUploadThread.load(); - }); + eventBufferCondition.wait_for(lock, std::chrono::seconds(static_cast(this->publishInterval)), [this]() { return stopUploadThread.load(); }); } }); if(uploadCachedOnStart) { @@ -293,7 +284,7 @@ bool EventsManager::fetchConfigurationLimits() { header["Authorization"] = "Bearer " + token; cpr::Url requestUrl = static_cast(this->url + "/v2/api-usage"); // Might change to infinte retrying in the future - for (int i = 0; i < uploadRetryPolicy.maxAttempts && !stopUploadThread; i++) { + for(int i = 0; i < uploadRetryPolicy.maxAttempts && !stopUploadThread; i++) { cpr::Response response = cpr::Get( cpr::Url{requestUrl}, cpr::Header{header}, @@ -314,14 +305,12 @@ bool EventsManager::fetchConfigurationLimits() { logger::error("Failed to fetch configuration limits, status code: {}", response.status_code); // Apply exponential backoff - auto factor = std::pow(uploadRetryPolicy.factor, i+1); + auto factor = std::pow(uploadRetryPolicy.factor, i + 1); std::chrono::milliseconds duration = std::chrono::milliseconds(uploadRetryPolicy.baseDelay.count() * static_cast(factor)); - logger::info("Retrying to fetch configuration limits, (attempt {}/{}) in {} ms", i+1, uploadRetryPolicy.maxAttempts, duration.count()); - + logger::info("Retrying to fetch configuration limits, (attempt {}/{}) in {} ms", i + 1, uploadRetryPolicy.maxAttempts, duration.count()); + std::unique_lock lock(stopThreadConditionMutex); - eventBufferCondition.wait_for(lock, duration, [this]() { - return stopUploadThread.load(); - }); + eventBufferCondition.wait_for(lock, duration, [this]() { return stopUploadThread.load(); }); } else { logger::info("Configuration limits fetched successfully"); auto apiUsage = std::make_unique(); @@ -329,16 +318,16 @@ bool EventsManager::fetchConfigurationLimits() { if(logResponse) { logger::info("ApiUsage response: \n{}", apiUsage->DebugString()); } - // TO DO: Use this data - maxFileSizeBytes = apiUsage->files().max_file_size_bytes(); // - remainingStorageBytes = apiUsage->files().remaining_storage_bytes(); // + // TO DO: Use this data + maxFileSizeBytes = apiUsage->files().max_file_size_bytes(); // + remainingStorageBytes = apiUsage->files().remaining_storage_bytes(); // bytesPerHour = apiUsage->files().bytes_per_hour_rate(); uploadsPerHour = apiUsage->files().uploads_per_hour_rate(); - maxGroupsPerBatch = apiUsage->files().groups_per_allocation(); // - maxFilesPerGroup = apiUsage->files().files_per_group_in_allocation(); // + maxGroupsPerBatch = apiUsage->files().groups_per_allocation(); // + maxFilesPerGroup = apiUsage->files().files_per_group_in_allocation(); // eventsPerHour = apiUsage->events().events_per_hour_rate(); snapsPerHour = apiUsage->events().snaps_per_hour_rate(); - eventsPerRequest = apiUsage->events().events_per_request(); // + eventsPerRequest = apiUsage->events().events_per_request(); // return true; } @@ -347,9 +336,9 @@ bool EventsManager::fetchConfigurationLimits() { } void EventsManager::uploadFileBatch(std::deque> inputSnapBatch) { - // Prepare files for upload + // Prepare files for upload auto fileGroupBatchPrepare = std::make_unique(); - if (inputSnapBatch.empty()) { + if(inputSnapBatch.empty()) { return; } if(token.empty()) { @@ -357,9 +346,9 @@ void EventsManager::uploadFileBatch(std::deque> inputS return; } // Fill the batch with the groups from inputSnapBatch and their corresponding files - for (size_t i = 0; i < inputSnapBatch.size(); ++i) { + for(size_t i = 0; i < inputSnapBatch.size(); ++i) { auto fileGroup = std::make_unique(); - for (auto& file : inputSnapBatch.at(i)->fileGroup->fileData) { + for(auto& file : inputSnapBatch.at(i)->fileGroup->fileData) { auto addedFile = fileGroup->add_files(); addedFile->set_checksum(file->checksum); addedFile->set_mime_type(file->mimeType); @@ -371,7 +360,7 @@ void EventsManager::uploadFileBatch(std::deque> inputS } int retryAttempt = 0; - while (!stopUploadThread) { + while(!stopUploadThread) { std::string serializedBatch; fileGroupBatchPrepare->SerializeToString(&serializedBatch); cpr::Url requestUrl = static_cast(this->url + "/v2/files/prepare-batch"); @@ -392,49 +381,47 @@ void EventsManager::uploadFileBatch(std::deque> inputS } return true; })); - if (response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { + if(response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { logger::error("Failed to prepare a batch of file groups, status code: {}", response.status_code); // Apply exponential backoff auto factor = std::pow(uploadRetryPolicy.factor, ++retryAttempt); std::chrono::milliseconds duration = std::chrono::milliseconds(uploadRetryPolicy.baseDelay.count() * static_cast(factor)); logger::info("Retrying to prepare a batch of file groups (attempt {} in {} ms)", retryAttempt, duration.count()); - + std::unique_lock lock(stopThreadConditionMutex); - eventBufferCondition.wait_for(lock, duration, [this]() { - return stopUploadThread.load(); - }); - // TO DO: After a few tries, we can determine that the connection is not established. + eventBufferCondition.wait_for(lock, duration, [this]() { return stopUploadThread.load(); }); + // TO DO: After a few tries, we can determine that the connection is not established. // We can then check if caching is chosen. and do it - } - else { + } else { logger::info("Batch of file groups has been successfully prepared"); auto prepareBatchResults = std::make_unique(); prepareBatchResults->ParseFromString(response.text); - if (logResponse) { + if(logResponse) { logger::info("BatchFileUploadResult response: \n{}", prepareBatchResults->DebugString()); } // Upload groups of files std::vector> groupUploadResults; - for (int i = 0; i < prepareBatchResults->groups_size(); i++) { + for(int i = 0; i < prepareBatchResults->groups_size(); i++) { auto snapData = inputSnapBatch.at(i); auto prepareGroupResult = prepareBatchResults->groups(i); // Skip rejected groups - if (prepareGroupResult.has_rejected()) { + if(prepareGroupResult.has_rejected()) { std::string rejectionReason = dai::proto::event::RejectedFileGroupReason_descriptor() - ->FindValueByNumber(static_cast(prepareGroupResult.rejected().reason()))->name(); + ->FindValueByNumber(static_cast(prepareGroupResult.rejected().reason())) + ->name(); logger::info("A group has been rejected because of {}", rejectionReason); continue; } // Handle groups asynchronously - groupUploadResults.emplace_back(std::async(std::launch::async, - [&, snap = std::move(snapData), group = std::move(prepareGroupResult)]() mutable { + groupUploadResults.emplace_back( + std::async(std::launch::async, [&, snap = std::move(snapData), group = std::move(prepareGroupResult)]() mutable { return uploadGroup(std::move(snap), std::move(group)); - })); + })); } // Wait for all of the reponses, indicating the finish of group uploads - for (auto& uploadResult : groupUploadResults) { - if (!uploadResult.valid() || !uploadResult.get()) { + for(auto& uploadResult : groupUploadResults) { + if(!uploadResult.valid() || !uploadResult.get()) { logger::info("Failed to upload all of the groups in the given batch"); } } @@ -445,24 +432,25 @@ void EventsManager::uploadFileBatch(std::deque> inputS bool EventsManager::uploadGroup(std::shared_ptr snapData, dai::proto::event::FileUploadGroupResult prepareGroupResult) { std::vector> fileUploadResults; - for (int i = 0; i < prepareGroupResult.files_size(); i++) { + for(int i = 0; i < prepareGroupResult.files_size(); i++) { auto prepareFileResult = prepareGroupResult.files(i); if(prepareFileResult.result_case() == proto::event::FileUploadResult::kAccepted) { // Add an associate file to the event auto associateFile = snapData->event->add_associate_files(); associateFile->set_id(prepareFileResult.accepted().id()); // Upload files asynchronously - fileUploadResults.emplace_back(std::async(std::launch::async, + fileUploadResults.emplace_back(std::async( + std::launch::async, [&, fileData = std::move(snapData->fileGroup->fileData.at(i)), uploadUrl = std::move(prepareFileResult.accepted().upload_url())]() mutable { return uploadFile(std::move(fileData), std::move(uploadUrl)); - })); + })); } else { return false; } } // Wait for all of the results, indicating the finish of file uploads - for (auto& uploadResult : fileUploadResults) { - if (!uploadResult.valid() || !uploadResult.get()) { + for(auto& uploadResult : fileUploadResults) { + if(!uploadResult.valid() || !uploadResult.get()) { logger::info("Failed to upload all of the files in the given group"); return false; } @@ -477,7 +465,7 @@ bool EventsManager::uploadFile(std::shared_ptr fileData, std::string u logger::info("Uploading file {} to: {}", fileData->fileName, uploadUrl); auto header = cpr::Header(); header["Content-Type"] = fileData->mimeType; - for (int i = 0; i < uploadRetryPolicy.maxAttempts && !stopUploadThread; ++i) { + for(int i = 0; i < uploadRetryPolicy.maxAttempts && !stopUploadThread; ++i) { cpr::Response response = cpr::Put( cpr::Url{uploadUrl}, cpr::Body{fileData->data}, @@ -497,18 +485,16 @@ bool EventsManager::uploadFile(std::shared_ptr fileData, std::string u })); if(response.status_code != cpr::status::HTTP_OK && response.status_code != cpr::status::HTTP_CREATED) { logger::error("Failed to upload file {}, status code: {}", fileData->fileName, response.status_code); - if (logResponse) { + if(logResponse) { logger::info("Response {}", response.text); } // Apply exponential backoff - auto factor = std::pow(uploadRetryPolicy.factor, i+1); + auto factor = std::pow(uploadRetryPolicy.factor, i + 1); std::chrono::milliseconds duration = std::chrono::milliseconds(uploadRetryPolicy.baseDelay.count() * static_cast(factor)); - logger::info("Retrying upload of file {}, (attempt {}/{}) in {} ms", fileData->fileName, i+1, uploadRetryPolicy.maxAttempts, duration.count()); - + logger::info("Retrying upload of file {}, (attempt {}/{}) in {} ms", fileData->fileName, i + 1, uploadRetryPolicy.maxAttempts, duration.count()); + std::unique_lock lock(stopThreadConditionMutex); - eventBufferCondition.wait_for(lock, duration, [this]() { - return stopUploadThread.load(); - }); + eventBufferCondition.wait_for(lock, duration, [this]() { return stopUploadThread.load(); }); } else { return true; } @@ -527,7 +513,7 @@ void EventsManager::uploadEventBatch() { logger::warn("Missing token, please set DEPTHAI_HUB_API_KEY environment variable or use setToken method"); return; } - for (size_t i = 0; i < eventBuffer.size() && i < eventsPerRequest; ++i) { + for(size_t i = 0; i < eventBuffer.size() && i < eventsPerRequest; ++i) { eventBatch->add_events()->Swap(eventBuffer.at(i).get()); } } @@ -585,11 +571,11 @@ bool EventsManager::sendEvent(const std::string& name, event->set_source_serial_number(deviceSerialNo); event->set_source_app_id(sourceAppId); event->set_source_app_identifier(sourceAppIdentifier); - for (const auto& file : associateFiles) { + for(const auto& file : associateFiles) { auto addedFile = event->add_associate_files(); addedFile->set_id(file); } - if (!validateEvent(*event)) { + if(!validateEvent(*event)) { logger::error("Failed to send event, validation failed"); return false; } @@ -622,20 +608,20 @@ bool EventsManager::sendSnap(const std::string& name, snapData->event->set_source_serial_number(deviceSerialNo); snapData->event->set_source_app_id(sourceAppId); snapData->event->set_source_app_identifier(sourceAppIdentifier); - if (!validateEvent(*snapData->event)) { + if(!validateEvent(*snapData->event)) { logger::error("Failed to send snap, validation failed"); return false; } snapData->event->add_tags("snap"); - if (fileGroup->fileData.size() > maxFilesPerGroup) { + if(fileGroup->fileData.size() > maxFilesPerGroup) { logger::error("Failed to send snap, the number of files in a file group {} exceeds {}", fileGroup->fileData.size(), maxFilesPerGroup); return false; - } else if (fileGroup->fileData.empty()) { + } else if(fileGroup->fileData.empty()) { logger::error("Failed to send snap, the file group is empty"); return false; } - for (const auto& file : fileGroup->fileData) { - if (file->size >= maxFileSizeBytes) { + for(const auto& file : fileGroup->fileData) { + if(file->size >= maxFileSizeBytes) { logger::error("Failed to send snap, file: {} is bigger then the configured maximum size: {}", file->fileName, maxFileSizeBytes); return false; } @@ -663,61 +649,59 @@ bool EventsManager::sendSnap(const std::string& name, bool EventsManager::validateEvent(const proto::event::Event& inputEvent) { // Name const auto& name = inputEvent.name(); - if (name.empty()) { + if(name.empty()) { logger::error("Invalid event name: empty string"); return false; } - if (name.length() > eventValidationNameLength) { + if(name.length() > eventValidationNameLength) { logger::error("Invalid event name: length {} exceeds {}", name.length(), eventValidationNameLength); return false; } // Tags - if (inputEvent.tags_size() > eventValidationMaxTags) { + if(inputEvent.tags_size() > eventValidationMaxTags) { logger::error("Invalid event tags: number of tags {} exceeds {}", inputEvent.tags_size(), eventValidationMaxTags); return false; } - for (int i = 0; i < inputEvent.tags_size(); ++i) { + for(int i = 0; i < inputEvent.tags_size(); ++i) { const auto& tag = inputEvent.tags(i); - if (tag.empty()) { + if(tag.empty()) { logger::error("Invalid event tags: tag[{}] empty string", i); return false; } - if (tag.length() > 56) { + if(tag.length() > 56) { logger::error("Invalid event tags: tag[{}] length {} exceeds {}", i, tag.length(), eventValidationTagLength); return false; } } // Event extras - if (inputEvent.extras_size() > eventValidationMaxExtras) { + if(inputEvent.extras_size() > eventValidationMaxExtras) { logger::error("Invalid event extras: number of extras {} exceeds {}", inputEvent.extras_size(), eventValidationMaxExtras); return false; } int index = 0; - for (const auto& extra : inputEvent.extras()) { + for(const auto& extra : inputEvent.extras()) { const auto& key = extra.first; const auto& value = extra.second; - if (key.empty()) { + if(key.empty()) { logger::error("Invalid event extras: extra[{}] key empty string", index); return false; } - if (key.length() > eventValidationExtraKeyLength) { + if(key.length() > eventValidationExtraKeyLength) { logger::error("Invalid event extras: extra[{}] key length {} exceeds {}", index, key.length(), eventValidationExtraKeyLength); return false; } - if (value.length() > eventValidationExtraValueLength) { - logger::error("Invalid event extras: extra[{}] value length {} exceeds {}", - index, value.length(), eventValidationExtraValueLength); + if(value.length() > eventValidationExtraValueLength) { + logger::error("Invalid event extras: extra[{}] value length {} exceeds {}", index, value.length(), eventValidationExtraValueLength); return false; } index++; } // Associate files - if (inputEvent.associate_files_size() > eventValidationMaxAssociateFiles) { - logger::error("Invalid associate files: number of associate files {} exceeds {}", - inputEvent.associate_files_size(), eventValidationMaxAssociateFiles); + if(inputEvent.associate_files_size() > eventValidationMaxAssociateFiles) { + logger::error("Invalid associate files: number of associate files {} exceeds {}", inputEvent.associate_files_size(), eventValidationMaxAssociateFiles); return false; } From 94fd63467133ddc85fd43c7e0c503b8536217b99 Mon Sep 17 00:00:00 2001 From: AljazD Date: Mon, 13 Oct 2025 09:02:35 +0200 Subject: [PATCH 24/33] Updated handling of uploadFileBatch futures --- include/depthai/utility/EventsManager.hpp | 2 +- src/utility/EventsManager.cpp | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index f64f165530..ad992f8e2b 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -225,7 +225,7 @@ class EventsManager { std::unique_ptr uploadThread; std::deque> eventBuffer; std::deque> snapBuffer; - std::vector> uploadFileBatchFutures; + std::deque> uploadFileBatchFutures; std::mutex eventBufferMutex; std::mutex snapBufferMutex; std::mutex stopThreadConditionMutex; diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index d75056f6e8..e9157db35f 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -256,9 +256,18 @@ EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) snapBatch.insert(snapBatch.end(), std::make_move_iterator(snapBuffer.begin()), std::make_move_iterator(snapBuffer.begin() + size)); snapBuffer.erase(snapBuffer.begin(), snapBuffer.begin() + size); } - // TO DO: Handle the clearing of these futures + uploadFileBatchFutures.emplace_back( std::async(std::launch::async, [&, inputSnapBatch = std::move(snapBatch)]() mutable { uploadFileBatch(std::move(inputSnapBatch)); })); + // Clean up finished futures + for(auto iterator = uploadFileBatchFutures.begin(); iterator != uploadFileBatchFutures.end();) { + if(iterator->wait_for(std::chrono::seconds(0)) == std::future_status::ready) { + iterator->get(); + iterator = uploadFileBatchFutures.erase(iterator); + } else { + ++iterator; + } + } uploadEventBatch(); std::unique_lock lock(stopThreadConditionMutex); From 0096d240b5afc6d30b170f07c13586729cca7d95 Mon Sep 17 00:00:00 2001 From: Matevz Morato Date: Mon, 13 Oct 2025 14:55:57 +0200 Subject: [PATCH 25/33] Minor cleanup --- examples/cpp/Events/events.cpp | 2 +- examples/python/Events/events.py | 10 +++++----- src/utility/EventsManager.cpp | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/cpp/Events/events.cpp b/examples/cpp/Events/events.cpp index 98be0ebfec..a3fe32a00a 100644 --- a/examples/cpp/Events/events.cpp +++ b/examples/cpp/Events/events.cpp @@ -97,4 +97,4 @@ int main() { } return EXIT_SUCCESS; -} \ No newline at end of file +} diff --git a/examples/python/Events/events.py b/examples/python/Events/events.py index 075a834516..99fb8d2808 100644 --- a/examples/python/Events/events.py +++ b/examples/python/Events/events.py @@ -20,7 +20,7 @@ # Create output queues qRgb = detectionNetwork.passthrough.createOutputQueue() - qDet = detectionNetwork.out.createOutputQueue() + qDet = detectionNetwork.out.createOutputQueue() pipeline.start() @@ -30,7 +30,7 @@ def frameNorm(frame, bbox): normVals = np.full(len(bbox), frame.shape[0]) normVals[::2] = frame.shape[1] return (np.clip(np.array(bbox), 0, 1) * normVals).astype(int) - + counter = 0 while pipeline.isRunning(): @@ -73,14 +73,14 @@ def frameNorm(frame, bbox): for detection in inDet.detections: if detection.confidence > 0.5 and detection.confidence < 0.6: borderDetectionsList.append(detection) - + # Are there any border detections if len(borderDetectionsList) > 0: borderDetections = dai.ImgDetections() borderDetections.detections = borderDetectionsList fileName = f"ImageDetection_{counter}" - fileGroup.clearFiles(); + fileGroup.clearFiles() fileGroup.addImageDetectionsPair(fileName, inRgb, borderDetections) eventMan.sendSnap("ImageDetection", fileGroup, ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "") @@ -88,4 +88,4 @@ def frameNorm(frame, bbox): if cv2.waitKey(1) == ord("q"): pipeline.stop() - break \ No newline at end of file + break diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index e9157db35f..196523898a 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -17,7 +17,6 @@ namespace dai { namespace utility { -using std::move; template void addToFileData(std::vector>& container, Args&&... args) { From 0cb59a89d778784c3827260291cff28772c86ca2 Mon Sep 17 00:00:00 2001 From: AljazD Date: Mon, 13 Oct 2025 15:28:34 +0200 Subject: [PATCH 26/33] Added caching of events --- .../src/utility/EventsManagerBindings.cpp | 5 +- include/depthai/utility/EventsManager.hpp | 19 +--- src/utility/EventsManager.cpp | 105 ++++++++---------- 3 files changed, 54 insertions(+), 75 deletions(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 333337227d..72cc3bb093 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -90,10 +90,7 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { .def("setLogResponse", &EventsManager::setLogResponse, py::arg("logResponse"), DOC(dai, utility, EventsManager, setLogResponse)) .def("setVerifySsl", &EventsManager::setVerifySsl, py::arg("verifySsl"), DOC(dai, utility, EventsManager, setVerifySsl)) .def("setCacheDir", &EventsManager::setCacheDir, py::arg("cacheDir"), DOC(dai, utility, EventsManager, setCacheDir)) - .def("setCacheIfCannotSend", - &EventsManager::setCacheIfCannotSend, - py::arg("cacheIfCannotUpload"), - DOC(dai, utility, EventsManager, setCacheIfCannotSend)) + .def("setCacheOnExit", &EventsManager::setCacheOnExit, py::arg("cacheOnExit"), DOC(dai, utility, EventsManager, setCacheOnExit)) .def("uploadCachedData", &EventsManager::uploadCachedData, DOC(dai, utility, EventsManager, uploadCachedData)) .def("sendEvent", &EventsManager::sendEvent, diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index ad992f8e2b..1a87fd948f 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -142,27 +142,23 @@ class EventsManager { * @return void */ void setVerifySsl(bool verifySsl); - - // TO DO: Should be private? /** * Upload cached data to the events service * @return void */ void uploadCachedData(); - /** * Set the cache directory for storing cached data. By default, the cache directory is set to /internal/private * @param cacheDir Cache directory * @return void */ void setCacheDir(const std::string& cacheDir); - /** - * Set whether to cache data if it cannot be sent. By default, cacheIfCannotSend is set to false - * @param cacheIfCannotSend bool + * Set whether to cache data when exiting the application. By default, cacheOnExit is set to false + * @param cacheOnExit bool * @return void */ - void setCacheIfCannotSend(bool cacheIfCannotSend); + void setCacheOnExit(bool cacheOnExit); private: struct SnapData { @@ -205,13 +201,9 @@ class EventsManager { */ bool validateEvent(const proto::event::Event& inputEvent); /** - * // TO DO: Add description + * Cache events from the eventBuffer to the filesystem */ void cacheEvents(); - /** - * // TO DO: Add description - */ - bool checkForCachedData(); std::string token; std::string url; @@ -221,7 +213,7 @@ class EventsManager { bool logResponse; bool verifySsl; std::string cacheDir; - bool cacheIfCannotSend; + bool cacheOnExit; std::unique_ptr uploadThread; std::deque> eventBuffer; std::deque> snapBuffer; @@ -230,6 +222,7 @@ class EventsManager { std::mutex snapBufferMutex; std::mutex stopThreadConditionMutex; std::atomic stopUploadThread; + std::atomic configurationLimitsFetched; std::condition_variable eventBufferCondition; uint64_t maxFileSizeBytes; diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 196523898a..150aac6560 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -225,8 +225,9 @@ EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) logResponse(false), verifySsl(true), cacheDir("/internal/private"), - cacheIfCannotSend(false), + cacheOnExit(false), stopUploadThread(false), + configurationLimitsFetched(false), warningStorageBytes(52428800) { auto appId = utility::getEnvAs("OAKAGENT_APP_ID", ""); auto containerId = utility::getEnvAs("OAKAGENT_CONTAINER_ID", ""); @@ -236,10 +237,13 @@ EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) token = utility::getEnvAs("DEPTHAI_HUB_API_KEY", ""); // Thread handling preparation and uploads uploadThread = std::make_unique([this]() { - auto nextTime = std::chrono::steady_clock::now(); + // Fetch configuration limits when starting the new thread + configurationLimitsFetched = fetchConfigurationLimits(); + auto currentTime = std::chrono::steady_clock::now(); + auto nextTime = currentTime + std::chrono::hours(1); while(!stopUploadThread) { // Hourly check for fetching configuration and limits - auto currentTime = std::chrono::steady_clock::now(); + currentTime = std::chrono::steady_clock::now(); if(currentTime >= nextTime) { fetchConfigurationLimits(); nextTime += std::chrono::hours(1); @@ -272,6 +276,11 @@ EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) std::unique_lock lock(stopThreadConditionMutex); eventBufferCondition.wait_for(lock, std::chrono::seconds(static_cast(this->publishInterval)), [this]() { return stopUploadThread.load(); }); } + + // Cache events from eventBuffer + if(cacheOnExit) { + cacheEvents(); + } }); if(uploadCachedOnStart) { uploadCachedData(); @@ -291,8 +300,8 @@ bool EventsManager::fetchConfigurationLimits() { auto header = cpr::Header(); header["Authorization"] = "Bearer " + token; cpr::Url requestUrl = static_cast(this->url + "/v2/api-usage"); - // Might change to infinte retrying in the future - for(int i = 0; i < uploadRetryPolicy.maxAttempts && !stopUploadThread; i++) { + int retryAttempt = 0; + while(!stopUploadThread) { cpr::Response response = cpr::Get( cpr::Url{requestUrl}, cpr::Header{header}, @@ -313,9 +322,9 @@ bool EventsManager::fetchConfigurationLimits() { logger::error("Failed to fetch configuration limits, status code: {}", response.status_code); // Apply exponential backoff - auto factor = std::pow(uploadRetryPolicy.factor, i + 1); + auto factor = std::pow(uploadRetryPolicy.factor, ++retryAttempt); std::chrono::milliseconds duration = std::chrono::milliseconds(uploadRetryPolicy.baseDelay.count() * static_cast(factor)); - logger::info("Retrying to fetch configuration limits, (attempt {}/{}) in {} ms", i + 1, uploadRetryPolicy.maxAttempts, duration.count()); + logger::info("Retrying to fetch configuration limits, (attempt {} in {} ms)", retryAttempt, duration.count()); std::unique_lock lock(stopThreadConditionMutex); eventBufferCondition.wait_for(lock, duration, [this]() { return stopUploadThread.load(); }); @@ -565,6 +574,12 @@ bool EventsManager::sendEvent(const std::string& name, const std::unordered_map& extras, const std::string& deviceSerialNo, const std::vector& associateFiles) { + // Check if the configuration and limits have already been fetched + if(!configurationLimitsFetched) { + logger::error("The configuration and limits have not been successfully fetched, event not send"); + return false; + } + // Create an event auto event = std::make_unique(); event->set_created_at(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); @@ -599,6 +614,12 @@ bool EventsManager::sendSnap(const std::string& name, const std::vector& tags, const std::unordered_map& extras, const std::string& deviceSerialNo) { + // Check if the configuration and limits have already been fetched + if(!configurationLimitsFetched) { + logger::error("The configuration and limits have not been successfully fetched, snap not send"); + return false; + } + // Prepare snapData auto snapData = std::make_unique(); snapData->fileGroup = fileGroup; @@ -717,76 +738,44 @@ bool EventsManager::validateEvent(const proto::event::Event& inputEvent) { } void EventsManager::cacheEvents() { - /* + // Create a unique directory and save the protobuf message for each event in the eventBuffer logger::info("Caching events"); - // for each event, create a unique directory, save protobuf message and associated files - // TO DO: Make this using associate file field of proto::event::Event - std::lock_guard lock(uploadMutex); - for(auto& eventM : eventBuffer) { - auto& event = eventM->event; - auto& data = eventM->data; - std::filesystem::path p(cacheDir); - p = p / ("event_" + event->name() + "_" + event->nonce()); - std::string eventDir = p.string(); + std::lock_guard lock(eventBufferMutex); + for(const auto& event : eventBuffer) { + std::filesystem::path path(cacheDir); + path = path / ("event_" + event->name() + "_" + std::to_string(event->created_at())); + std::string eventDir = path.string(); logger::info("Caching event to {}", eventDir); if(!std::filesystem::exists(cacheDir)) { std::filesystem::create_directories(cacheDir); } std::filesystem::create_directory(eventDir); - std::ofstream eventFile(p / "event.pb", std::ios::binary); + std::ofstream eventFile(path / "event.pb", std::ios::binary); event->SerializeToOstream(&eventFile); - for(auto& file : data) { - file->toFile(eventDir); - } } eventBuffer.clear(); - */ } void EventsManager::uploadCachedData() { - /* - // iterate over all directories in cacheDir, read event.pb and associated files, and send them + // Iterate over the directories in cacheDir, read event.pb files and add events to eventBuffer logger::info("Uploading cached data"); - if(!checkConnection()) { - return; - } - // check if cacheDir exists if(!std::filesystem::exists(cacheDir)) { logger::warn("Cache directory does not exist"); return; } for(const auto& entry : std::filesystem::directory_iterator(cacheDir)) { - if(entry.is_directory()) { - const auto& eventDir = entry.path(); - std::ifstream eventFile(eventDir / "event.pb", std::ios::binary); - proto::event::Event event; - event.ParseFromIstream(&eventFile); - std::vector> data; - for(const auto& fileEntry : std::filesystem::directory_iterator(eventDir)) { - if(fileEntry.is_regular_file() && fileEntry.path() != eventDir / "event.pb") { - auto fileData = std::make_shared(fileEntry.path().string()); - data.push_back(fileData); - } - } - std::lock_guard lock(eventBufferMutex); - auto eventPtr = std::make_shared(event); - auto eventMessage = std::make_shared(); - eventMessage->event = eventPtr; - eventMessage->data = data; - eventMessage->cachePath = eventDir.string(); - eventBuffer.push_back(eventMessage); + if(!entry.is_directory()) { + continue; } - } - */ -} + const auto& eventDir = entry.path(); + std::ifstream eventFile(eventDir / "event.pb", std::ios::binary); + auto event = std::make_shared(); + event->ParseFromIstream(&eventFile); -bool EventsManager::checkForCachedData() { - if(!std::filesystem::exists(cacheDir)) { - logger::warn("Cache directory does not exist"); - return false; + // Add event to eventBuffer + std::lock_guard lock(eventBufferMutex); + eventBuffer.push_back(std::move(event)); } - return std::any_of( - std::filesystem::directory_iterator(cacheDir), std::filesystem::directory_iterator(), [](const auto& entry) { return entry.is_directory(); }); } void EventsManager::setCacheDir(const std::string& cacheDir) { @@ -805,8 +794,8 @@ void EventsManager::setVerifySsl(bool verifySsl) { this->verifySsl = verifySsl; } -void EventsManager::setCacheIfCannotSend(bool cacheIfCannotSend) { - this->cacheIfCannotSend = cacheIfCannotSend; +void EventsManager::setCacheOnExit(bool cacheOnExit) { + this->cacheOnExit = cacheOnExit; } } // namespace utility From 15a8366d367d16ca4cb7c999c34dec5badd8f0f6 Mon Sep 17 00:00:00 2001 From: AljazD Date: Tue, 14 Oct 2025 09:20:22 +0200 Subject: [PATCH 27/33] PR clean up and fixes --- .../src/utility/EventsManagerBindings.cpp | 40 ++++---- examples/cpp/Events/events.cpp | 2 +- examples/python/Events/events.py | 2 +- include/depthai/utility/EventsManager.hpp | 33 +++---- src/utility/EventsManager.cpp | 96 +++++++++---------- 5 files changed, 83 insertions(+), 90 deletions(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 72cc3bb093..28fba1be21 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -31,7 +31,7 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { py::arg("mimeType"), DOC(dai, utility, FileGroup, addFile)) .def("addFile", - static_cast(&FileGroup::addFile), + static_cast(&FileGroup::addFile), py::arg("fileName"), py::arg("filePath"), DOC(dai, utility, FileGroup, addFile)) @@ -45,11 +45,11 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { py::arg("fileName"), py::arg("encodedFrame"), DOC(dai, utility, FileGroup, addFile)) - .def("addFile", - static_cast&)>(&FileGroup::addFile), - py::arg("fileName"), - py::arg("nnData"), - DOC(dai, utility, FileGroup, addFile)) + //.def("addFile", + // static_cast&)>(&FileGroup::addFile), + // py::arg("fileName"), + // py::arg("nnData"), + // DOC(dai, utility, FileGroup, addFile)) .def("addFile", static_cast&)>(&FileGroup::addFile), py::arg("fileName"), @@ -68,20 +68,20 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { py::arg("fileName"), py::arg("encodedFrame"), py::arg("imgDetections"), - DOC(dai, utility, FileGroup, addImageDetectionsPair)) - .def("addImageNNDataPair", - static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), - py::arg("fileName"), - py::arg("imgFrame"), - py::arg("nnData"), - DOC(dai, utility, FileGroup, addImageNNDataPair)) - .def( - "addImageNNDataPair", - static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), - py::arg("fileName"), - py::arg("encodedFrame"), - py::arg("nnData"), - DOC(dai, utility, FileGroup, addImageNNDataPair)); + DOC(dai, utility, FileGroup, addImageDetectionsPair)); + //.def("addImageNNDataPair", + // static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), + // py::arg("fileName"), + // py::arg("imgFrame"), + // py::arg("nnData"), + // DOC(dai, utility, FileGroup, addImageNNDataPair)) + //.def( + // "addImageNNDataPair", + // static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), + // py::arg("fileName"), + // py::arg("encodedFrame"), + // py::arg("nnData"), + // DOC(dai, utility, FileGroup, addImageNNDataPair)); py::class_(m, "EventsManager") .def(py::init<>()) diff --git a/examples/cpp/Events/events.cpp b/examples/cpp/Events/events.cpp index a3fe32a00a..d8f35d82df 100644 --- a/examples/cpp/Events/events.cpp +++ b/examples/cpp/Events/events.cpp @@ -86,7 +86,7 @@ int main() { fileGroup->clearFiles(); fileGroup->addImageDetectionsPair(ss.str(), inRgb, borderDetections); - eventsManager->sendSnap("ImageDetection", fileGroup, {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, ""); + eventsManager->sendSnap("LowConfidenceDetection", fileGroup, {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, ""); counter++; } diff --git a/examples/python/Events/events.py b/examples/python/Events/events.py index 99fb8d2808..367c22004d 100644 --- a/examples/python/Events/events.py +++ b/examples/python/Events/events.py @@ -82,7 +82,7 @@ def frameNorm(frame, bbox): fileGroup.clearFiles() fileGroup.addImageDetectionsPair(fileName, inRgb, borderDetections) - eventMan.sendSnap("ImageDetection", fileGroup, ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "") + eventMan.sendSnap("LowConfidenceDetection", fileGroup, ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "") counter += 1 diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 1a87fd948f..24478841c1 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -30,21 +30,14 @@ namespace utility { class FileData { public: FileData(std::string data, std::string fileName, std::string mimeType); - explicit FileData(std::string filePath, std::string fileName); + explicit FileData(std::filesystem::path filePath, std::string fileName); explicit FileData(const std::shared_ptr& imgFrame, std::string fileName); explicit FileData(const std::shared_ptr& encodedFrame, std::string fileName); - explicit FileData(const std::shared_ptr& nnData, std::string fileName); + //explicit FileData(const std::shared_ptr& nnData, std::string fileName); explicit FileData(const std::shared_ptr& imgDetections, std::string fileName); bool toFile(const std::string& inputPath); private: - /** - * Calculate SHA256 checksum for the given data - * @param data Data for checksum calculation - * @return checksum string - */ - std::string calculateSHA256Checksum(const std::string& data); - std::string mimeType; std::string fileName; std::string data; @@ -58,15 +51,15 @@ class FileGroup { public: void clearFiles(); void addFile(std::string fileName, std::string data, std::string mimeType); - void addFile(std::string fileName, std::string filePath); + void addFile(std::string fileName, std::filesystem::path filePath); void addFile(std::string fileName, const std::shared_ptr& imgFrame); void addFile(std::string fileName, const std::shared_ptr& encodedFrame); - void addFile(std::string fileName, const std::shared_ptr& nnData); + //void addFile(std::string fileName, const std::shared_ptr& nnData); void addFile(std::string fileName, const std::shared_ptr& imgDetections); void addImageDetectionsPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections); void addImageDetectionsPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections); - void addImageNNDataPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections); - void addImageNNDataPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections); + //void addImageNNDataPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections); + //void addImageNNDataPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections); private: std::vector> fileData; @@ -238,13 +231,13 @@ class EventsManager { UploadRetryPolicy uploadRetryPolicy; - static constexpr int eventValidationNameLength = 56; - static constexpr int eventValidationMaxTags = 20; - static constexpr int eventValidationTagLength = 56; - static constexpr int eventValidationMaxExtras = 25; - static constexpr int eventValidationExtraKeyLength = 40; - static constexpr int eventValidationExtraValueLength = 100; - static constexpr int eventValidationMaxAssociateFiles = 20; + static constexpr int EVENT_VALIDATION_NAME_LENGTH = 56; + static constexpr int EVENT_VALIDATION_MAX_TAGS = 20; + static constexpr int EVENT_VALIDATION_TAG_LENGTH = 56; + static constexpr int EVENT_VALIDATION_MAX_EXTRAS = 25; + static constexpr int EVENT_VALIDATION_EXTRA_KEY_LENGTH = 40; + static constexpr int EVENT_VALIDATION_EXTRA_VALUE_LENGTH = 100; + static constexpr int EVENT_VALIDATION_MAX_ASSOCIATE_FILES = 20; }; } // namespace utility } // namespace dai diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 150aac6560..a467947831 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -31,7 +31,7 @@ void FileGroup::addFile(std::string fileName, std::string data, std::string mime addToFileData(fileData, std::move(data), std::move(fileName), std::move(mimeType)); } -void FileGroup::addFile(std::string fileName, std::string filePath) { +void FileGroup::addFile(std::string fileName, std::filesystem::path filePath) { addToFileData(fileData, std::move(filePath), std::move(fileName)); } @@ -43,9 +43,9 @@ void FileGroup::addFile(std::string fileName, const std::shared_ptr(fileData, encodedFrame, std::move(fileName)); } -void FileGroup::addFile(std::string fileName, const std::shared_ptr& nnData) { - addToFileData(fileData, nnData, std::move(fileName)); -} +//void FileGroup::addFile(std::string fileName, const std::shared_ptr& nnData) { +// addToFileData(fileData, nnData, std::move(fileName)); +//} void FileGroup::addFile(std::string fileName, const std::shared_ptr& imgDetections) { addToFileData(fileData, imgDetections, std::move(fileName)); @@ -63,14 +63,25 @@ void FileGroup::addImageDetectionsPair(std::string fileName, addToFileData(fileData, imgDetections, std::move(fileName)); } -void FileGroup::addImageNNDataPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& nnData) { - addToFileData(fileData, imgFrame, std::move(fileName)); - addToFileData(fileData, nnData, std::move(fileName)); -} +//void FileGroup::addImageNNDataPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& nnData) { +// addToFileData(fileData, imgFrame, std::move(fileName)); +// addToFileData(fileData, nnData, std::move(fileName)); +//} -void FileGroup::addImageNNDataPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& nnData) { - addToFileData(fileData, encodedFrame, std::move(fileName)); - addToFileData(fileData, nnData, std::move(fileName)); +//void FileGroup::addImageNNDataPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& nnData) { +// addToFileData(fileData, encodedFrame, std::move(fileName)); +// addToFileData(fileData, nnData, std::move(fileName)); +//} + +std::string calculateSHA256Checksum(const std::string& data) { + unsigned char digest[SHA256_DIGEST_LENGTH]; + SHA256(reinterpret_cast(data.data()), data.size(), digest); + + std::ostringstream oss; + for(int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(digest[i]); + } + return oss.str(); } FileData::FileData(std::string data, std::string fileName, std::string mimeType) @@ -81,7 +92,7 @@ FileData::FileData(std::string data, std::string fileName, std::string mimeType) checksum(calculateSHA256Checksum(data)), classification(proto::event::PrepareFileUploadClass::UNKNOWN_FILE) {} -FileData::FileData(std::string filePath, std::string fileName) : fileName(std::move(fileName)) { +FileData::FileData(std::filesystem::path filePath, std::string fileName) : fileName(std::move(fileName)) { static const std::unordered_map mimeTypeExtensionMap = {{".html", "text/html"}, {".htm", "text/html"}, {".css", "text/css"}, @@ -96,7 +107,7 @@ FileData::FileData(std::string filePath, std::string fileName) : fileName(std::m // Read the data std::ifstream fileStream(filePath, std::ios::binary | std::ios::ate); if(!fileStream) { - logger::error("File: {} doesn't exist", filePath); + logger::error("File: {} doesn't exist", filePath.string()); return; } std::streamsize fileSize = fileStream.tellg(); @@ -106,7 +117,7 @@ FileData::FileData(std::string filePath, std::string fileName) : fileName(std::m size = data.size(); checksum = calculateSHA256Checksum(data); // Determine the mime type - auto it = mimeTypeExtensionMap.find(std::filesystem::path(filePath).extension().string()); + auto it = mimeTypeExtensionMap.find(filePath.extension().string()); if(it != mimeTypeExtensionMap.end()) { mimeType = it->second; } else { @@ -148,15 +159,15 @@ FileData::FileData(const std::shared_ptr& encodedFrame, std::strin checksum = calculateSHA256Checksum(data); } -FileData::FileData(const std::shared_ptr& nnData, std::string fileName) - : mimeType("application/octet-stream"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::UNKNOWN_FILE) { - // Convert NNData to bytes - std::stringstream ss; - ss.write((const char*)nnData->data->getData().data(), nnData->data->getData().size()); - data = ss.str(); - size = data.size(); - checksum = calculateSHA256Checksum(data); -} +//FileData::FileData(const std::shared_ptr& nnData, std::string fileName) +// : mimeType("application/octet-stream"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::UNKNOWN_FILE) { +// // Convert NNData to bytes +// std::stringstream ss; +// ss.write((const char*)nnData->data->getData().data(), nnData->data->getData().size()); +// data = ss.str(); +// size = data.size(); +// checksum = calculateSHA256Checksum(data); +//} FileData::FileData(const std::shared_ptr& imgDetections, std::string fileName) : mimeType("application/x-protobuf; proto=SnapAnnotation"), @@ -209,17 +220,6 @@ bool FileData::toFile(const std::string& inputPath) { return true; } -std::string FileData::calculateSHA256Checksum(const std::string& data) { - unsigned char digest[SHA256_DIGEST_LENGTH]; - SHA256(reinterpret_cast(data.data()), data.size(), digest); - - std::ostringstream oss; - for(int i = 0; i < SHA256_DIGEST_LENGTH; ++i) { - oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(digest[i]); - } - return oss.str(); -} - EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) : publishInterval(publishInterval), logResponse(false), @@ -682,14 +682,14 @@ bool EventsManager::validateEvent(const proto::event::Event& inputEvent) { logger::error("Invalid event name: empty string"); return false; } - if(name.length() > eventValidationNameLength) { - logger::error("Invalid event name: length {} exceeds {}", name.length(), eventValidationNameLength); + if(name.length() > EVENT_VALIDATION_NAME_LENGTH) { + logger::error("Invalid event name: length {} exceeds {}", name.length(), EVENT_VALIDATION_NAME_LENGTH); return false; } // Tags - if(inputEvent.tags_size() > eventValidationMaxTags) { - logger::error("Invalid event tags: number of tags {} exceeds {}", inputEvent.tags_size(), eventValidationMaxTags); + if(inputEvent.tags_size() > EVENT_VALIDATION_MAX_TAGS) { + logger::error("Invalid event tags: number of tags {} exceeds {}", inputEvent.tags_size(), EVENT_VALIDATION_MAX_TAGS); return false; } for(int i = 0; i < inputEvent.tags_size(); ++i) { @@ -698,15 +698,15 @@ bool EventsManager::validateEvent(const proto::event::Event& inputEvent) { logger::error("Invalid event tags: tag[{}] empty string", i); return false; } - if(tag.length() > 56) { - logger::error("Invalid event tags: tag[{}] length {} exceeds {}", i, tag.length(), eventValidationTagLength); + if(tag.length() > EVENT_VALIDATION_TAG_LENGTH) { + logger::error("Invalid event tags: tag[{}] length {} exceeds {}", i, tag.length(), EVENT_VALIDATION_TAG_LENGTH); return false; } } // Event extras - if(inputEvent.extras_size() > eventValidationMaxExtras) { - logger::error("Invalid event extras: number of extras {} exceeds {}", inputEvent.extras_size(), eventValidationMaxExtras); + if(inputEvent.extras_size() > EVENT_VALIDATION_MAX_EXTRAS) { + logger::error("Invalid event extras: number of extras {} exceeds {}", inputEvent.extras_size(), EVENT_VALIDATION_MAX_EXTRAS); return false; } int index = 0; @@ -717,20 +717,20 @@ bool EventsManager::validateEvent(const proto::event::Event& inputEvent) { logger::error("Invalid event extras: extra[{}] key empty string", index); return false; } - if(key.length() > eventValidationExtraKeyLength) { - logger::error("Invalid event extras: extra[{}] key length {} exceeds {}", index, key.length(), eventValidationExtraKeyLength); + if(key.length() > EVENT_VALIDATION_EXTRA_KEY_LENGTH) { + logger::error("Invalid event extras: extra[{}] key length {} exceeds {}", index, key.length(), EVENT_VALIDATION_EXTRA_KEY_LENGTH); return false; } - if(value.length() > eventValidationExtraValueLength) { - logger::error("Invalid event extras: extra[{}] value length {} exceeds {}", index, value.length(), eventValidationExtraValueLength); + if(value.length() > EVENT_VALIDATION_EXTRA_VALUE_LENGTH) { + logger::error("Invalid event extras: extra[{}] value length {} exceeds {}", index, value.length(), EVENT_VALIDATION_EXTRA_VALUE_LENGTH); return false; } index++; } // Associate files - if(inputEvent.associate_files_size() > eventValidationMaxAssociateFiles) { - logger::error("Invalid associate files: number of associate files {} exceeds {}", inputEvent.associate_files_size(), eventValidationMaxAssociateFiles); + if(inputEvent.associate_files_size() > EVENT_VALIDATION_MAX_ASSOCIATE_FILES) { + logger::error("Invalid associate files: number of associate files {} exceeds {}", inputEvent.associate_files_size(), EVENT_VALIDATION_MAX_ASSOCIATE_FILES); return false; } From 622de45d92f40751faeddd6e4cd639525b039319 Mon Sep 17 00:00:00 2001 From: AljazD Date: Tue, 14 Oct 2025 11:43:33 +0200 Subject: [PATCH 28/33] Updated events examples --- .../src/utility/EventsManagerBindings.cpp | 27 +++-- examples/cpp/Events/CMakeLists.txt | 1 + examples/cpp/Events/events.cpp | 27 ++--- examples/cpp/Events/events_file_group.cpp | 98 +++++++++++++++++++ examples/python/Events/events.py | 30 ++---- examples/python/Events/events_file_group.py | 89 +++++++++++++++++ include/depthai/utility/EventsManager.hpp | 9 +- src/utility/EventsManager.cpp | 47 +++++---- 8 files changed, 242 insertions(+), 86 deletions(-) create mode 100644 examples/cpp/Events/events_file_group.cpp create mode 100644 examples/python/Events/events_file_group.py diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 28fba1be21..7fc593a953 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -23,7 +23,6 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { using namespace dai::utility; py::class_>(m, "FileGroup") .def(py::init<>()) - .def("clearFiles", &FileGroup::clearFiles, DOC(dai, utility, FileGroup, clearFiles)) .def("addFile", static_cast(&FileGroup::addFile), py::arg("fileName"), @@ -69,19 +68,19 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { py::arg("encodedFrame"), py::arg("imgDetections"), DOC(dai, utility, FileGroup, addImageDetectionsPair)); - //.def("addImageNNDataPair", - // static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), - // py::arg("fileName"), - // py::arg("imgFrame"), - // py::arg("nnData"), - // DOC(dai, utility, FileGroup, addImageNNDataPair)) - //.def( - // "addImageNNDataPair", - // static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), - // py::arg("fileName"), - // py::arg("encodedFrame"), - // py::arg("nnData"), - // DOC(dai, utility, FileGroup, addImageNNDataPair)); + //.def("addImageNNDataPair", + // static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), + // py::arg("fileName"), + // py::arg("imgFrame"), + // py::arg("nnData"), + // DOC(dai, utility, FileGroup, addImageNNDataPair)) + //.def( + // "addImageNNDataPair", + // static_cast&, const std::shared_ptr&)>(&FileGroup::addImageNNDataPair), + // py::arg("fileName"), + // py::arg("encodedFrame"), + // py::arg("nnData"), + // DOC(dai, utility, FileGroup, addImageNNDataPair)); py::class_(m, "EventsManager") .def(py::init<>()) diff --git a/examples/cpp/Events/CMakeLists.txt b/examples/cpp/Events/CMakeLists.txt index f9824550ad..f5298decdc 100644 --- a/examples/cpp/Events/CMakeLists.txt +++ b/examples/cpp/Events/CMakeLists.txt @@ -6,4 +6,5 @@ cmake_minimum_required(VERSION 3.10) if(DEPTHAI_ENABLE_EVENTS_MANAGER) dai_add_example(events events.cpp OFF OFF) + dai_add_example(events_file_group events_file_group.cpp OFF OFF) endif() \ No newline at end of file diff --git a/examples/cpp/Events/events.cpp b/examples/cpp/Events/events.cpp index d8f35d82df..9cb4ede7f9 100644 --- a/examples/cpp/Events/events.cpp +++ b/examples/cpp/Events/events.cpp @@ -18,8 +18,6 @@ int main() { // Enter you hub team's api-key auto eventsManager = std::make_shared(); eventsManager->setToken(""); - eventsManager->setLogResponse(false); - auto fileGroup = std::make_shared(); auto camRgb = pipeline.create()->build(); auto detectionNetwork = pipeline.create(); @@ -37,6 +35,10 @@ int main() { int counter = 0; while(pipeline.isRunning()) { + if(cv::waitKey(1) == 'q') { + break; + } + auto inRgb = qRgb->get(); auto inDet = qDet->get(); if(inRgb == nullptr || inDet == nullptr) { @@ -70,30 +72,15 @@ int main() { cv::imshow("rgb", frame); } - // Suppose we are only interested in the detections with confidence between 50% and 60% - auto borderDetections = std::make_shared(); - for(const auto& detection : inDet->detections) { - if(detection.confidence > 0.5f && detection.confidence < 0.6f) { - borderDetections->detections.emplace_back(detection); - } - } - - // Are there any border detections - if(borderDetections->detections.size() > 0) { + // Trigger sendSnap() + if(cv::waitKey(1) == 's') { std::string fileName = "ImageDetection_"; std::stringstream ss; ss << fileName << counter; - fileGroup->clearFiles(); - fileGroup->addImageDetectionsPair(ss.str(), inRgb, borderDetections); - eventsManager->sendSnap("LowConfidenceDetection", fileGroup, {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, ""); - + eventsManager->sendSnap("ImageDetection", ss.str(), inRgb, inDet, {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}); counter++; } - - if(cv::waitKey(1) == 'q') { - break; - } } return EXIT_SUCCESS; diff --git a/examples/cpp/Events/events_file_group.cpp b/examples/cpp/Events/events_file_group.cpp new file mode 100644 index 0000000000..6200a56214 --- /dev/null +++ b/examples/cpp/Events/events_file_group.cpp @@ -0,0 +1,98 @@ +#include +#include +#include +#include + +#include "depthai/depthai.hpp" +#include "depthai/utility/EventsManager.hpp" + +// Helper function to normalize frame coordinates +cv::Rect frameNorm(const cv::Mat& frame, const dai::Point2f& topLeft, const dai::Point2f& bottomRight) { + float width = frame.cols, height = frame.rows; + return cv::Rect(cv::Point(topLeft.x * width, topLeft.y * height), cv::Point(bottomRight.x * width, bottomRight.y * height)); +} + +int main() { + dai::Pipeline pipeline(true); + + // Enter you hub team's api-key + auto eventsManager = std::make_shared(); + eventsManager->setToken(""); + + auto camRgb = pipeline.create()->build(); + auto detectionNetwork = pipeline.create(); + + dai::NNModelDescription modelDescription; + modelDescription.model = "yolov6-nano"; + detectionNetwork->build(camRgb, modelDescription); + auto labelMap = detectionNetwork->getClasses(); + + // Create output queues + auto qRgb = detectionNetwork->passthrough.createOutputQueue(); + auto qDet = detectionNetwork->out.createOutputQueue(); + + pipeline.start(); + + int counter = 0; + while(pipeline.isRunning()) { + if(cv::waitKey(1) == 'q') { + break; + } + + auto inRgb = qRgb->get(); + auto inDet = qDet->get(); + if(inRgb == nullptr || inDet == nullptr) { + continue; + } + + // Display the video stream and detections + cv::Mat frame = inRgb->getCvFrame(); + if(!frame.empty()) { + // Display detections + for(const auto& detection : inDet->detections) { + auto bbox = frameNorm(frame, dai::Point2f(detection.xmin, detection.ymin), dai::Point2f(detection.xmax, detection.ymax)); + + // Draw label + cv::putText( + frame, labelMap.value()[detection.label], cv::Point(bbox.x + 10, bbox.y + 20), cv::FONT_HERSHEY_TRIPLEX, 0.5, cv::Scalar(255, 255, 255)); + + // Draw confidence + cv::putText(frame, + std::to_string(static_cast(detection.confidence * 100)) + "%", + cv::Point(bbox.x + 10, bbox.y + 40), + cv::FONT_HERSHEY_TRIPLEX, + 0.5, + cv::Scalar(255, 255, 255)); + + // Draw rectangle + cv::rectangle(frame, bbox, cv::Scalar(255, 0, 0), 2); + } + + // Show the frame + cv::imshow("rgb", frame); + } + + // Suppose we are only interested in the detections with confidence between 50% and 60% + auto borderDetections = std::make_shared(); + for(const auto& detection : inDet->detections) { + if(detection.confidence > 0.5f && detection.confidence < 0.6f) { + borderDetections->detections.emplace_back(detection); + } + } + + // Are there any border detections + if(borderDetections->detections.size() > 0) { + std::string fileName = "ImageDetection_"; + std::stringstream ss; + ss << fileName << counter; + + auto fileGroup = std::make_shared(); + fileGroup->addImageDetectionsPair(ss.str(), inRgb, borderDetections); + eventsManager->sendSnap("LowConfidenceDetection", fileGroup, {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, ""); + + counter++; + } + } + + return EXIT_SUCCESS; +} diff --git a/examples/python/Events/events.py b/examples/python/Events/events.py index 367c22004d..0643511287 100644 --- a/examples/python/Events/events.py +++ b/examples/python/Events/events.py @@ -11,8 +11,6 @@ # Enter you hub team's api-key eventMan = dai.EventsManager() eventMan.setToken("") - eventMan.setLogResponse(False) - fileGroup = dai.FileGroup() cameraNode = pipeline.create(dai.node.Camera).build() detectionNetwork = pipeline.create(dai.node.DetectionNetwork).build(cameraNode, dai.NNModelDescription("yolov6-nano")) @@ -34,6 +32,10 @@ def frameNorm(frame, bbox): counter = 0 while pipeline.isRunning(): + if cv2.waitKey(1) == ord("q"): + pipeline.stop() + break + inRgb: dai.ImgFrame = qRgb.get() inDet: dai.ImgDetections = qDet.get() if inRgb is None or inDet is None: @@ -68,24 +70,8 @@ def frameNorm(frame, bbox): # Show the frame cv2.imshow("rgb", frame) - # Suppose we are only interested in the detections with confidence between 50% and 60% - borderDetectionsList = [] - for detection in inDet.detections: - if detection.confidence > 0.5 and detection.confidence < 0.6: - borderDetectionsList.append(detection) - - # Are there any border detections - if len(borderDetectionsList) > 0: - borderDetections = dai.ImgDetections() - borderDetections.detections = borderDetectionsList + # Trigger sendSnap() + if cv2.waitKey(1) == ord("s"): fileName = f"ImageDetection_{counter}" - - fileGroup.clearFiles() - fileGroup.addImageDetectionsPair(fileName, inRgb, borderDetections) - eventMan.sendSnap("LowConfidenceDetection", fileGroup, ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "") - - counter += 1 - - if cv2.waitKey(1) == ord("q"): - pipeline.stop() - break + eventMan.sendSnap("ImageDetection", fileName, inRgb, inDet, ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "") + counter += 1 \ No newline at end of file diff --git a/examples/python/Events/events_file_group.py b/examples/python/Events/events_file_group.py new file mode 100644 index 0000000000..42d840bc2f --- /dev/null +++ b/examples/python/Events/events_file_group.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 + +import cv2 +import depthai as dai +import numpy as np +import time + + +# Create pipeline +with dai.Pipeline() as pipeline: + # Enter you hub team's api-key + eventMan = dai.EventsManager() + eventMan.setToken("") + + cameraNode = pipeline.create(dai.node.Camera).build() + detectionNetwork = pipeline.create(dai.node.DetectionNetwork).build(cameraNode, dai.NNModelDescription("yolov6-nano")) + labelMap = detectionNetwork.getClasses() + + # Create output queues + qRgb = detectionNetwork.passthrough.createOutputQueue() + qDet = detectionNetwork.out.createOutputQueue() + + pipeline.start() + + + # nn data, being the bounding box locations, are in <0..1> range - they need to be normalized with frame width/height + def frameNorm(frame, bbox): + normVals = np.full(len(bbox), frame.shape[0]) + normVals[::2] = frame.shape[1] + return (np.clip(np.array(bbox), 0, 1) * normVals).astype(int) + + + counter = 0 + while pipeline.isRunning(): + if cv2.waitKey(1) == ord("q"): + pipeline.stop() + break + + inRgb: dai.ImgFrame = qRgb.get() + inDet: dai.ImgDetections = qDet.get() + if inRgb is None or inDet is None: + continue + + # Display the video stream and detections + color = (255, 0, 0) + frame = inRgb.getCvFrame() + if frame is not None: + for detection in inDet.detections: + bbox = frameNorm( + frame, + (detection.xmin, detection.ymin, detection.xmax, detection.ymax), + ) + cv2.putText( + frame, + labelMap[detection.label], + (bbox[0] + 10, bbox[1] + 20), + cv2.FONT_HERSHEY_TRIPLEX, + 0.5, + 255, + ) + cv2.putText( + frame, + f"{int(detection.confidence * 100)}%", + (bbox[0] + 10, bbox[1] + 40), + cv2.FONT_HERSHEY_TRIPLEX, + 0.5, + 255, + ) + cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), color, 2) + # Show the frame + cv2.imshow("rgb", frame) + + # Suppose we are only interested in the detections with confidence between 50% and 60% + borderDetectionsList = [] + for detection in inDet.detections: + if detection.confidence > 0.5 and detection.confidence < 0.6: + borderDetectionsList.append(detection) + + # Are there any border detections + if len(borderDetectionsList) > 0: + borderDetections = dai.ImgDetections() + borderDetections.detections = borderDetectionsList + fileName = f"ImageDetection_{counter}" + + fileGroup = dai.FileGroup() + fileGroup.addImageDetectionsPair(fileName, inRgb, borderDetections) + eventMan.sendSnap("LowConfidenceDetection", fileGroup, ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "") + + counter += 1 \ No newline at end of file diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 24478841c1..5e7ecaa3c5 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -33,7 +33,7 @@ class FileData { explicit FileData(std::filesystem::path filePath, std::string fileName); explicit FileData(const std::shared_ptr& imgFrame, std::string fileName); explicit FileData(const std::shared_ptr& encodedFrame, std::string fileName); - //explicit FileData(const std::shared_ptr& nnData, std::string fileName); + // explicit FileData(const std::shared_ptr& nnData, std::string fileName); explicit FileData(const std::shared_ptr& imgDetections, std::string fileName); bool toFile(const std::string& inputPath); @@ -49,17 +49,16 @@ class FileData { class FileGroup { public: - void clearFiles(); void addFile(std::string fileName, std::string data, std::string mimeType); void addFile(std::string fileName, std::filesystem::path filePath); void addFile(std::string fileName, const std::shared_ptr& imgFrame); void addFile(std::string fileName, const std::shared_ptr& encodedFrame); - //void addFile(std::string fileName, const std::shared_ptr& nnData); + // void addFile(std::string fileName, const std::shared_ptr& nnData); void addFile(std::string fileName, const std::shared_ptr& imgDetections); void addImageDetectionsPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections); void addImageDetectionsPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections); - //void addImageNNDataPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections); - //void addImageNNDataPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections); + // void addImageNNDataPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections); + // void addImageNNDataPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections); private: std::vector> fileData; diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index a467947831..fef41181ce 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -23,10 +23,6 @@ void addToFileData(std::vector>& container, Args&&... container.emplace_back(std::make_shared(std::forward(args)...)); } -void FileGroup::clearFiles() { - fileData.clear(); -} - void FileGroup::addFile(std::string fileName, std::string data, std::string mimeType) { addToFileData(fileData, std::move(data), std::move(fileName), std::move(mimeType)); } @@ -43,9 +39,9 @@ void FileGroup::addFile(std::string fileName, const std::shared_ptr(fileData, encodedFrame, std::move(fileName)); } -//void FileGroup::addFile(std::string fileName, const std::shared_ptr& nnData) { -// addToFileData(fileData, nnData, std::move(fileName)); -//} +// void FileGroup::addFile(std::string fileName, const std::shared_ptr& nnData) { +// addToFileData(fileData, nnData, std::move(fileName)); +// } void FileGroup::addFile(std::string fileName, const std::shared_ptr& imgDetections) { addToFileData(fileData, imgDetections, std::move(fileName)); @@ -63,15 +59,15 @@ void FileGroup::addImageDetectionsPair(std::string fileName, addToFileData(fileData, imgDetections, std::move(fileName)); } -//void FileGroup::addImageNNDataPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& nnData) { -// addToFileData(fileData, imgFrame, std::move(fileName)); -// addToFileData(fileData, nnData, std::move(fileName)); -//} +// void FileGroup::addImageNNDataPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& nnData) { +// addToFileData(fileData, imgFrame, std::move(fileName)); +// addToFileData(fileData, nnData, std::move(fileName)); +// } -//void FileGroup::addImageNNDataPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& nnData) { -// addToFileData(fileData, encodedFrame, std::move(fileName)); -// addToFileData(fileData, nnData, std::move(fileName)); -//} +// void FileGroup::addImageNNDataPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& nnData) { +// addToFileData(fileData, encodedFrame, std::move(fileName)); +// addToFileData(fileData, nnData, std::move(fileName)); +// } std::string calculateSHA256Checksum(const std::string& data) { unsigned char digest[SHA256_DIGEST_LENGTH]; @@ -159,15 +155,15 @@ FileData::FileData(const std::shared_ptr& encodedFrame, std::strin checksum = calculateSHA256Checksum(data); } -//FileData::FileData(const std::shared_ptr& nnData, std::string fileName) -// : mimeType("application/octet-stream"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::UNKNOWN_FILE) { -// // Convert NNData to bytes -// std::stringstream ss; -// ss.write((const char*)nnData->data->getData().data(), nnData->data->getData().size()); -// data = ss.str(); -// size = data.size(); -// checksum = calculateSHA256Checksum(data); -//} +// FileData::FileData(const std::shared_ptr& nnData, std::string fileName) +// : mimeType("application/octet-stream"), fileName(std::move(fileName)), classification(proto::event::PrepareFileUploadClass::UNKNOWN_FILE) { +// // Convert NNData to bytes +// std::stringstream ss; +// ss.write((const char*)nnData->data->getData().data(), nnData->data->getData().size()); +// data = ss.str(); +// size = data.size(); +// checksum = calculateSHA256Checksum(data); +// } FileData::FileData(const std::shared_ptr& imgDetections, std::string fileName) : mimeType("application/x-protobuf; proto=SnapAnnotation"), @@ -730,7 +726,8 @@ bool EventsManager::validateEvent(const proto::event::Event& inputEvent) { // Associate files if(inputEvent.associate_files_size() > EVENT_VALIDATION_MAX_ASSOCIATE_FILES) { - logger::error("Invalid associate files: number of associate files {} exceeds {}", inputEvent.associate_files_size(), EVENT_VALIDATION_MAX_ASSOCIATE_FILES); + logger::error( + "Invalid associate files: number of associate files {} exceeds {}", inputEvent.associate_files_size(), EVENT_VALIDATION_MAX_ASSOCIATE_FILES); return false; } From f33d957d01fafb67faf942e63040ad6c38bc7c0a Mon Sep 17 00:00:00 2001 From: AljazD Date: Tue, 14 Oct 2025 14:13:07 +0200 Subject: [PATCH 29/33] FileName is now optional when adding to fileGroups or sending snaps --- .../src/utility/EventsManagerBindings.cpp | 14 +++---- examples/cpp/Events/events.cpp | 8 +--- examples/cpp/Events/events_file_group.cpp | 2 +- examples/python/Events/events.py | 5 +-- examples/python/Events/events_file_group.py | 2 +- include/depthai/utility/EventsManager.hpp | 18 ++++---- src/utility/EventsManager.cpp | 41 ++++++++++++------- 7 files changed, 48 insertions(+), 42 deletions(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 7fc593a953..7b27c32126 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -35,12 +35,12 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { py::arg("filePath"), DOC(dai, utility, FileGroup, addFile)) .def("addFile", - static_cast&)>(&FileGroup::addFile), + static_cast&, const std::shared_ptr&)>(&FileGroup::addFile), py::arg("fileName"), py::arg("imgFrame"), DOC(dai, utility, FileGroup, addFile)) .def("addFile", - static_cast&)>(&FileGroup::addFile), + static_cast&, const std::shared_ptr&)>(&FileGroup::addFile), py::arg("fileName"), py::arg("encodedFrame"), DOC(dai, utility, FileGroup, addFile)) @@ -50,19 +50,19 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { // py::arg("nnData"), // DOC(dai, utility, FileGroup, addFile)) .def("addFile", - static_cast&)>(&FileGroup::addFile), + static_cast&, const std::shared_ptr&)>(&FileGroup::addFile), py::arg("fileName"), py::arg("imgDetections"), DOC(dai, utility, FileGroup, addFile)) .def("addImageDetectionsPair", - static_cast&, const std::shared_ptr&)>( + static_cast&, const std::shared_ptr&, const std::shared_ptr&)>( &FileGroup::addImageDetectionsPair), py::arg("fileName"), py::arg("imgFrame"), py::arg("imgDetections"), DOC(dai, utility, FileGroup, addImageDetectionsPair)) .def("addImageDetectionsPair", - static_cast&, const std::shared_ptr&)>( + static_cast&, const std::shared_ptr&, const std::shared_ptr&)>( &FileGroup::addImageDetectionsPair), py::arg("fileName"), py::arg("encodedFrame"), @@ -113,9 +113,9 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { DOC(dai, utility, EventsManager, sendSnap)) .def("sendSnap", static_cast&, const std::shared_ptr, - const std::shared_ptr, + const std::optional>&, const std::vector&, const std::unordered_map&, const std::string&)>(&EventsManager::sendSnap), diff --git a/examples/cpp/Events/events.cpp b/examples/cpp/Events/events.cpp index 9cb4ede7f9..9c58b27aaf 100644 --- a/examples/cpp/Events/events.cpp +++ b/examples/cpp/Events/events.cpp @@ -33,7 +33,6 @@ int main() { pipeline.start(); - int counter = 0; while(pipeline.isRunning()) { if(cv::waitKey(1) == 'q') { break; @@ -74,12 +73,7 @@ int main() { // Trigger sendSnap() if(cv::waitKey(1) == 's') { - std::string fileName = "ImageDetection_"; - std::stringstream ss; - ss << fileName << counter; - - eventsManager->sendSnap("ImageDetection", ss.str(), inRgb, inDet, {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}); - counter++; + eventsManager->sendSnap("ImageDetection", std::nullopt, inRgb, inDet, {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}); } } diff --git a/examples/cpp/Events/events_file_group.cpp b/examples/cpp/Events/events_file_group.cpp index 6200a56214..286ea0ebf2 100644 --- a/examples/cpp/Events/events_file_group.cpp +++ b/examples/cpp/Events/events_file_group.cpp @@ -88,7 +88,7 @@ int main() { auto fileGroup = std::make_shared(); fileGroup->addImageDetectionsPair(ss.str(), inRgb, borderDetections); - eventsManager->sendSnap("LowConfidenceDetection", fileGroup, {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}, ""); + eventsManager->sendSnap("LowConfidenceDetection", fileGroup, {"EventsExample", "C++"}, {{"key_0", "value_0"}, {"key_1", "value_1"}}); counter++; } diff --git a/examples/python/Events/events.py b/examples/python/Events/events.py index 0643511287..27544e3ee5 100644 --- a/examples/python/Events/events.py +++ b/examples/python/Events/events.py @@ -30,7 +30,6 @@ def frameNorm(frame, bbox): return (np.clip(np.array(bbox), 0, 1) * normVals).astype(int) - counter = 0 while pipeline.isRunning(): if cv2.waitKey(1) == ord("q"): pipeline.stop() @@ -72,6 +71,4 @@ def frameNorm(frame, bbox): # Trigger sendSnap() if cv2.waitKey(1) == ord("s"): - fileName = f"ImageDetection_{counter}" - eventMan.sendSnap("ImageDetection", fileName, inRgb, inDet, ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "") - counter += 1 \ No newline at end of file + eventMan.sendSnap("ImageDetection", None, inRgb, inDet, ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1"}) \ No newline at end of file diff --git a/examples/python/Events/events_file_group.py b/examples/python/Events/events_file_group.py index 42d840bc2f..3cb21aea4e 100644 --- a/examples/python/Events/events_file_group.py +++ b/examples/python/Events/events_file_group.py @@ -84,6 +84,6 @@ def frameNorm(frame, bbox): fileGroup = dai.FileGroup() fileGroup.addImageDetectionsPair(fileName, inRgb, borderDetections) - eventMan.sendSnap("LowConfidenceDetection", fileGroup, ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1", "key_2" : "value_2"}, "") + eventMan.sendSnap("LowConfidenceDetection", fileGroup, ["EventsExample", "Python"], {"key_0" : "value_0", "key_1" : "value_1"}) counter += 1 \ No newline at end of file diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 5e7ecaa3c5..0f80427321 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -51,12 +51,16 @@ class FileGroup { public: void addFile(std::string fileName, std::string data, std::string mimeType); void addFile(std::string fileName, std::filesystem::path filePath); - void addFile(std::string fileName, const std::shared_ptr& imgFrame); - void addFile(std::string fileName, const std::shared_ptr& encodedFrame); + void addFile(const std::optional& fileName, const std::shared_ptr& imgFrame); + void addFile(const std::optional& fileName, const std::shared_ptr& encodedFrame); // void addFile(std::string fileName, const std::shared_ptr& nnData); - void addFile(std::string fileName, const std::shared_ptr& imgDetections); - void addImageDetectionsPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections); - void addImageDetectionsPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections); + void addFile(const std::optional& fileName, const std::shared_ptr& imgDetections); + void addImageDetectionsPair(const std::optional& fileName, + const std::shared_ptr& imgFrame, + const std::shared_ptr& imgDetections); + void addImageDetectionsPair(const std::optional& fileName, + const std::shared_ptr& encodedFrame, + const std::shared_ptr& imgDetections); // void addImageNNDataPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections); // void addImageNNDataPair(std::string fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections); @@ -110,9 +114,9 @@ class EventsManager { * @return bool */ bool sendSnap(const std::string& name, - const std::string& fileName, + const std::optional& fileName, const std::shared_ptr imgFrame, - const std::shared_ptr imgDetections, + const std::optional>& imgDetections = std::nullopt, const std::vector& tags = {}, const std::unordered_map& extras = {}, const std::string& deviceSerialNo = ""); diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index fef41181ce..056b467ec2 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -31,32 +31,39 @@ void FileGroup::addFile(std::string fileName, std::filesystem::path filePath) { addToFileData(fileData, std::move(filePath), std::move(fileName)); } -void FileGroup::addFile(std::string fileName, const std::shared_ptr& imgFrame) { - addToFileData(fileData, imgFrame, std::move(fileName)); +void FileGroup::addFile(const std::optional& fileName, const std::shared_ptr& imgFrame) { + std::string dataFileName = fileName.has_value() ? fileName.value() : "Image"; + addToFileData(fileData, imgFrame, std::move(dataFileName)); } -void FileGroup::addFile(std::string fileName, const std::shared_ptr& encodedFrame) { - addToFileData(fileData, encodedFrame, std::move(fileName)); +void FileGroup::addFile(const std::optional& fileName, const std::shared_ptr& encodedFrame) { + std::string dataFileName = fileName.has_value() ? fileName.value() : "Image"; + addToFileData(fileData, encodedFrame, std::move(dataFileName)); } // void FileGroup::addFile(std::string fileName, const std::shared_ptr& nnData) { // addToFileData(fileData, nnData, std::move(fileName)); // } -void FileGroup::addFile(std::string fileName, const std::shared_ptr& imgDetections) { - addToFileData(fileData, imgDetections, std::move(fileName)); +void FileGroup::addFile(const std::optional& fileName, const std::shared_ptr& imgDetections) { + std::string dataFileName = fileName.has_value() ? fileName.value() : "Detections"; + addToFileData(fileData, imgDetections, std::move(dataFileName)); } -void FileGroup::addImageDetectionsPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections) { - addToFileData(fileData, imgFrame, std::move(fileName)); - addToFileData(fileData, imgDetections, std::move(fileName)); +void FileGroup::addImageDetectionsPair(const std::optional& fileName, + const std::shared_ptr& imgFrame, + const std::shared_ptr& imgDetections) { + std::string dataFileName = fileName.has_value() ? fileName.value() : "ImageDetection"; + addToFileData(fileData, imgFrame, std::move(dataFileName)); + addToFileData(fileData, imgDetections, std::move(dataFileName)); } -void FileGroup::addImageDetectionsPair(std::string fileName, +void FileGroup::addImageDetectionsPair(const std::optional& fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections) { - addToFileData(fileData, encodedFrame, std::move(fileName)); - addToFileData(fileData, imgDetections, std::move(fileName)); + std::string dataFileName = fileName.has_value() ? fileName.value() : "ImageDetection"; + addToFileData(fileData, encodedFrame, std::move(dataFileName)); + addToFileData(fileData, imgDetections, std::move(dataFileName)); } // void FileGroup::addImageNNDataPair(std::string fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& nnData) { @@ -658,15 +665,19 @@ bool EventsManager::sendSnap(const std::string& name, } bool EventsManager::sendSnap(const std::string& name, - const std::string& fileName, + const std::optional& fileName, const std::shared_ptr imgFrame, - const std::shared_ptr imgDetections, + const std::optional>& imgDetections, const std::vector& tags, const std::unordered_map& extras, const std::string& deviceSerialNo) { // Create a FileGroup and send a snap containing it auto fileGroup = std::make_shared(); - fileGroup->addImageDetectionsPair(fileName, imgFrame, imgDetections); + if(imgDetections.has_value()) { + fileGroup->addImageDetectionsPair(fileName, imgFrame, imgDetections.value()); + } else { + fileGroup->addFile(fileName, imgFrame); + } return sendSnap(name, fileGroup, tags, extras, deviceSerialNo); } From 501c5fdabc1d2d53dce24929a1d7bf108dae2e26 Mon Sep 17 00:00:00 2001 From: AljazD Date: Wed, 15 Oct 2025 12:45:26 +0200 Subject: [PATCH 30/33] Updated caching of snaps and events --- .../src/utility/EventsManagerBindings.cpp | 6 +- include/depthai/utility/EventsManager.hpp | 35 +++-- src/utility/EventsManager.cpp | 137 ++++++++++++++---- 3 files changed, 138 insertions(+), 40 deletions(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 7b27c32126..89dbec853c 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -89,8 +89,10 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { .def("setLogResponse", &EventsManager::setLogResponse, py::arg("logResponse"), DOC(dai, utility, EventsManager, setLogResponse)) .def("setVerifySsl", &EventsManager::setVerifySsl, py::arg("verifySsl"), DOC(dai, utility, EventsManager, setVerifySsl)) .def("setCacheDir", &EventsManager::setCacheDir, py::arg("cacheDir"), DOC(dai, utility, EventsManager, setCacheDir)) - .def("setCacheOnExit", &EventsManager::setCacheOnExit, py::arg("cacheOnExit"), DOC(dai, utility, EventsManager, setCacheOnExit)) - .def("uploadCachedData", &EventsManager::uploadCachedData, DOC(dai, utility, EventsManager, uploadCachedData)) + .def("setCacheIfCannotSend", + &EventsManager::setCacheIfCannotSend, + py::arg("cacheIfCannotUpload"), + DOC(dai, utility, EventsManager, setCacheIfCannotSend)) .def("sendEvent", &EventsManager::sendEvent, py::arg("name"), diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 0f80427321..9cb5d1949a 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -35,7 +35,7 @@ class FileData { explicit FileData(const std::shared_ptr& encodedFrame, std::string fileName); // explicit FileData(const std::shared_ptr& nnData, std::string fileName); explicit FileData(const std::shared_ptr& imgDetections, std::string fileName); - bool toFile(const std::string& inputPath); + bool toFile(const std::filesystem::path& inputPath); private: std::string mimeType; @@ -138,11 +138,6 @@ class EventsManager { * @return void */ void setVerifySsl(bool verifySsl); - /** - * Upload cached data to the events service - * @return void - */ - void uploadCachedData(); /** * Set the cache directory for storing cached data. By default, the cache directory is set to /internal/private * @param cacheDir Cache directory @@ -150,17 +145,16 @@ class EventsManager { */ void setCacheDir(const std::string& cacheDir); /** - * Set whether to cache data when exiting the application. By default, cacheOnExit is set to false - * @param cacheOnExit bool + * Set whether to cache data if it cannot be sent. By default, cacheIfCannotSend is set to false + * @param cacheIfCannotSend bool * @return void */ - void setCacheOnExit(bool cacheOnExit); + void setCacheIfCannotSend(bool cacheIfCannotSend); private: struct SnapData { std::shared_ptr event; std::shared_ptr fileGroup; - std::string cachePath; }; struct UploadRetryPolicy { @@ -200,6 +194,23 @@ class EventsManager { * Cache events from the eventBuffer to the filesystem */ void cacheEvents(); + /** + * Cache snapData from the inputSnapBatch to the filesystem + */ + void cacheSnapData(std::deque>& inputSnapBatch); + /** + * Upload cached data to the events service + * @return void + */ + void uploadCachedData(); + /** + * Check if there's any cached data in the filesystem + */ + bool checkForCachedData(); + /** + * Clear cached data in the filesystem (if any) + */ + void clearCachedData(const std::filesystem::path& directory); std::string token; std::string url; @@ -209,7 +220,7 @@ class EventsManager { bool logResponse; bool verifySsl; std::string cacheDir; - bool cacheOnExit; + bool cacheIfCannotSend; std::unique_ptr uploadThread; std::deque> eventBuffer; std::deque> snapBuffer; @@ -234,6 +245,8 @@ class EventsManager { UploadRetryPolicy uploadRetryPolicy; + static constexpr int EVENT_BUFFER_MAX_SIZE = 300; + static constexpr int EVENT_VALIDATION_NAME_LENGTH = 56; static constexpr int EVENT_VALIDATION_MAX_TAGS = 20; static constexpr int EVENT_VALIDATION_TAG_LENGTH = 56; diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index 056b467ec2..e133fb31fb 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -197,18 +197,17 @@ FileData::FileData(const std::shared_ptr& imgDetections, std::str checksum = calculateSHA256Checksum(data); } -bool FileData::toFile(const std::string& inputPath) { +bool FileData::toFile(const std::filesystem::path& inputPath) { if(fileName.empty()) { logger::error("Filename is empty"); return false; } - std::filesystem::path path(inputPath); std::string extension = mimeType == "image/jpeg" ? ".jpg" : ".txt"; // Choose a unique filename - std::filesystem::path target = path / (fileName + extension); + std::filesystem::path target = inputPath / (fileName + extension); for(int i = 1; std::filesystem::exists(target); ++i) { logger::warn("File {} exists, trying a new name", target.string()); - target = path / (fileName + "_" + std::to_string(i) + extension); + target = inputPath / (fileName + "_" + std::to_string(i) + extension); } std::ofstream fileStream(target, std::ios::binary); if(!fileStream) { @@ -228,7 +227,7 @@ EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) logResponse(false), verifySsl(true), cacheDir("/internal/private"), - cacheOnExit(false), + cacheIfCannotSend(false), stopUploadThread(false), configurationLimitsFetched(false), warningStorageBytes(52428800) { @@ -279,14 +278,12 @@ EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) std::unique_lock lock(stopThreadConditionMutex); eventBufferCondition.wait_for(lock, std::chrono::seconds(static_cast(this->publishInterval)), [this]() { return stopUploadThread.load(); }); } - - // Cache events from eventBuffer - if(cacheOnExit) { - cacheEvents(); - } }); + // Upload or clear previously cached data on start if(uploadCachedOnStart) { uploadCachedData(); + } else { + clearCachedData(cacheDir); } } @@ -410,8 +407,15 @@ void EventsManager::uploadFileBatch(std::deque> inputS std::unique_lock lock(stopThreadConditionMutex); eventBufferCondition.wait_for(lock, duration, [this]() { return stopUploadThread.load(); }); - // TO DO: After a few tries, we can determine that the connection is not established. - // We can then check if caching is chosen. and do it + // After retrying a defined number of times, we can determine the connection is not established, cache if enabled + if(retryAttempt >= uploadRetryPolicy.maxAttempts) { + if(cacheIfCannotSend) { + cacheSnapData(inputSnapBatch); + } else { + logger::warn("Caching is not enabled, dropping snapBatch"); + } + return; + } } else { logger::info("Batch of file groups has been successfully prepared"); auto prepareBatchResults = std::make_unique(); @@ -443,6 +447,13 @@ void EventsManager::uploadFileBatch(std::deque> inputS for(auto& uploadResult : groupUploadResults) { if(!uploadResult.valid() || !uploadResult.get()) { logger::info("Failed to upload all of the groups in the given batch"); + // File upload was unsuccesful, cache if enabled + if(cacheIfCannotSend) { + cacheSnapData(inputSnapBatch); + } else { + logger::warn("Caching is not enabled, dropping snapBatch"); + } + return; } } return; @@ -534,7 +545,7 @@ void EventsManager::uploadEventBatch() { return; } for(size_t i = 0; i < eventBuffer.size() && i < eventsPerRequest; ++i) { - eventBatch->add_events()->Swap(eventBuffer.at(i).get()); + eventBatch->add_events()->CopyFrom(*eventBuffer.at(i).get()); } } std::string serializedBatch; @@ -559,7 +570,16 @@ void EventsManager::uploadEventBatch() { })); if(response.status_code != cpr::status::HTTP_OK) { logger::error("Failed to send event, status code: {}", response.status_code); - // TO DO: In case of repeated errors, caching of the events could be added if needed + // In case the eventBuffer gets too full (dropped connection), cache the events or drop them + if(eventBuffer.size() >= EVENT_BUFFER_MAX_SIZE) { + if(cacheIfCannotSend) { + cacheEvents(); + } else { + logger::warn("EventBuffer is full and caching is not enabled, dropping events"); + std::lock_guard lock(eventBufferMutex); + eventBuffer.clear(); + } + } } else { logger::info("Event sent successfully"); if(logResponse) { @@ -764,25 +784,88 @@ void EventsManager::cacheEvents() { eventBuffer.clear(); } +void EventsManager::cacheSnapData(std::deque>& inputSnapBatch) { + // Create a unique directory and save the snapData + logger::info("Caching snapData"); + for(const auto& snap : inputSnapBatch) { + std::filesystem::path path(cacheDir); + path = path / ("snap_" + snap->event->name() + "_" + std::to_string(snap->event->created_at())); + std::string snapDir = path.string(); + logger::info("Caching snap to {}", snapDir); + if(!std::filesystem::exists(cacheDir)) { + std::filesystem::create_directories(cacheDir); + } + std::filesystem::create_directory(snapDir); + std::ofstream eventFile(path / "snap.pb", std::ios::binary); + snap->event->SerializeToOstream(&eventFile); + for(auto& file : snap->fileGroup->fileData) { + file->toFile(path); + } + } +} + void EventsManager::uploadCachedData() { - // Iterate over the directories in cacheDir, read event.pb files and add events to eventBuffer - logger::info("Uploading cached data"); - if(!std::filesystem::exists(cacheDir)) { - logger::warn("Cache directory does not exist"); + // Iterate over the directories in cacheDir, add events and snapsData to buffers + if(!checkForCachedData()) { + logger::warn("Cache directory is empty, no cached data will be uploaded"); return; } + logger::info("Uploading cached data"); + for(const auto& entry : std::filesystem::directory_iterator(cacheDir)) { if(!entry.is_directory()) { continue; } - const auto& eventDir = entry.path(); - std::ifstream eventFile(eventDir / "event.pb", std::ios::binary); - auto event = std::make_shared(); - event->ParseFromIstream(&eventFile); - // Add event to eventBuffer - std::lock_guard lock(eventBufferMutex); - eventBuffer.push_back(std::move(event)); + if(entry.path().filename().string().rfind("event", 0) == 0) { + std::ifstream eventFile(entry.path() / "event.pb", std::ios::binary); + auto event = std::make_shared(); + event->ParseFromIstream(&eventFile); + std::lock_guard lock(eventBufferMutex); + eventBuffer.push_back(std::move(event)); + // Clear entries added to the eventBuffer + clearCachedData(entry.path()); + + } else if(entry.path().filename().string().rfind("snap", 0) == 0) { + std::ifstream snapFile(entry.path() / "snap.pb", std::ios::binary); + auto snapData = std::make_unique(); + auto event = std::make_shared(); + auto fileGroup = std::make_shared(); + event->ParseFromIstream(&snapFile); + for(const auto& fileEntry : std::filesystem::directory_iterator(entry.path())) { + if(fileEntry.is_regular_file() && fileEntry.path() != entry.path() / "snap.pb") { + auto fileData = std::make_shared(fileEntry.path(), fileEntry.path().filename().string()); + fileGroup->fileData.push_back(fileData); + } + } + snapData->event = event; + snapData->fileGroup = fileGroup; + std::lock_guard lock(snapBufferMutex); + snapBuffer.push_back(std::move(snapData)); + // Clear entries added to the snapBuffer + clearCachedData(entry.path()); + } + } +} + +bool EventsManager::checkForCachedData() { + if(!std::filesystem::exists(cacheDir)) { + return false; + } + return std::any_of( + std::filesystem::directory_iterator(cacheDir), std::filesystem::directory_iterator(), [](const auto& entry) { return entry.is_directory(); }); +} + +void EventsManager::clearCachedData(const std::filesystem::path& directory) { + if(!checkForCachedData()) { + return; + } + std::error_code ec; + std::filesystem::remove_all(directory, ec); + if(ec) { + logger::error("Failed to remove cache directory {}: {}", directory.string(), ec.message()); + } else { + logger::info("Cleared cache directory {}", directory.string()); } } @@ -802,8 +885,8 @@ void EventsManager::setVerifySsl(bool verifySsl) { this->verifySsl = verifySsl; } -void EventsManager::setCacheOnExit(bool cacheOnExit) { - this->cacheOnExit = cacheOnExit; +void EventsManager::setCacheIfCannotSend(bool cacheIfCannotSend) { + this->cacheIfCannotSend = cacheIfCannotSend; } } // namespace utility From d66489c671da0aee5713135222d0c628eec01d4b Mon Sep 17 00:00:00 2001 From: AljazD Date: Tue, 21 Oct 2025 10:02:50 +0200 Subject: [PATCH 31/33] DEPTHAI_HUB_URL env variable rename, snap tag fix, minor fixes --- README.md | 2 +- src/utility/EventsManager.cpp | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7bfd91cd06..90b6f662e7 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ The following environment variables can be set to alter default behavior of the | DEPTHAI_CRASHDUMP_TIMEOUT | Specifies the duration in milliseconds to wait for device reboot when obtaining a crash dump. Crash dump retrieval disabled if 0. | | DEPTHAI_ENABLE_ANALYTICS_COLLECTION | Enables automatic analytics collection (pipeline schemas) used to improve the library | | DEPTHAI_DISABLE_CRASHDUMP_COLLECTION | Disables automatic crash dump collection used to improve the library | -| DEPTHAI_HUB_URL | URL for the Luxonis Hub | +| DEPTHAI_HUB_EVENTS_BASE_URL | URL for the Luxonis Hub | | DEPTHAI_HUB_API_KEY | API key for the Luxonis Hub | | DEPTHAI_ZOO_INTERNET_CHECK | (Default) 1 - perform internet check, if available, download the newest model version 0 - skip internet check and use cached model | | DEPTHAI_ZOO_INTERNET_CHECK_TIMEOUT | (Default) 1000 - timeout in milliseconds for the internet check | diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index e133fb31fb..bb6dfe730d 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -32,12 +32,12 @@ void FileGroup::addFile(std::string fileName, std::filesystem::path filePath) { } void FileGroup::addFile(const std::optional& fileName, const std::shared_ptr& imgFrame) { - std::string dataFileName = fileName.has_value() ? fileName.value() : "Image"; + std::string dataFileName = fileName.value_or("Image"); addToFileData(fileData, imgFrame, std::move(dataFileName)); } void FileGroup::addFile(const std::optional& fileName, const std::shared_ptr& encodedFrame) { - std::string dataFileName = fileName.has_value() ? fileName.value() : "Image"; + std::string dataFileName = fileName.value_or("Image"); addToFileData(fileData, encodedFrame, std::move(dataFileName)); } @@ -46,23 +46,23 @@ void FileGroup::addFile(const std::optional& fileName, const std::s // } void FileGroup::addFile(const std::optional& fileName, const std::shared_ptr& imgDetections) { - std::string dataFileName = fileName.has_value() ? fileName.value() : "Detections"; + std::string dataFileName = fileName.value_or("Detections"); addToFileData(fileData, imgDetections, std::move(dataFileName)); } void FileGroup::addImageDetectionsPair(const std::optional& fileName, const std::shared_ptr& imgFrame, const std::shared_ptr& imgDetections) { - std::string dataFileName = fileName.has_value() ? fileName.value() : "ImageDetection"; - addToFileData(fileData, imgFrame, std::move(dataFileName)); + std::string dataFileName = fileName.value_or("ImageDetection"); + addToFileData(fileData, imgFrame, dataFileName); addToFileData(fileData, imgDetections, std::move(dataFileName)); } void FileGroup::addImageDetectionsPair(const std::optional& fileName, const std::shared_ptr& encodedFrame, const std::shared_ptr& imgDetections) { - std::string dataFileName = fileName.has_value() ? fileName.value() : "ImageDetection"; - addToFileData(fileData, encodedFrame, std::move(dataFileName)); + std::string dataFileName = fileName.value_or("ImageDetection"); + addToFileData(fileData, encodedFrame, dataFileName); addToFileData(fileData, imgDetections, std::move(dataFileName)); } @@ -235,7 +235,7 @@ EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) auto containerId = utility::getEnvAs("OAKAGENT_CONTAINER_ID", ""); sourceAppId = appId == "" ? containerId : appId; sourceAppIdentifier = utility::getEnvAs("OAKAGENT_APP_IDENTIFIER", ""); - url = utility::getEnvAs("DEPTHAI_HUB_URL", "https://events.cloud.luxonis.com"); + url = utility::getEnvAs("DEPTHAI_HUB_EVENTS_BASE_URL", "https://events.cloud.luxonis.com"); token = utility::getEnvAs("DEPTHAI_HUB_API_KEY", ""); // Thread handling preparation and uploads uploadThread = std::make_unique([this]() { @@ -650,6 +650,7 @@ bool EventsManager::sendSnap(const std::string& name, snapData->event = std::make_unique(); snapData->event->set_created_at(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); snapData->event->set_name(name); + snapData->event->add_tags("snap"); for(const auto& tag : tags) { snapData->event->add_tags(tag); } @@ -664,7 +665,6 @@ bool EventsManager::sendSnap(const std::string& name, logger::error("Failed to send snap, validation failed"); return false; } - snapData->event->add_tags("snap"); if(fileGroup->fileData.size() > maxFilesPerGroup) { logger::error("Failed to send snap, the number of files in a file group {} exceeds {}", fileGroup->fileData.size(), maxFilesPerGroup); return false; From d99d0f6a1267f39c3ec55784d140c4e58467bbb4 Mon Sep 17 00:00:00 2001 From: AljazD Date: Fri, 24 Oct 2025 13:37:35 +0200 Subject: [PATCH 32/33] Added missing token check when fetching configuration limits --- src/utility/EventsManager.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index bb6dfe730d..c035a72ff2 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -297,6 +297,10 @@ EventsManager::~EventsManager() { bool EventsManager::fetchConfigurationLimits() { logger::info("Fetching configuration limits"); + if(token.empty()) { + logger::warn("Missing token, please set DEPTHAI_HUB_API_KEY environment variable or use the setToken method"); + return false; + } auto header = cpr::Header(); header["Authorization"] = "Bearer " + token; cpr::Url requestUrl = static_cast(this->url + "/v2/api-usage"); @@ -359,7 +363,7 @@ void EventsManager::uploadFileBatch(std::deque> inputS return; } if(token.empty()) { - logger::warn("Missing token, please set DEPTHAI_HUB_API_KEY environment variable or use setToken method"); + logger::warn("Missing token, please set DEPTHAI_HUB_API_KEY environment variable or use the setToken method"); return; } // Fill the batch with the groups from inputSnapBatch and their corresponding files @@ -541,7 +545,7 @@ void EventsManager::uploadEventBatch() { return; } if(token.empty()) { - logger::warn("Missing token, please set DEPTHAI_HUB_API_KEY environment variable or use setToken method"); + logger::warn("Missing token, please set DEPTHAI_HUB_API_KEY environment variable or use the setToken method"); return; } for(size_t i = 0; i < eventBuffer.size() && i < eventsPerRequest; ++i) { From 72bc6569fdd30371f2f19d453232dec5cb88d7e9 Mon Sep 17 00:00:00 2001 From: AljazD Date: Thu, 6 Nov 2025 12:56:45 +0100 Subject: [PATCH 33/33] Publish interval setting will no longer be exposed to the end user --- bindings/python/src/utility/EventsManagerBindings.cpp | 2 +- include/depthai/utility/EventsManager.hpp | 2 +- src/utility/EventsManager.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bindings/python/src/utility/EventsManagerBindings.cpp b/bindings/python/src/utility/EventsManagerBindings.cpp index 89dbec853c..e09d314413 100644 --- a/bindings/python/src/utility/EventsManagerBindings.cpp +++ b/bindings/python/src/utility/EventsManagerBindings.cpp @@ -84,7 +84,7 @@ void EventsManagerBindings::bind(pybind11::module& m, void* pCallstack) { py::class_(m, "EventsManager") .def(py::init<>()) - .def(py::init(), py::arg("uploadCachedOnStart") = false, py::arg("publishInterval") = 10.0) + .def(py::init(), py::arg("uploadCachedOnStart") = false) .def("setToken", &EventsManager::setToken, py::arg("token"), DOC(dai, utility, EventsManager, setToken)) .def("setLogResponse", &EventsManager::setLogResponse, py::arg("logResponse"), DOC(dai, utility, EventsManager, setLogResponse)) .def("setVerifySsl", &EventsManager::setVerifySsl, py::arg("verifySsl"), DOC(dai, utility, EventsManager, setVerifySsl)) diff --git a/include/depthai/utility/EventsManager.hpp b/include/depthai/utility/EventsManager.hpp index 9cb5d1949a..09ba9caa12 100644 --- a/include/depthai/utility/EventsManager.hpp +++ b/include/depthai/utility/EventsManager.hpp @@ -71,7 +71,7 @@ class FileGroup { class EventsManager { public: - explicit EventsManager(bool uploadCachedOnStart = false, float publishInterval = 10.0); + explicit EventsManager(bool uploadCachedOnStart = false); ~EventsManager(); /** diff --git a/src/utility/EventsManager.cpp b/src/utility/EventsManager.cpp index c035a72ff2..75b42e2f72 100644 --- a/src/utility/EventsManager.cpp +++ b/src/utility/EventsManager.cpp @@ -222,8 +222,8 @@ bool FileData::toFile(const std::filesystem::path& inputPath) { return true; } -EventsManager::EventsManager(bool uploadCachedOnStart, float publishInterval) - : publishInterval(publishInterval), +EventsManager::EventsManager(bool uploadCachedOnStart) + : publishInterval(30.0f), logResponse(false), verifySsl(true), cacheDir("/internal/private"),