From 02539e19ed2e773233ec556d9d4b6f1f4238255d Mon Sep 17 00:00:00 2001
From: Rainer Kartmann <rainer.kartmann@kit.edu>
Date: Mon, 1 Mar 2021 15:02:00 +0100
Subject: [PATCH] Add functions to load recognized and spoken object names

---
 .../libraries/ArmarXObjects/ObjectFinder.cpp  | 38 +++++++++++++++
 .../libraries/ArmarXObjects/ObjectFinder.h    | 26 ++++++++++
 .../libraries/ArmarXObjects/ObjectInfo.cpp    | 47 +++++++++++++++++++
 .../libraries/ArmarXObjects/ObjectInfo.h      | 16 +++++++
 4 files changed, 127 insertions(+)

diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.cpp b/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.cpp
index 0be8ac4ea..e9a9fc4eb 100644
--- a/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.cpp
+++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.cpp
@@ -1,5 +1,6 @@
 #include <VirtualRobot/XML/ObjectIO.h>
 
+#include <SimoxUtility/algorithm/string.h>
 #include <SimoxUtility/filesystem/list_directory.h>
 
 #include <ArmarXCore/core/system/ArmarXDataPath.h>
@@ -229,6 +230,43 @@ namespace armarx
         return loadObstacle(findObject(obj));
     }
 
+
+    static std::vector<std::string> _loadNames(
+            const ObjectFinder& finder,
+            const ObjectID& objectID,
+            const bool includeClassName,
+            const std::function<std::optional<std::vector<std::string>>(const ObjectInfo&)> loadNamesFn)
+    {
+        std::vector<std::string> names;
+        if (includeClassName)
+        {
+            names.push_back(simox::alg::to_lower(objectID.className()));
+        }
+        if (std::optional<ObjectInfo> info = finder.findObject(objectID))
+        {
+            if (std::optional<std::vector<std::string>> loadedNames = loadNamesFn(*info))
+            {
+                // Source: https://stackoverflow.com/a/201729
+                names.insert(names.end(), loadedNames->begin(), loadedNames->end());
+            }
+        }
+        return names;
+    }
+    std::vector<std::string> ObjectFinder::loadRecognizedNames(const ObjectID& objectID, bool includeClassName) const
+    {
+        return _loadNames(*this, objectID, includeClassName, [](const ObjectInfo& info)
+        {
+            return info.loadRecognizedNames();
+        });
+    }
+    std::vector<std::string> ObjectFinder::loadSpokenNames(const ObjectID& objectID, bool includeClassName) const
+    {
+        return _loadNames(*this, objectID, includeClassName, [](const ObjectInfo& info)
+        {
+            return info.loadSpokenNames();
+        });
+    }
+
     ObjectFinder::path ObjectFinder::_rootDirAbs() const
     {
         return packageDataDir / packageName;
diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.h b/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.h
index bb503090e..f6e983159 100644
--- a/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.h
+++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectFinder.h
@@ -53,6 +53,32 @@ namespace armarx
         static loadObstacle(const std::optional<ObjectInfo>& ts);
         VirtualRobot::ObstaclePtr
         loadObstacle(const objpose::ObjectPose& obj) const;
+
+
+        /**
+         * @brief Load names to use when matched when recognizing an object by name.
+         *
+         * If the object's names JSON file does not exist, no names will be added from a file.
+         * If you would like to detect this case, first `findObject()`, then use
+         * `ObjectInfo::loadRecognizedNames()`, which returns a `std::optional`.
+         *
+         * @param includeClassName If true, include the raw class name in the result.
+         * @see `ObjectInfo::loadRecognizedNames()`
+         */
+        std::vector<std::string> loadRecognizedNames(const ObjectID& objectID, bool includeClassName = false) const;
+        /**
+         * @brief Load names to use when verbalizing an object name.
+         *
+         * If the object's names JSON file does not exist, no names will be added from a file.
+         * If you would like to detect this case, first `findObject()`, then use
+         * `ObjectInfo::loadSpokenNames()`, which returns a `std::optional`.
+         *
+         * @param includeClassName If true, include the raw class name in the result.
+         * @see `ObjectInfo::loadSpokenNames()`
+         */
+        std::vector<std::string> loadSpokenNames(const ObjectID& objectID, bool includeClassName = false) const;
+
+
     private:
         void init() const;
 
diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.cpp b/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.cpp
index 1dc1903ea..8c70a93aa 100644
--- a/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.cpp
+++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.cpp
@@ -1,5 +1,6 @@
 #include "ObjectInfo.h"
 
+#include <SimoxUtility/algorithm/string.h>
 #include <SimoxUtility/json.h>
 #include <SimoxUtility/shapes/AxisAlignedBoundingBox.h>
 #include <SimoxUtility/shapes/OrientedBox.h>
@@ -85,6 +86,11 @@ namespace armarx
         return file(".json", "_bb");
     }
 
+    PackageFileLocation ObjectInfo::namesJson() const
+    {
+        return file(".json", "_names");
+    }
+
     std::optional<simox::AxisAlignedBoundingBox> ObjectInfo::loadAABB() const
     {
         nlohmann::json j;
@@ -147,6 +153,47 @@ namespace armarx
         return oobb;
     }
 
+    std::optional<std::vector<std::string>> ObjectInfo::loadRecognizedNames() const
+    {
+        return loadNames("recognized_name");
+    }
+
+    std::optional<std::vector<std::string>> ObjectInfo::loadSpokenNames() const
+    {
+        return loadNames("spoken_name");
+    }
+
+    std::optional<std::vector<std::string> > ObjectInfo::loadNames(const std::string& jsonKey) const
+    {
+        const PackageFileLocation file = namesJson();
+        if (fs::is_regular_file(file.absolutePath))
+        {
+            nlohmann::json json;
+
+            try
+            {
+                json = nlohmann::read_json(file.absolutePath);
+            }
+            catch (const nlohmann::json::exception& e)
+            {
+                ARMARX_WARNING << "Failed to parse JSON file " << file.absolutePath << ": \n" << e.what();
+                return std::nullopt;
+            }
+            catch (const std::exception& e)
+            {
+                ARMARX_WARNING << "Failed to read file " << file.absolutePath << ": \n" << e.what();
+                return std::nullopt;
+            }
+
+            return json.at(jsonKey).get<std::vector<std::string>>();
+        }
+        else
+        {
+            return std::nullopt;
+        }
+    }
+
+
     bool ObjectInfo::checkPaths() const
     {
         namespace fs = std::filesystem;
diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.h b/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.h
index 5f50ec956..9888a4c7a 100644
--- a/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.h
+++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectInfo.h
@@ -64,6 +64,10 @@ namespace armarx
 
         PackageFileLocation boundingBoxJson() const;
 
+        /// File containing recognized and spoken names of objects.
+        PackageFileLocation namesJson() const;
+
+
         /**
          * @brief Load the AABB (axis-aligned bounding-box) from the bounding box JSON file.
          * @return Return the AABB if successful, `std::nullopt` if file does not exist.
@@ -76,6 +80,17 @@ namespace armarx
          */
         std::optional<simox::OrientedBox<float>> loadOOBB() const;
 
+        /**
+         * @brief Load names to use when matched when recognizing an object by name.
+         * @see `namesJson()`
+         */
+        std::optional<std::vector<std::string>> loadRecognizedNames() const;
+        /**
+         * @brief Load names to use when verbalizing an object name.
+         * @see `namesJson()`
+         */
+        std::optional<std::vector<std::string>> loadSpokenNames() const;
+
 
         /**
          * @brief Checks the existence of expected files.
@@ -88,6 +103,7 @@ namespace armarx
     private:
 
         path objectDirectory() const;
+        std::optional<std::vector<std::string>> loadNames(const std::string& jsonKey) const;
 
 
     private:
-- 
GitLab