task_api: add a concrete class for ObjectDetection 96/319596/4
authorInki Dae <inki.dae@samsung.com>
Wed, 12 Feb 2025 03:59:26 +0000 (12:59 +0900)
committerInki Dae <inki.dae@samsung.com>
Wed, 19 Feb 2025 05:51:34 +0000 (14:51 +0900)
Add a concrete class for ObjectDetection task.

This patch refactors the implementation of the Object Detection task
by encapsulating its dependencies within a dedicated concrete class.
Consequently, this design change allows us to extend the TaskAPI service
with additional Task APIs without modifying the existing TaskAPI service
itself.

Change-Id: I6940f0582589f3749d92b9e484c72870695cfaf0
Signed-off-by: Inki Dae <inki.dae@samsung.com>
services/task_api/CMakeLists.txt
services/task_api/include/ITaskBase.h [new file with mode: 0644]
services/task_api/include/ObjectDetectionTask.h [new file with mode: 0644]
services/task_api/include/TaskApi.h
services/task_api/src/ObjectDetectionTask.cpp [new file with mode: 0644]
services/task_api/src/TaskApi.cpp
test/services/test_task_api_on_screen.cpp

index c460a895c6cbbe24e324440906f4b57b5c8d42e0..d984a1271179bdfa890af81b0d203f6b63e97155 100644 (file)
@@ -1,6 +1,7 @@
 SET(SINGLEO_SERVICE_SOURCE_FILES
     ${SINGLEO_SERVICE_SOURCE_FILES}
     task_api/src/TaskApi.cpp
+    task_api/src/ObjectDetectionTask.cpp
 )
 
 LIST(APPEND SERVICE_LIBRARY_LIST ${SERVICE_LIBRARY_LIST})
diff --git a/services/task_api/include/ITaskBase.h b/services/task_api/include/ITaskBase.h
new file mode 100644 (file)
index 0000000..88972f0
--- /dev/null
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ITASK_BASE_H__
+#define __ITASK_BASE_H__
+
+#include <memory>
+#include "SingleoException.h"
+#include "SingleoCommonTypes.h"
+#include "ServiceDataType.h"
+#include "DataTypes.h"
+#include "TaskManager.h"
+
+namespace singleo
+{
+namespace services
+{
+namespace taskapi
+{
+class ITaskBase
+{
+public:
+       virtual ~ITaskBase() {};
+
+       virtual void setupPipeline(std::unique_ptr<TaskManager> &taskManager) = 0;
+       virtual void resetPipeline(std::unique_ptr<TaskManager> &taskManager) = 0;
+       virtual void renderOn(BaseDataType &data) = 0;
+       virtual void updateResult(std::vector<std::shared_ptr<BaseResultType> > &outputs) = 0;
+       virtual TaskApiResult &getResult() = 0;
+       // Make sure to update a _result member object in concrete class with a given result
+       // because _result in concrete class is used by getResultXxx().
+       // I.e.,
+       // int XxxTask::getNumOfResult(const TaskApiResult &result)
+       // {
+       //     _result = result;
+       //         return _result.bboxes.size();
+       // }
+       virtual int getNumOfResult(const TaskApiResult &result) = 0;
+       virtual unsigned int getResultInt(unsigned int idx, const std::string &key) = 0;
+       virtual const char *getResultStr(unsigned int idx, const std::string &key) = 0;
+};
+} // taskapi
+} // service
+} // singleo
+
+#endif
\ No newline at end of file
diff --git a/services/task_api/include/ObjectDetectionTask.h b/services/task_api/include/ObjectDetectionTask.h
new file mode 100644 (file)
index 0000000..a90467f
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __OBJECT_DETECTION_TASK_H__
+#define __OBJECT_DETECTION_TASK_H__
+
+#include "SingleoException.h"
+#include "SingleoCommonTypes.h"
+#include "ServiceDataType.h"
+#include "DataTypes.h"
+#include "TaskManager.h"
+
+#include "ITaskBase.h"
+
+namespace singleo
+{
+namespace services
+{
+namespace taskapi
+{
+class ObjectDetectionTask : public ITaskBase
+{
+private:
+       std::unique_ptr<TaskManager> _taskManager;
+       std::map<std::string, TaskApiResultType> _result_keys = { { "MIN_X", TaskApiResultType::MIN_X },
+                                                                                                                                { "MIN_Y", TaskApiResultType::MIN_Y },
+                                                                                                                                { "MAX_X", TaskApiResultType::MAX_X },
+                                                                                                                                { "MAX_Y", TaskApiResultType::MAX_Y },
+                                                                                                                                { "LABEL", TaskApiResultType::LABEL } };
+
+       bool isKeyValid(const std::string &key);
+       TaskApiResult _result;
+       std::mutex _result_lock;
+
+public:
+       ObjectDetectionTask();
+       virtual ~ObjectDetectionTask();
+
+       void setupPipeline(std::unique_ptr<TaskManager> &taskManager) final;
+       void resetPipeline(std::unique_ptr<TaskManager> &taskManager) final;
+       void renderOn(BaseDataType &data) final;
+       void updateResult(std::vector<std::shared_ptr<BaseResultType> > &outputs) final;
+       TaskApiResult &getResult() final;
+       int getNumOfResult(const TaskApiResult &result) final;
+       unsigned int getResultInt(unsigned int idx, const std::string &key) final;
+       const char *getResultStr(unsigned int idx, const std::string &key) final;
+};
+
+} // taskapi
+} // service
+} // singleo
+
+#endif
\ No newline at end of file
index 4e5576c614b0d7ad6486824896ecdb8f7fe029ec..5bab8032de0555a35c7c8a4ff2117300baffe573 100644 (file)
@@ -32,6 +32,7 @@
 #include "AsyncManager.h"
 #include "IPreprocessor.h"
 #include "TaskManager.h"
+#include "ITaskBase.h"
 
 namespace singleo
 {
@@ -43,20 +44,13 @@ class TaskApi : public IService, public input::IInputObserver
 {
 private:
        static bool _registered;
+       std::unique_ptr<ITaskBase> _task;
        std::unique_ptr<TaskManager> _taskManager;
        std::unique_ptr<singleo::input::IInputService> _input_service;
        std::queue<std::shared_ptr<BaseDataType> > _inputQ;
        std::mutex _inputMutex;
-       TaskApiResult _result;
-       std::mutex _result_lock;
-       std::map<std::string, TaskApiResultType> _result_keys = { { "MIN_X", TaskApiResultType::MIN_X },
-                                                                                                                                { "MIN_Y", TaskApiResultType::MIN_Y },
-                                                                                                                                { "MAX_X", TaskApiResultType::MAX_X },
-                                                                                                                                { "MAX_Y", TaskApiResultType::MAX_Y },
-                                                                                                                                { "LABEL", TaskApiResultType::LABEL } };
        bool _async_mode { false };
 
-       bool isKeyValid(std::string key);
        void updateResult(BaseDataType &input_data);
 
        std::unique_ptr<AsyncManager<ImageDataType, TaskApiResult> > _async_manager;
diff --git a/services/task_api/src/ObjectDetectionTask.cpp b/services/task_api/src/ObjectDetectionTask.cpp
new file mode 100644 (file)
index 0000000..4def276
--- /dev/null
@@ -0,0 +1,168 @@
+/**
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <map>
+#include <opencv2/opencv.hpp>
+#include "SingleoException.h"
+#include "InferenceNode.h"
+#include "BridgeNode.h"
+#include "EndpointNode.h"
+#include "InferenceTaskFactory.h"
+#include "ObjectDetectionTask.h"
+
+using namespace std;
+using namespace singleo::inference;
+using namespace singleo::exception;
+
+namespace singleo
+{
+namespace services
+{
+namespace taskapi
+{
+ObjectDetectionTask::ObjectDetectionTask()
+{}
+
+ObjectDetectionTask::~ObjectDetectionTask()
+{}
+
+void ObjectDetectionTask::setupPipeline(std::unique_ptr<TaskManager> &taskManager)
+{
+       auto factory = InferenceTaskFactory::instance().create("MvInferenceTaskFactory");
+
+       taskManager->requestNewNode(NodeType::STARTPOINT, "startpoint");
+
+       auto object_detection_node = taskManager->requestNewNode(NodeType::INFERENCE, "object_detection");
+       dynamic_cast<InferenceNode *>(object_detection_node)->setInferenceTask(factory->createObjectDetection());
+
+       taskManager->requestNewNode(NodeType::ENDPOINT, "endpoint");
+       taskManager->resetPipeline();
+
+       taskManager->addEdge("startpoint", "object_detection");
+       taskManager->addEdge("object_detection", "endpoint");
+
+       taskManager->verify();
+}
+
+void ObjectDetectionTask::resetPipeline(std::unique_ptr<TaskManager> &taskManager)
+{
+       taskManager->clear();
+}
+
+void ObjectDetectionTask::renderOn(BaseDataType &data)
+{
+       auto preprocessedImg = dynamic_cast<ImageDataType &>(data);
+       unique_lock<mutex> lock(_result_lock);
+
+       if (_result.is_valid) {
+               auto rects = _result.bboxes;
+               auto labels = _result.labels;
+               cv::Mat imgCv(cv::Size(preprocessedImg.width, preprocessedImg.height), CV_MAKETYPE(CV_8U, 3),
+                                         preprocessedImg.ptr);
+
+               lock.unlock();
+
+               for (unsigned int idx = 0; idx < rects.size(); ++idx) {
+                       cv::rectangle(imgCv, cv::Point(rects[idx].left, rects[idx].top),
+                                                       cv::Point(rects[idx].right, rects[idx].bottom), cv::Scalar(255, 0, 0), 3);
+                       cv::putText(imgCv, labels[idx], cv::Point(rects[idx].left, rects[idx].top - 10), cv::FONT_HERSHEY_SIMPLEX, 1.2, cv::Scalar(255, 0, 0), 2);
+               }
+
+               lock.lock();
+               _result.is_valid = false;
+       }
+}
+
+void ObjectDetectionTask::updateResult(std::vector<std::shared_ptr<BaseResultType> > &outputs)
+{
+       for (auto &output : outputs) {
+               if (output->_is_empty)
+                       continue;
+
+               unique_lock<mutex> lock(_result_lock);
+
+               if (output->_type == ResultType::OBJECT_DETECTION) {
+                       _result.bboxes = dynamic_cast<OdResultType &>(*output)._rects;
+                       _result.labels = dynamic_cast<OdResultType &>(*output)._labels;
+                       _result.is_valid = true;
+               }
+       }
+}
+
+TaskApiResult &ObjectDetectionTask::getResult()
+{
+       unique_lock<mutex> lock(_result_lock);
+
+       return _result;
+}
+
+int ObjectDetectionTask::getNumOfResult(const TaskApiResult &result)
+{
+       unique_lock<mutex> lock(_result_lock);
+
+       _result = result;
+
+       return _result.bboxes.size();
+}
+
+bool ObjectDetectionTask::isKeyValid(const std::string &key)
+{
+       return (_result_keys.find(key) != _result_keys.end());
+}
+
+unsigned int ObjectDetectionTask::getResultInt(unsigned int idx, const std::string &key)
+{
+       if (!isKeyValid(key))
+               throw InvalidParameter("A given result key is inavlid.");
+
+       unique_lock<mutex> lock(_result_lock);
+
+       switch (_result_keys[key]) {
+       case TaskApiResultType::MIN_X:
+               return _result.bboxes[idx].left;
+       case TaskApiResultType::MIN_Y:
+               return _result.bboxes[idx].top;
+       case TaskApiResultType::MAX_X:
+               return _result.bboxes[idx].right;
+       case TaskApiResultType::MAX_Y:
+               return _result.bboxes[idx].bottom;
+       default:
+               throw InvalidParameter("A given result key is inavlid.");
+       }
+}
+
+const char *ObjectDetectionTask::getResultStr(unsigned int idx, const std::string &key)
+{
+       if (!isKeyValid(key))
+               throw InvalidParameter("A given result key is inavlid.");
+
+       unique_lock<mutex> lock(_result_lock);
+
+       if (_result.labels.empty())
+               throw NoData("Label not provided.");
+
+       switch (_result_keys[key]) {
+       case TaskApiResultType::LABEL:
+               return _result.labels[idx].c_str();
+       default:
+               throw InvalidParameter("A given result key is inavlid.");
+       }
+}
+
+}
+}
+}
index 492f3d3a693c45d14b27d1812b0f4edd4b698b5b..98d791ac0a4a6e0ccb1d3608bf2c8a9ccd662aaf 100644 (file)
@@ -18,7 +18,6 @@
 #include <map>
 #include "SingleoException.h"
 #include "TaskApi.h"
-#include "InferenceTaskFactory.h"
 #include "SingleoLog.h"
 #include "ImagePreprocessor.h"
 #include "ServiceFactory.h"
@@ -28,6 +27,8 @@
 #include "BridgeNode.h"
 #include "EndpointNode.h"
 
+#include "ObjectDetectionTask.h"
+
 using namespace std;
 using namespace singleo::inference;
 using namespace singleo::input;
@@ -43,27 +44,19 @@ bool TaskApi::_registered = registerService<TaskApi>("TaskApi");
 
 TaskApi::TaskApi()
 {
-       // In default, we will use Inference service factory for Mediavision to use Mediavision framework
-       // for inference service. TODO. introduce meta config file approach later.
-       auto factory = InferenceTaskFactory::instance().create("MvInferenceTaskFactory");
-
        _taskManager = make_unique<TaskManager>();
-
-       _taskManager->requestNewNode(NodeType::STARTPOINT, "startpoint");
-
-       auto object_detection_node = _taskManager->requestNewNode(NodeType::INFERENCE, "object_detection");
-       dynamic_cast<InferenceNode *>(object_detection_node)->setInferenceTask(factory->createObjectDetection());
-
-       _taskManager->requestNewNode(NodeType::ENDPOINT, "endpoint");
-
        _preprocessor = make_unique<ImagePreprocessor>();
+}
 
-       _taskManager->resetPipeline();
-
-       _taskManager->addEdge("startpoint", "object_detection");
-       _taskManager->addEdge("object_detection", "endpoint");
+TaskApi::~TaskApi()
+{
+       if (_async_mode) {
+               _input_service->streamOff();
+               _async_manager->destroy();
+       }
 
-       _taskManager->verify();
+       if (_task)
+               _task->resetPipeline(_taskManager);
 }
 
 void TaskApi::configure(ServiceConfigBase &config)
@@ -72,8 +65,6 @@ void TaskApi::configure(ServiceConfigBase &config)
 
        // Create InputCamera service if input service type is CAMERA.
        if (taskapi_config.input_config->_input_feed_type == InputFeedType::CAMERA) {
-               CameraConfig *cameraConfig = NULL;
-
                try {
                        CameraConfig &cameraConfig = dynamic_cast<CameraConfig &>(*taskapi_config.input_config);
 
@@ -88,17 +79,12 @@ void TaskApi::configure(ServiceConfigBase &config)
                SINGLEO_LOGD("Camera input service has been initialized.");
        }
 
-       SINGLEO_LOGD("task name : %s", taskapi_config._task_name.c_str());
-}
-
-TaskApi::~TaskApi()
-{
-       if (_async_mode) {
-               _input_service->streamOff();
-               _async_manager->destroy();
+       if (taskapi_config._task_name == "OBJECT_DETECTION") {
+               _task = make_unique<ObjectDetectionTask>();
+               _task->setupPipeline(_taskManager);
+       } else {
+               throw InvalidOperation("Unsupported task name.");
        }
-
-       _taskManager->clear();
 }
 
 void TaskApi::inputFeedCb(BaseDataType &data)
@@ -109,29 +95,7 @@ void TaskApi::inputFeedCb(BaseDataType &data)
        auto preprocessedImg = dynamic_cast<ImageDataType &>(_preprocessor->getOutput());
 
        if (_user_cb) {
-               cv::Mat imgCv(cv::Size(preprocessedImg.width, preprocessedImg.height), CV_MAKETYPE(CV_8U, 3),
-                                         preprocessedImg.ptr);
-
-               _result_lock.lock();
-
-               if (_result.is_valid) {
-                       auto &rects = _result.bboxes;
-                       auto &labels = _result.labels;
-
-                       _result_lock.unlock();
-
-                       for (unsigned int idx = 0; idx < rects.size(); ++idx) {
-                               cv::rectangle(imgCv, cv::Point(rects[idx].left, rects[idx].top),
-                                                         cv::Point(rects[idx].right, rects[idx].bottom), cv::Scalar(255, 0, 0), 3);
-                               cv::putText(imgCv, labels[idx], cv::Point(rects[idx].left, rects[idx].top - 10), cv::FONT_HERSHEY_SIMPLEX, 1.2, cv::Scalar(255, 0, 0), 2);
-                       }
-
-                       _result_lock.lock();
-                       _result.is_valid = false;
-                       _result_lock.unlock();
-               }
-
-               _result_lock.unlock();
+               _task->renderOn(data);
                _user_cb(preprocessedImg.ptr, preprocessedImg.width, preprocessedImg.height, preprocessedImg.byte_per_pixel, _user_data);
        }
 
@@ -149,15 +113,6 @@ void TaskApi::inputFeedCb(BaseDataType &data)
                delete originalImg->ptr;
 }
 
-bool TaskApi::isKeyValid(std::string key)
-{
-       auto it = _result_keys.find(key);
-       if (it == _result_keys.end())
-               return false;
-
-       return true;
-}
-
 void TaskApi::add_input(BaseDataType &input_data)
 {
        if (_inputQ.size() > 0) {
@@ -230,42 +185,20 @@ void TaskApi::performAsync()
 
 void TaskApi::updateResult(BaseDataType &in_data)
 {
-       TaskApiResult taskapi_result;
-
-       for (auto &output : _taskManager->output()) {
-               if (output->_is_empty)
-                       continue;
+       _task->updateResult(_taskManager->output());
 
-               if (output->_type == ResultType::OBJECT_DETECTION) {
-                       taskapi_result.bboxes = dynamic_cast<OdResultType &>(*output)._rects;
-                       taskapi_result.labels = dynamic_cast<OdResultType &>(*output)._labels;
-                       taskapi_result.is_valid = true;
-               }
-       }
-
-       if (_async_mode) {
-               _async_manager->pushOutput(taskapi_result);
-       } else {
-               _result_lock.lock();
-               _result = taskapi_result;
-               _result_lock.unlock();
-       }
+       if (_async_mode)
+               _async_manager->pushOutput(_task->getResult());
 }
 
 void TaskApi::getResultCnt(unsigned int *cnt)
 {
        if (_async_mode) {
-               auto result = _async_manager->popOutput();
-               std::unique_lock<std::mutex> lock(_result_lock);
-
-               _result = result;
-               *cnt = static_cast<unsigned int>(_result.bboxes.size());
+               *cnt = static_cast<unsigned int>(_task->getNumOfResult(_async_manager->popOutput()));
                return;
        }
 
-       std::unique_lock<std::mutex> lock(_result_lock);
-
-       *cnt = static_cast<unsigned int>(_result.bboxes.size());
+       *cnt = static_cast<unsigned int>(_task->getNumOfResult(_task->getResult()));
 }
 
 void TaskApi::getResultInt(unsigned int idx, std::string key, unsigned int *value)
@@ -274,25 +207,7 @@ void TaskApi::getResultInt(unsigned int idx, std::string key, unsigned int *valu
 
        SINGLEO_LOGD("given key = %s", key.c_str());
 
-       if (!isKeyValid(key))
-               throw InvalidParameter("A given result key is inavlid.");
-
-       std::unique_lock<std::mutex> lock(_result_lock);
-
-       switch (_result_keys[key]) {
-       case TaskApiResultType::MIN_X:
-               *value = _result.bboxes[idx].left;
-               break;
-       case TaskApiResultType::MIN_Y:
-               *value = _result.bboxes[idx].top;
-               break;
-       case TaskApiResultType::MAX_X:
-               *value = _result.bboxes[idx].right;
-               break;
-       case TaskApiResultType::MAX_Y:
-               *value = _result.bboxes[idx].bottom;
-               break;
-       }
+       *value = _task->getResultInt(idx, key);
 }
 
 void TaskApi::getResultStr(unsigned int idx, std::string key, const char **value)
@@ -301,16 +216,7 @@ void TaskApi::getResultStr(unsigned int idx, std::string key, const char **value
 
        SINGLEO_LOGD("given key = %s", key.c_str());
 
-       if (!isKeyValid(key))
-               throw InvalidParameter("A given result key is inavlid.");
-
-       std::unique_lock<std::mutex> lock(_result_lock);
-
-       switch (_result_keys[key]) {
-       case TaskApiResultType::LABEL:
-               *value = _result.labels[idx].c_str();
-               break;
-       }
+       *value = _task->getResultStr(idx, key);
 }
 
 void TaskApi::registerUserCallback(singleo_user_cb_t user_cb, void *user_data)
index f088c02305097ca6cd1ae48d1dd7ad51275857bd..40050552347d941a2761849c9c0242da025581bb 100644 (file)
@@ -66,13 +66,15 @@ void taskapi_callback(void *user_data)
                        ret = singleo_service_get_result_int(handle, idx, "MAX_Y", &max_y);
                        ASSERT_EQ(ret, SINGLEO_ERROR_NONE);
 
+                       cout << "x1 = " << min_x << " y1 = " << min_y << " x2 = " << max_x << " y2 = " << max_y;
+
                        const char *label {};
 
                        ret = singleo_service_get_result_str(handle, idx, "LABEL", &label);
-                       ASSERT_EQ(ret, SINGLEO_ERROR_NONE);
+                       if (ret == SINGLEO_ERROR_NONE)
+                               cout << " label = " << label;
 
-
-                       cout << "x1 = " << min_x << " y1 = " << min_y << " x2 = " << max_x << " y2 = " << max_y << " label = " << label << endl;
+                       cout << endl;
                }
 
                if (++frame_number > 5000 && cnt > 0)
@@ -83,7 +85,6 @@ void taskapi_callback(void *user_data)
 void user_callback(unsigned char *buffer, unsigned int width, unsigned int height, unsigned int bytes_per_pixel,
                                   void *user_data)
 {
-       Context *context = static_cast<Context *>(user_data);
        cv::Mat result(cv::Size(width, height), CV_MAKETYPE(CV_8U, 3), buffer);
 
        cv::Mat resized;