diff --git a/tests/ut/cpp/dataset/CMakeLists.txt b/tests/ut/cpp/dataset/CMakeLists.txt index bfdc2b4cb343be0a9e8b8a4ead97949d1e5b7e92..eda1ca88acebfc1eacc8eaac5f20bd89c3b12dc4 100644 --- a/tests/ut/cpp/dataset/CMakeLists.txt +++ b/tests/ut/cpp/dataset/CMakeLists.txt @@ -3,6 +3,7 @@ include(GoogleTest) SET(DE_UT_SRCS common/common.cc common/cvop_common.cc + common/bboxop_common.cc batch_op_test.cc bit_functions_test.cc storage_container_test.cc diff --git a/tests/ut/cpp/dataset/common/bboxop_common.cc b/tests/ut/cpp/dataset/common/bboxop_common.cc new file mode 100644 index 0000000000000000000000000000000000000000..70e6b5a339e45c8e313b381d844e4af3dac5b055 --- /dev/null +++ b/tests/ut/cpp/dataset/common/bboxop_common.cc @@ -0,0 +1,230 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * 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 "bboxop_common.h" + +#include +#include +#include +#include + +#include + +#include "./tinyxml2.h" +#include "opencv2/opencv.hpp" +#include "common/utils.h" +#include "dataset/core/cv_tensor.h" +#include "dataset/util/path.h" +#include "dataset/core/constants.h" +#include "utils/log_adapter.h" + +using namespace mindspore::dataset; +using namespace UT::CVOP::BBOXOP; +using tinyxml2::XMLDocument; +using tinyxml2::XMLElement; +using tinyxml2::XMLError; + +const char kAnnotationsFolder[] = "/Annotations/"; +const char kImagesFolder[] = "/JPEGImages/"; +const char kExpectedName[] = "Expected"; +const char kActualName[] = "Actual"; +const char kAnnotExt[] = ".xml"; +const char kImageExt[] = ".jpg"; + +BBoxOpCommon::BBoxOpCommon() {} + +BBoxOpCommon::~BBoxOpCommon() {} + +void BBoxOpCommon::SetUp() { + MS_LOG(INFO) << "starting test."; + image_folder_build_ = "data/dataset/imagefolder/"; + image_folder_src_ = "../../../../../tests/ut/data/dataset/imagefolder/"; + std::string dir_path = "data/dataset/testVOC2012_2"; + GetInputImagesAndAnnotations(dir_path); +} + +void BBoxOpCommon::GetInputImagesAndAnnotations(const std::string &dir, std::size_t num_of_samples) { + std::string images_path = dir + std::string(kImagesFolder); + std::string annots_path = dir + std::string(kAnnotationsFolder); + Path dir_path(images_path); + std::shared_ptr image_dir_itr = Path::DirIterator::OpenDirectory(&dir_path); + std::vector paths_to_fetch; + if (!dir_path.Exists()) { + MS_LOG(ERROR) << "Images folder was not found : " + images_path; + EXPECT_TRUE(dir_path.Exists()); + } + std::size_t files_fetched = 0; + // get image file paths + while (image_dir_itr->hasNext() && files_fetched < num_of_samples) { + Path image_path = image_dir_itr->next(); + if (image_path.Extension() == std::string(kImageExt)) { + paths_to_fetch.push_back(image_path.toString()); + files_fetched++; + } + } + // sort fetched files + std::sort(paths_to_fetch.begin(), paths_to_fetch.end()); + for (const auto &image_file : paths_to_fetch) { + std::string image_ext = std::string(kImageExt); + std::string annot_file = image_file; + std::size_t pos = 0; + // first replace the Image dir with the Annotation dir. + if ((pos = image_file.find(std::string(kImagesFolder), 0)) != std::string::npos) { + annot_file.replace(pos, std::string(kImagesFolder).length(), std::string(kAnnotationsFolder)); + } + // then replace the extensions. the image extension to annotation extension + if ((pos = annot_file.find(image_ext, 0)) != std::string::npos) { + annot_file.replace(pos, std::string(kAnnotExt).length(), std::string(kAnnotExt)); + } + std::shared_ptr annotation_tensor; + // load annotations and log failure + if (!LoadAnnotationFile(annot_file, &annotation_tensor)) { + MS_LOG(ERROR) << "Loading Annotations failed in GetInputImagesAndAnnotations"; + EXPECT_EQ(0, 1); + } + // load image + GetInputImage(image_file); + // add image and annotation to the tensor table + TensorRow row_data({std::move(input_tensor_), std::move(annotation_tensor)}); + images_and_annotations_.push_back(row_data); + } +} + +void BBoxOpCommon::SaveImagesWithAnnotations(BBoxOpCommon::FileType type, const std::string &op_name, + const TensorTable &table) { + int i = 0; + for (auto &row : table) { + std::shared_ptr row_to_save; + Status swap_status = SwapRedAndBlue(row[0], &row_to_save); + if (!swap_status.IsOk()) { + MS_LOG(ERROR) << "Swaping red and blue channels failed in SaveImagesWithAnnotations."; + EXPECT_TRUE(swap_status.IsOk()); + } + cv::Mat image = std::static_pointer_cast(row_to_save)->mat(); + uint32_t num_of_boxes = row[1]->shape()[0]; + bool passing_data_fetch = true; + // For each bounding box draw on the image. + for (uint32_t i = 0; i < num_of_boxes; i++) { + uint32_t x = 0; + uint32_t y = 0; + uint32_t w = 0; + uint32_t h = 0; + passing_data_fetch &= row[1]->GetUnsignedIntAt(&x, {i, 0}).IsOk(); + passing_data_fetch &= row[1]->GetUnsignedIntAt(&y, {i, 1}).IsOk(); + passing_data_fetch &= row[1]->GetUnsignedIntAt(&w, {i, 2}).IsOk(); + passing_data_fetch &= row[1]->GetUnsignedIntAt(&h, {i, 3}).IsOk(); + if (!passing_data_fetch) { + MS_LOG(ERROR) << "Fetching bbox coordinates failed in SaveImagesWithAnnotations."; + EXPECT_TRUE(passing_data_fetch); + } + cv::Rect bbox(x, y, w, h); + cv::rectangle(image, bbox, cv::Scalar(255, 0, 0), 10, 8, 0); + } + bool im_write_success = false; + // if user wants to save an expected image, use the path to the source folder. + if (type == FileType::kExpected) { + im_write_success = cv::imwrite( + image_folder_src_ + std::string(kExpectedName) + op_name + std::to_string(i) + std::string(kImageExt), image); + } else { + // otherwise if we are saving actual images only for comparison, save in current working dir in build folders. + im_write_success = + cv::imwrite(std::string(kActualName) + op_name + std::to_string(i) + std::string(kImageExt), image); + } + if (!im_write_success) { + MS_LOG(ERROR) << "Image write failed in SaveImagesWithAnnotations."; + EXPECT_TRUE(im_write_success); + } + i += 1; + } +} + +void BBoxOpCommon::CompareActualAndExpected(const std::string &op_name) { + size_t num_of_images = images_and_annotations_.size(); + for (size_t i = 0; i < num_of_images; i++) { + // load actual and expected images. + std::string actual_path = std::string(kActualName) + op_name + std::to_string(i) + std::string(kImageExt); + std::string expected_path = + image_folder_build_ + std::string(kExpectedName) + op_name + std::to_string(i) + std::string(kImageExt); + cv::Mat expect_img = cv::imread(expected_path, cv::IMREAD_COLOR); + cv::Mat actual_img = cv::imread(actual_path, cv::IMREAD_COLOR); + // after comparison is done remove temporary file + EXPECT_TRUE(remove(actual_path.c_str()) == 0); + // compare using ==operator by Tensor + if (actual_img.data) { + EXPECT_EQ(CVTensor(expect_img) == CVTensor(actual_img), true); + } else { + MS_LOG(ERROR) << "Not pass verification! Image data is null."; + EXPECT_EQ(0, 1); + } + } +} + +bool BBoxOpCommon::LoadAnnotationFile(const std::string &path, std::shared_ptr *target_BBox) { + if (!Path(path).Exists()) { + MS_LOG(ERROR) << "File is not found : " + path; + return false; + } + XMLDocument doc; + XMLError e = doc.LoadFile(mindspore::common::SafeCStr(path)); + if (e != XMLError::XML_SUCCESS) { + MS_LOG(ERROR) << "Xml load failed"; + return false; + } + XMLElement *root = doc.RootElement(); + if (root == nullptr) { + MS_LOG(ERROR) << "Xml load root element error"; + return false; + } + XMLElement *object = root->FirstChildElement("object"); + if (object == nullptr) { + MS_LOG(ERROR) << "No object find in " + path; + return false; + } + std::vector return_value_list; + dsize_t bbox_count = 0; // keep track of number of bboxes in file + dsize_t bbox_val_count = 4; // creating bboxes of size 4 to test function + // FILE OK TO READ + while (object != nullptr) { + bbox_count += 1; + std::string label_name; + uint32_t xmin = 0, ymin = 0, xmax = 0, ymax = 0; + XMLElement *bbox_node = object->FirstChildElement("bndbox"); + if (bbox_node != nullptr) { + XMLElement *xmin_node = bbox_node->FirstChildElement("xmin"); + if (xmin_node != nullptr) xmin = xmin_node->UnsignedText(); + XMLElement *ymin_node = bbox_node->FirstChildElement("ymin"); + if (ymin_node != nullptr) ymin = ymin_node->UnsignedText(); + XMLElement *xmax_node = bbox_node->FirstChildElement("xmax"); + if (xmax_node != nullptr) xmax = xmax_node->UnsignedText(); + XMLElement *ymax_node = bbox_node->FirstChildElement("ymax"); + if (ymax_node != nullptr) ymax = ymax_node->UnsignedText(); + } else { + MS_LOG(ERROR) << "bndbox dismatch in " + path; + return false; + } + if (xmin > 0 && ymin > 0 && xmax > xmin && ymax > ymin) { + for (auto item : {xmin, ymin, xmax - xmin, ymax - ymin}) { + return_value_list.push_back(item); + } + } + object = object->NextSiblingElement("object"); // Read next BBox if exists + } + std::shared_ptr ret_value; + Status s = Tensor::CreateTensor(&ret_value, return_value_list, TensorShape({bbox_count, bbox_val_count})); + EXPECT_TRUE(s.IsOk()); + (*target_BBox) = ret_value; // load bbox from file into return + return true; +} diff --git a/tests/ut/cpp/dataset/common/bboxop_common.h b/tests/ut/cpp/dataset/common/bboxop_common.h new file mode 100644 index 0000000000000000000000000000000000000000..ba3ceb62d941f845d328d1f85042119d946d462b --- /dev/null +++ b/tests/ut/cpp/dataset/common/bboxop_common.h @@ -0,0 +1,74 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * 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 TESTS_DATASET_UT_CORE_COMMON_DE_UT_BBOXOP_COMMON_H_ +#define TESTS_DATASET_UT_CORE_COMMON_DE_UT_BBOXOP_COMMON_H_ + +#include "cvop_common.h" +#include "dataset/util/path.h" + +namespace UT { +namespace CVOP { +namespace BBOXOP { + +class BBoxOpCommon : public CVOpCommon { + public: + enum FileType { + kExpected, + kActual + }; + + BBoxOpCommon(); + + ~BBoxOpCommon(); + + /// \brief Sets up the class's variable, images_and_annotations + void SetUp() override; + + /// \brief Get all images and annotations in images_and_annotations TensorTable from dir + /// \param[in] dir directory containing images and annotation folders + /// \param[in] num_of_samples number of rows to fetch (default = 1) + void GetInputImagesAndAnnotations(const std::string &dir, std::size_t num_of_samples = 1); + + /// \brief Save the given tensor table + /// \param[in] type type of images to be stored (e.g. Expected or Actual) + /// \param[in] op_name name of op being tested + /// \param[in] table rows of images and corresponding annotations + void SaveImagesWithAnnotations(FileType type, const std::string &op_name, const TensorTable &table); + + /// \brief Compare actual and expected results. The images will have the bounding boxes on them + /// Log if images don't match + /// \param[in] op_name name of op being tested + void CompareActualAndExpected(const std::string &op_name); + + /// \brief Load BBox data from an XML file into a Tensor + /// \param[in] path path to XML bbox data file + /// \param[inout] target_BBox pointer to a Tensor to load + /// \return True if file loaded successfully, false if error -> logged to STD out + bool LoadAnnotationFile(const std::string &path, std::shared_ptr *target_BBox); + + TensorTable images_and_annotations_; + + private: + // directory of image_folder when the dataset/data gets transferred to build + std::string image_folder_build_; + // directory of image_folder in the source project (used to store expected results) + std::string image_folder_src_; +}; +} // namespace BBOXOP +} // namespace CVOP +} // namespace UT + +#endif // TESTS_DATASET_UT_CORE_COMMON_DE_UT_BBOXOP_COMMON_H_