diff --git a/mindspore/ccsrc/minddata/dataset/api/python/bindings/dataset/kernels/image/bindings.cc b/mindspore/ccsrc/minddata/dataset/api/python/bindings/dataset/kernels/image/bindings.cc index ae351625da..35b05a7942 100644 --- a/mindspore/ccsrc/minddata/dataset/api/python/bindings/dataset/kernels/image/bindings.cc +++ b/mindspore/ccsrc/minddata/dataset/api/python/bindings/dataset/kernels/image/bindings.cc @@ -31,6 +31,7 @@ #include "minddata/dataset/kernels/image/mixup_batch_op.h" #include "minddata/dataset/kernels/image/normalize_op.h" #include "minddata/dataset/kernels/image/pad_op.h" +#include "minddata/dataset/kernels/image/random_affine_op.h" #include "minddata/dataset/kernels/image/random_color_adjust_op.h" #include "minddata/dataset/kernels/image/random_crop_and_resize_op.h" #include "minddata/dataset/kernels/image/random_crop_and_resize_with_bbox_op.h" @@ -115,6 +116,19 @@ PYBIND_REGISTER(ResizeWithBBoxOp, 1, ([](const py::module *m) { py::arg("interpolation") = ResizeWithBBoxOp::kDefInterpolation); })); +PYBIND_REGISTER(RandomAffineOp, 1, ([](const py::module *m) { + (void)py::class_>( + *m, "RandomAffineOp", "Tensor operation to apply random affine transformations on an image.") + .def(py::init, std::vector, std::vector, + std::vector, InterpolationMode, std::vector>(), + py::arg("degrees") = RandomAffineOp::kDegreesRange, + py::arg("translate_range") = RandomAffineOp::kTranslationPercentages, + py::arg("scale_range") = RandomAffineOp::kScaleRange, + py::arg("shear_ranges") = RandomAffineOp::kShearRanges, + py::arg("interpolation") = RandomAffineOp::kDefInterpolation, + py::arg("fill_value") = RandomAffineOp::kFillValue); + })); + PYBIND_REGISTER( RandomResizeWithBBoxOp, 1, ([](const py::module *m) { (void)py::class_>( diff --git a/mindspore/ccsrc/minddata/dataset/api/transforms.cc b/mindspore/ccsrc/minddata/dataset/api/transforms.cc index cec690c503..d16a7c46fa 100644 --- a/mindspore/ccsrc/minddata/dataset/api/transforms.cc +++ b/mindspore/ccsrc/minddata/dataset/api/transforms.cc @@ -25,6 +25,7 @@ #include "minddata/dataset/kernels/image/normalize_op.h" #include "minddata/dataset/kernels/data/one_hot_op.h" #include "minddata/dataset/kernels/image/pad_op.h" +#include "minddata/dataset/kernels/image/random_affine_op.h" #include "minddata/dataset/kernels/image/random_color_adjust_op.h" #include "minddata/dataset/kernels/image/random_crop_op.h" #include "minddata/dataset/kernels/image/random_horizontal_flip_op.h" @@ -136,6 +137,22 @@ std::shared_ptr RandomColorAdjust(std::vector return op; } +// Function to create RandomAffineOperation. +std::shared_ptr RandomAffine(const std::vector °rees, + const std::vector &translate_range, + const std::vector &scale_range, + const std::vector &shear_ranges, + InterpolationMode interpolation, + const std::vector &fill_value) { + auto op = std::make_shared(degrees, translate_range, scale_range, shear_ranges, interpolation, + fill_value); + // Input validation + if (!op->ValidateParams()) { + return nullptr; + } + return op; +} + // Function to create RandomCropOperation. std::shared_ptr RandomCrop(std::vector size, std::vector padding, bool pad_if_needed, std::vector fill_value, @@ -452,6 +469,82 @@ std::shared_ptr RandomColorAdjustOperation::Build() { return tensor_op; } +// RandomAffineOperation +RandomAffineOperation::RandomAffineOperation(const std::vector °rees, + const std::vector &translate_range, + const std::vector &scale_range, + const std::vector &shear_ranges, InterpolationMode interpolation, + const std::vector &fill_value) + : degrees_(degrees), + translate_range_(translate_range), + scale_range_(scale_range), + shear_ranges_(shear_ranges), + interpolation_(interpolation), + fill_value_(fill_value) {} + +bool RandomAffineOperation::ValidateParams() { + // Degrees + if (degrees_.size() != 2) { + MS_LOG(ERROR) << "RandomAffine: degrees vector has incorrect size: degrees.size() = " << degrees_.size(); + return false; + } + if (degrees_[0] > degrees_[1]) { + MS_LOG(ERROR) << "RandomAffine: minimum of degrees range is greater than maximum: min = " << degrees_[0] + << ", max = " << degrees_[1]; + return false; + } + // Translate + if (translate_range_.size() != 2) { + MS_LOG(ERROR) << "RandomAffine: translate_range vector has incorrect size: translate_range.size() = " + << translate_range_.size(); + return false; + } + if (translate_range_[0] > translate_range_[1]) { + MS_LOG(ERROR) << "RandomAffine: minimum of translate range is greater than maximum: min = " << translate_range_[0] + << ", max = " << translate_range_[1]; + return false; + } + // Scale + if (scale_range_.size() != 2) { + MS_LOG(ERROR) << "RandomAffine: scale_range vector has incorrect size: scale_range.size() = " + << scale_range_.size(); + return false; + } + if (scale_range_[0] > scale_range_[1]) { + MS_LOG(ERROR) << "RandomAffine: minimum of scale range is greater than maximum: min = " << scale_range_[0] + << ", max = " << scale_range_[1]; + return false; + } + // Shear + if (shear_ranges_.size() != 4) { + MS_LOG(ERROR) << "RandomAffine: shear_ranges vector has incorrect size: shear_ranges.size() = " + << shear_ranges_.size(); + return false; + } + if (shear_ranges_[0] > shear_ranges_[1]) { + MS_LOG(ERROR) << "RandomAffine: minimum of horizontal shear range is greater than maximum: min = " + << shear_ranges_[0] << ", max = " << shear_ranges_[1]; + return false; + } + if (shear_ranges_[2] > shear_ranges_[3]) { + MS_LOG(ERROR) << "RandomAffine: minimum of vertical shear range is greater than maximum: min = " << shear_ranges_[2] + << ", max = " << scale_range_[3]; + return false; + } + // Fill Value + if (fill_value_.size() != 3) { + MS_LOG(ERROR) << "RandomAffine: fill_value vector has incorrect size: fill_value.size() = " << fill_value_.size(); + return false; + } + return true; +} + +std::shared_ptr RandomAffineOperation::Build() { + auto tensor_op = std::make_shared(degrees_, translate_range_, scale_range_, shear_ranges_, + interpolation_, fill_value_); + return tensor_op; +} + // RandomCropOperation RandomCropOperation::RandomCropOperation(std::vector size, std::vector padding, bool pad_if_needed, std::vector fill_value, BorderType padding_mode) diff --git a/mindspore/ccsrc/minddata/dataset/include/transforms.h b/mindspore/ccsrc/minddata/dataset/include/transforms.h index 32f474b579..967fe1b57b 100644 --- a/mindspore/ccsrc/minddata/dataset/include/transforms.h +++ b/mindspore/ccsrc/minddata/dataset/include/transforms.h @@ -55,6 +55,7 @@ class MixUpBatchOperation; class NormalizeOperation; class OneHotOperation; class PadOperation; +class RandomAffineOperation; class RandomColorAdjustOperation; class RandomCropOperation; class RandomHorizontalFlipOperation; @@ -134,6 +135,23 @@ std::shared_ptr OneHot(int32_t num_classes); std::shared_ptr Pad(std::vector padding, std::vector fill_value = {0}, BorderType padding_mode = BorderType::kConstant); +/// \brief Function to create a RandomAffine TensorOperation. +/// \notes Applies a Random Affine transformation on input image in RGB or Greyscale mode. +/// \param[in] degrees A float vector size 2, representing the starting and ending degree +/// \param[in] translate_range A float vector size 2, representing percentages of translation on x and y axes. +/// \param[in] scale_range A float vector size 2, representing the starting and ending scales in the range. +/// \param[in] shear_ranges A float vector size 4, representing the starting and ending shear degrees vertically and +/// horizontally. +/// \param[in] interpolation An enum for the mode of interpolation +/// \param[in] fill_value A uint8_t vector size 3, representing the pixel intensity of the borders, it is used to +/// fill R, G, B channels respectively. +/// \return Shared pointer to the current TensorOperation. +std::shared_ptr RandomAffine( + const std::vector °rees, const std::vector &translate_range = {0.0, 0.0}, + const std::vector &scale_range = {1.0, 1.0}, const std::vector &shear_ranges = {0.0, 0.0, 0.0, 0.0}, + InterpolationMode interpolation = InterpolationMode::kNearestNeighbour, + const std::vector &fill_value = {0, 0, 0}); + /// \brief Randomly adjust the brightness, contrast, saturation, and hue of the input image /// \param[in] brightness Brightness adjustment factor. Must be a vector of one or two values /// if it's a vector of two values it needs to be in the form of [min, max]. Default value is {1, 1} @@ -333,6 +351,29 @@ class PadOperation : public TensorOperation { BorderType padding_mode_; }; +class RandomAffineOperation : public TensorOperation { + public: + RandomAffineOperation(const std::vector °rees, const std::vector &translate_range = {0.0, 0.0}, + const std::vector &scale_range = {1.0, 1.0}, + const std::vector &shear_ranges = {0.0, 0.0, 0.0, 0.0}, + InterpolationMode interpolation = InterpolationMode::kNearestNeighbour, + const std::vector &fill_value = {0, 0, 0}); + + ~RandomAffineOperation() = default; + + std::shared_ptr Build() override; + + bool ValidateParams() override; + + private: + std::vector degrees_; // min_degree, max_degree + std::vector translate_range_; // maximum x translation percentage, maximum y translation percentage + std::vector scale_range_; // min_scale, max_scale + std::vector shear_ranges_; // min_x_shear, max_x_shear, min_y_shear, max_y_shear + InterpolationMode interpolation_; + std::vector fill_value_; +}; + class RandomColorAdjustOperation : public TensorOperation { public: RandomColorAdjustOperation(std::vector brightness = {1.0, 1.0}, std::vector contrast = {1.0, 1.0}, diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/CMakeLists.txt b/mindspore/ccsrc/minddata/dataset/kernels/image/CMakeLists.txt index 9f55aee32b..1b3256f37f 100644 --- a/mindspore/ccsrc/minddata/dataset/kernels/image/CMakeLists.txt +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/CMakeLists.txt @@ -1,6 +1,7 @@ file(GLOB_RECURSE _CURRENT_SRC_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.cc") set_property(SOURCE ${_CURRENT_SRC_FILES} PROPERTY COMPILE_DEFINITIONS SUBMODULE_ID=mindspore::SubModuleId::SM_MD) add_library(kernels-image OBJECT + affine_op.cc auto_contrast_op.cc center_crop_op.cc crop_op.cc @@ -10,9 +11,11 @@ add_library(kernels-image OBJECT hwc_to_chw_op.cc image_utils.cc invert_op.cc + math_utils.cc mixup_batch_op.cc normalize_op.cc pad_op.cc + random_affine_op.cc random_color_adjust_op.cc random_crop_decode_resize_op.cc random_crop_and_resize_with_bbox_op.cc diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/affine_op.cc b/mindspore/ccsrc/minddata/dataset/kernels/image/affine_op.cc new file mode 100644 index 0000000000..1ea1ea434c --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/affine_op.cc @@ -0,0 +1,99 @@ +/** + * 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 +#include +#include +#include + +#include "minddata/dataset/kernels/image/affine_op.h" +#include "minddata/dataset/kernels/image/image_utils.h" +#include "minddata/dataset/kernels/image/math_utils.h" +#include "minddata/dataset/util/random.h" + +namespace mindspore { +namespace dataset { + +const InterpolationMode AffineOp::kDefInterpolation = InterpolationMode::kNearestNeighbour; +const float_t AffineOp::kDegrees = 0.0; +const std::vector AffineOp::kTranslation = {0.0, 0.0}; +const float_t AffineOp::kScale = 1.0; +const std::vector AffineOp::kShear = {0.0, 0.0}; +const std::vector AffineOp::kFillValue = {0, 0, 0}; + +AffineOp::AffineOp(float_t degrees, const std::vector &translation, float_t scale, + const std::vector &shear, InterpolationMode interpolation, + const std::vector &fill_value) + : degrees_(degrees), + translation_(translation), + scale_(scale), + shear_(shear), + interpolation_(interpolation), + fill_value_(fill_value) {} + +Status AffineOp::Compute(const std::shared_ptr &input, std::shared_ptr *output) { + IO_CHECK(input, output); + float_t translation_x = translation_[0]; + float_t translation_y = translation_[1]; + float_t degrees = 0.0; + DegreesToRadians(degrees_, °rees); + float_t shear_x = shear_[0]; + float_t shear_y = shear_[1]; + DegreesToRadians(shear_x, &shear_x); + DegreesToRadians(-1 * shear_y, &shear_y); + std::shared_ptr input_cv = CVTensor::AsCVTensor(input); + + // Apply Affine Transformation + // T is translation matrix: [1, 0, tx | 0, 1, ty | 0, 0, 1] + // C is translation matrix to keep center: [1, 0, cx | 0, 1, cy | 0, 0, 1] + // RSS is rotation with scale and shear matrix + // RSS(a, s, (sx, sy)) = + // = R(a) * S(s) * SHy(sy) * SHx(sx) + // = [ s*cos(a - sy)/cos(sy), s*(-cos(a - sy)*tan(x)/cos(y) - sin(a)), 0 ] + // [ s*sin(a - sy)/cos(sy), s*(-sin(a - sy)*tan(x)/cos(y) + cos(a)), 0 ] + // [ 0 , 0 , 1 ] + // + // where R is a rotation matrix, S is a scaling matrix, and SHx and SHy are the shears: + // SHx(s) = [1, -tan(s)] and SHy(s) = [1 , 0] + // [0, 1 ] [-tan(s), 1] + // + // Thus, the affine matrix is M = T * C * RSS * C^-1 + + float_t cx = ((input_cv->mat().cols - 1) / 2.0); + float_t cy = ((input_cv->mat().rows - 1) / 2.0); + // Calculate RSS + std::vector matrix{scale_ * cos(degrees + shear_y) / cos(shear_y), + scale_ * (-1 * cos(degrees + shear_y) * tan(shear_x) / cos(shear_y) - sin(degrees)), + 0, + scale_ * sin(degrees + shear_y) / cos(shear_y), + scale_ * (-1 * sin(degrees + shear_y) * tan(shear_x) / cos(shear_y) + cos(degrees)), + 0}; + // Compute T * C * RSS * C^-1 + matrix[2] = (1 - matrix[0]) * cx - matrix[1] * cy + translation_x; + matrix[5] = (1 - matrix[4]) * cy - matrix[3] * cx + translation_y; + cv::Mat affine_mat(matrix); + affine_mat = affine_mat.reshape(1, {2, 3}); + + std::shared_ptr output_cv; + RETURN_IF_NOT_OK(CVTensor::CreateEmpty(input_cv->shape(), input_cv->type(), &output_cv)); + RETURN_UNEXPECTED_IF_NULL(output_cv); + cv::warpAffine(input_cv->mat(), output_cv->mat(), affine_mat, input_cv->mat().size(), + GetCVInterpolationMode(interpolation_), cv::BORDER_CONSTANT, + cv::Scalar(fill_value_[0], fill_value_[1], fill_value_[2])); + (*output) = std::static_pointer_cast(output_cv); + return Status::OK(); +} +} // namespace dataset +} // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/affine_op.h b/mindspore/ccsrc/minddata/dataset/kernels/image/affine_op.h new file mode 100644 index 0000000000..c84863e4d5 --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/affine_op.h @@ -0,0 +1,68 @@ +/** + * 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 MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_IMAGE_AFFINE_OP_H_ +#define MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_IMAGE_AFFINE_OP_H_ + +#include +#include +#include + +#include "minddata/dataset/core/cv_tensor.h" +#include "minddata/dataset/core/tensor.h" +#include "minddata/dataset/kernels/tensor_op.h" +#include "minddata/dataset/util/status.h" + +namespace mindspore { +namespace dataset { +class AffineOp : public TensorOp { + public: + /// Default values + static const float_t kDegrees; + static const std::vector kTranslation; + static const float_t kScale; + static const std::vector kShear; + static const InterpolationMode kDefInterpolation; + static const std::vector kFillValue; + + /// Constructor + public: + explicit AffineOp(float_t degrees, const std::vector &translation = kTranslation, float_t scale = kScale, + const std::vector &shear = kShear, InterpolationMode interpolation = kDefInterpolation, + const std::vector &fill_value = kFillValue); + + ~AffineOp() override = default; + + std::string Name() const override { return kAffineOp; } + + Status Compute(const std::shared_ptr &input, std::shared_ptr *output) override; + + /// Member variables + private: + std::string kAffineOp = "AffineOp"; + + protected: + float_t degrees_; + std::vector translation_; // translation_x and translation_y + float_t scale_; + std::vector shear_; // shear_x and shear_y + InterpolationMode interpolation_; + std::vector fill_value_; +}; +} // namespace dataset +} // namespace mindspore + +#endif // MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_IMAGE_AFFINE_OP_H_ diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/image_utils.cc b/mindspore/ccsrc/minddata/dataset/kernels/image/image_utils.cc index 86de12597b..14420e5553 100644 --- a/mindspore/ccsrc/minddata/dataset/kernels/image/image_utils.cc +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/image_utils.cc @@ -21,6 +21,7 @@ #include #include #include "utils/ms_utils.h" +#include "minddata/dataset/kernels/image/math_utils.h" #include "minddata/dataset/core/constants.h" #include "minddata/dataset/core/cv_tensor.h" #include "minddata/dataset/core/tensor.h" @@ -631,36 +632,9 @@ Status AutoContrast(const std::shared_ptr &input, std::shared_ptr((cutoff / 100.0) * n); - if (cut != 0) { - for (int32_t lo = 0; lo < 256 && cut > 0; lo++) { - if (cut > hist_vec[lo]) { - cut -= hist_vec[lo]; - hist_vec[lo] = 0; - } else { - hist_vec[lo] -= cut; - cut = 0; - } - } - cut = static_cast((cutoff / 100.0) * n); - for (int32_t hi = 255; hi >= 0 && cut > 0; hi--) { - if (cut > hist_vec[hi]) { - cut -= hist_vec[hi]; - hist_vec[hi] = 0; - } else { - hist_vec[hi] -= cut; - cut = 0; - } - } - } - int32_t lo = 0; int32_t hi = 255; - for (; lo < 256 && !hist_vec[lo]; lo++) { - } - for (; hi >= 0 && !hist_vec[hi]; hi--) { - } + int32_t lo = 0; + RETURN_IF_NOT_OK(ComputeUpperAndLowerPercentiles(&hist_vec, cutoff, cutoff, &hi, &lo)); if (hi <= lo) { for (int32_t i = 0; i < 256; i++) { table.push_back(i); @@ -685,7 +659,6 @@ Status AutoContrast(const std::shared_ptr &input, std::shared_ptr output_cv; RETURN_IF_NOT_OK(CVTensor::CreateFromMat(result, &output_cv)); (*output) = std::static_pointer_cast(output_cv); - (*output) = std::static_pointer_cast(output_cv); (*output)->Reshape(input->shape()); } catch (const cv::Exception &e) { RETURN_STATUS_UNEXPECTED("Error in auto contrast"); diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/math_utils.cc b/mindspore/ccsrc/minddata/dataset/kernels/image/math_utils.cc new file mode 100644 index 0000000000..287df68f31 --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/math_utils.cc @@ -0,0 +1,84 @@ +/** + * 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 "minddata/dataset/kernels/image/math_utils.h" + +#include + +#include +#include + +namespace mindspore { +namespace dataset { +Status ComputeUpperAndLowerPercentiles(std::vector *hist, int32_t hi_p, int32_t low_p, int32_t *hi, + int32_t *lo) { + try { + int32_t n = std::accumulate(hist->begin(), hist->end(), 0); + int32_t cut = static_cast((low_p / 100.0) * n); + for (int32_t lb = 0; lb < hist->size() + 1 && cut > 0; lb++) { + if (cut > (*hist)[lb]) { + cut -= (*hist)[lb]; + (*hist)[lb] = 0; + } else { + (*hist)[lb] -= cut; + cut = 0; + } + } + cut = static_cast((hi_p / 100.0) * n); + for (int32_t ub = hist->size() - 1; ub >= 0 && cut > 0; ub--) { + if (cut > (*hist)[ub]) { + cut -= (*hist)[ub]; + (*hist)[ub] = 0; + } else { + (*hist)[ub] -= cut; + cut = 0; + } + } + *lo = 0; + *hi = hist->size() - 1; + for (; (*lo) < (*hi) && !(*hist)[*lo]; (*lo)++) { + } + for (; (*hi) >= 0 && !(*hist)[*hi]; (*hi)--) { + } + } catch (const std::exception &e) { + const char *err_msg = e.what(); + std::string err_message = "Error in ComputeUpperAndLowerPercentiles: "; + err_message += err_msg; + RETURN_STATUS_UNEXPECTED(err_message); + } + return Status::OK(); +} + +Status DegreesToRadians(float_t degrees, float_t *radians_target) { + *radians_target = CV_PI * degrees / 180.0; + return Status::OK(); +} + +Status GenerateRealNumber(float_t a, float_t b, std::mt19937 *rnd, float_t *result) { + try { + std::uniform_real_distribution distribution{a, b}; + *result = distribution(*rnd); + } catch (const std::exception &e) { + const char *err_msg = e.what(); + std::string err_message = "Error in GenerateRealNumber: "; + err_message += err_msg; + RETURN_STATUS_UNEXPECTED(err_message); + } + return Status::OK(); +} + +} // namespace dataset +} // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/math_utils.h b/mindspore/ccsrc/minddata/dataset/kernels/image/math_utils.h new file mode 100644 index 0000000000..66c28c0ae3 --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/math_utils.h @@ -0,0 +1,50 @@ +/** + * 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 MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_IMAGE_MATH_UTILS_H_ +#define MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_IMAGE_MATH_UTILS_H_ + +#include +#include +#include +#include "minddata/dataset/util/status.h" + +namespace mindspore { +namespace dataset { + +/// \brief Returns lower and upper pth percentiles of the input histogram. +/// \param[in] hist: Input histogram (mutates the histogram for computation purposes) +/// \param[in] hi_p: Right side percentile +/// \param[in] low_p: Left side percentile +/// \param[out] hi: Value at high end percentile +/// \param[out] lo: Value at low end percentile +Status ComputeUpperAndLowerPercentiles(std::vector *hist, int32_t hi_p, int32_t low_p, int32_t *hi, + int32_t *lo); + +/// \brief Converts degrees input to radians. +/// \param[in] degrees: Input degrees +/// \param[out] radians_target: Radians output +Status DegreesToRadians(float_t degrees, float_t *radians_target); + +/// \brief Generates a random real number in [a,b). +/// \param[in] a: Start of range +/// \param[in] b: End of range +/// \param[in] rnd: Random device +/// \param[out] result: Random number in range [a,b) +Status GenerateRealNumber(float_t a, float_t b, std::mt19937 *rnd, float_t *result); + +} // namespace dataset +} // namespace mindspore +#endif // MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_IMAGE_MATH_UTILS_H_ diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/random_affine_op.cc b/mindspore/ccsrc/minddata/dataset/kernels/image/random_affine_op.cc new file mode 100644 index 0000000000..bf2f6a99ce --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/random_affine_op.cc @@ -0,0 +1,77 @@ +/** + * 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 +#include +#include +#include + +#include "minddata/dataset/kernels/image/random_affine_op.h" +#include "minddata/dataset/kernels/image/image_utils.h" +#include "minddata/dataset/kernels/image/math_utils.h" +#include "minddata/dataset/util/random.h" + +namespace mindspore { +namespace dataset { + +const std::vector RandomAffineOp::kDegreesRange = {0.0, 0.0}; +const std::vector RandomAffineOp::kTranslationPercentages = {0.0, 0.0}; +const std::vector RandomAffineOp::kScaleRange = {1.0, 1.0}; +const std::vector RandomAffineOp::kShearRanges = {0.0, 0.0, 0.0, 0.0}; +const InterpolationMode RandomAffineOp::kDefInterpolation = InterpolationMode::kNearestNeighbour; +const std::vector RandomAffineOp::kFillValue = {0, 0, 0}; + +RandomAffineOp::RandomAffineOp(std::vector degrees, std::vector translate_range, + std::vector scale_range, std::vector shear_ranges, + InterpolationMode interpolation, std::vector fill_value) + : AffineOp(0.0), + degrees_range_(degrees), + translate_range_(translate_range), + scale_range_(scale_range), + shear_ranges_(shear_ranges) { + interpolation_ = interpolation; + fill_value_ = fill_value; + rnd_.seed(GetSeed()); +} + +Status RandomAffineOp::Compute(const std::shared_ptr &input, std::shared_ptr *output) { + IO_CHECK(input, output); + dsize_t height = input->shape()[0]; + dsize_t width = input->shape()[1]; + float_t max_dx = translate_range_[0] * height; + float_t max_dy = translate_range_[1] * width; + float_t degrees = 0.0; + RETURN_IF_NOT_OK(GenerateRealNumber(degrees_range_[0], degrees_range_[1], &rnd_, °rees)); + float_t translation_x = 0.0; + RETURN_IF_NOT_OK(GenerateRealNumber(-1 * max_dx, max_dx, &rnd_, &translation_x)); + float_t translation_y = 0.0; + RETURN_IF_NOT_OK(GenerateRealNumber(-1 * max_dy, max_dy, &rnd_, &translation_y)); + float_t scale = 1.0; + RETURN_IF_NOT_OK(GenerateRealNumber(scale_range_[0], scale_range_[1], &rnd_, &scale)); + float_t shear_x = 0.0; + RETURN_IF_NOT_OK(GenerateRealNumber(shear_ranges_[0], shear_ranges_[1], &rnd_, &shear_x)); + float_t shear_y = 0.0; + RETURN_IF_NOT_OK(GenerateRealNumber(shear_ranges_[2], shear_ranges_[3], &rnd_, &shear_y)); + // assign to base class variables + degrees_ = degrees; + scale_ = scale; + translation_[0] = translation_x; + translation_[1] = translation_y; + shear_[0] = shear_x; + shear_[1] = shear_y; + return AffineOp::Compute(input, output); +} +} // namespace dataset +} // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/random_affine_op.h b/mindspore/ccsrc/minddata/dataset/kernels/image/random_affine_op.h new file mode 100644 index 0000000000..dcaad46817 --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/random_affine_op.h @@ -0,0 +1,64 @@ +/** + * 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 MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_IMAGE_RANDOM_AFFINE_OP_H_ +#define MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_IMAGE_RANDOM_AFFINE_OP_H_ + +#include +#include +#include + +#include "minddata/dataset/core/cv_tensor.h" +#include "minddata/dataset/core/tensor.h" +#include "minddata/dataset/kernels/image/affine_op.h" +#include "minddata/dataset/util/status.h" + +namespace mindspore { +namespace dataset { +class RandomAffineOp : public AffineOp { + public: + /// Default values, also used by python_bindings.cc + static const std::vector kDegreesRange; + static const std::vector kTranslationPercentages; + static const std::vector kScaleRange; + static const std::vector kShearRanges; + static const InterpolationMode kDefInterpolation; + static const std::vector kFillValue; + + explicit RandomAffineOp(std::vector degrees, std::vector translate_range = kTranslationPercentages, + std::vector scale_range = kScaleRange, + std::vector shear_ranges = kShearRanges, + InterpolationMode interpolation = kDefInterpolation, + std::vector fill_value = kFillValue); + + ~RandomAffineOp() override = default; + + std::string Name() const override { return kRandomAffineOp; } + + Status Compute(const std::shared_ptr &input, std::shared_ptr *output) override; + + private: + std::string kRandomAffineOp = "RandomAffineOp"; + std::vector degrees_range_; // min_degree, max_degree + std::vector translate_range_; // maximum x translation percentage, maximum y translation percentage + std::vector scale_range_; // min_scale, max_scale + std::vector shear_ranges_; // min_x_shear, max_x_shear, min_y_shear, max_y_shear + std::mt19937 rnd_; // random device +}; +} // namespace dataset +} // namespace mindspore + +#endif // MINDSPORE_CCSRC_MINDDATA_DATASET_KERNELS_IMAGE_RANDOM_AFFINE_OP_H_ diff --git a/mindspore/dataset/transforms/vision/c_transforms.py b/mindspore/dataset/transforms/vision/c_transforms.py index 07e2952fd4..d9d2fc4422 100644 --- a/mindspore/dataset/transforms/vision/c_transforms.py +++ b/mindspore/dataset/transforms/vision/c_transforms.py @@ -47,7 +47,8 @@ from .utils import Inter, Border from .validators import check_prob, check_crop, check_resize_interpolation, check_random_resize_crop, \ check_mix_up_batch_c, check_normalize_c, check_random_crop, check_random_color_adjust, check_random_rotation, \ check_range, check_resize, check_rescale, check_pad, check_cutout, check_uniform_augment_cpp, \ - check_bounding_box_augment_cpp, check_random_select_subpolicy_op, check_auto_contrast, FLOAT_MAX_INTEGER + check_bounding_box_augment_cpp, check_random_select_subpolicy_op, check_auto_contrast, check_random_affine, \ + FLOAT_MAX_INTEGER DE_C_INTER_MODE = {Inter.NEAREST: cde.InterpolationMode.DE_INTER_NEAREST_NEIGHBOUR, Inter.LINEAR: cde.InterpolationMode.DE_INTER_LINEAR, @@ -170,6 +171,95 @@ class Normalize(cde.NormalizeOp): super().__init__(*mean, *std) +class RandomAffine(cde.RandomAffineOp): + """ + Apply Random affine transformation to the input PIL image. + + Args: + degrees (int or float or sequence): Range of the rotation degrees. + If degrees is a number, the range will be (-degrees, degrees). + If degrees is a sequence, it should be (min, max). + translate (sequence, optional): Sequence (tx, ty) of maximum translation in + x(horizontal) and y(vertical) directions (default=None). + The horizontal and vertical shift is selected randomly from the range: + (-tx*width, tx*width) and (-ty*height, ty*height), respectively. + If None, no translations gets applied. + scale (sequence, optional): Scaling factor interval (default=None, original scale is used). + shear (int or float or sequence, optional): Range of shear factor (default=None). + If a number 'shear', then a shear parallel to the x axis in the range of (-shear, +shear) is applied. + If a tuple or list of size 2, then a shear parallel to the x axis in the range of (shear[0], shear[1]) + is applied. + If a tuple of list of size 4, then a shear parallel to x axis in the range of (shear[0], shear[1]) + and a shear parallel to y axis in the range of (shear[2], shear[3]) is applied. + If None, no shear is applied. + resample (Inter mode, optional): An optional resampling filter (default=Inter.NEAREST). + If omitted, or if the image has mode "1" or "P", it is set to be Inter.NEAREST. + It can be any of [Inter.BILINEAR, Inter.NEAREST, Inter.BICUBIC]. + + - Inter.BILINEAR, means resample method is bilinear interpolation. + + - Inter.NEAREST, means resample method is nearest-neighbor interpolation. + + - Inter.BICUBIC, means resample method is bicubic interpolation. + + fill_value (tuple or int, optional): Optional fill_value to fill the area outside the transform + in the output image. Used only in Pillow versions > 5.0.0 (default=0, filling is performed). + + Raises: + ValueError: If degrees is negative. + ValueError: If translation value is not between 0 and 1. + ValueError: If scale is not positive. + ValueError: If shear is a number but is not positive. + TypeError: If degrees is not a number or a list or a tuple. + If degrees is a list or tuple, its length is not 2. + TypeError: If translate is specified but is not list or a tuple of length 2. + TypeError: If scale is not a list or tuple of length 2.'' + TypeError: If shear is not a list or tuple of length 2 or 4. + + Examples: + >>> c_transform.RandomAffine(degrees=15, translate=(0.1, 0.1), scale=(0.9, 1.1)) + """ + + @check_random_affine + def __init__(self, degrees, translate=None, scale=None, shear=None, resample=Inter.NEAREST, fill_value=0): + # Parameter checking + if shear is not None: + if isinstance(shear, numbers.Number): + shear = (-1 * shear, shear, 0., 0.) + else: + if len(shear) == 2: + shear = [shear[0], shear[1], 0., 0.] + elif len(shear) == 4: + shear = [s for s in shear] + + if isinstance(degrees, numbers.Number): + degrees = (-1 * degrees, degrees) + + if isinstance(fill_value, numbers.Number): + fill_value = (fill_value, fill_value, fill_value) + + # translation + if translate is None: + translate = (0.0, 0.0) + + # scale + if scale is None: + scale = (1.0, 1.0) + + # shear + if shear is None: + shear = (0.0, 0.0, 0.0, 0.0) + + self.degrees = degrees + self.translate = translate + self.scale_ = scale + self.shear = shear + self.resample = DE_C_INTER_MODE[resample] + self.fill_value = fill_value + + super().__init__(degrees, translate, scale, shear, DE_C_INTER_MODE[resample], fill_value) + + class RandomCrop(cde.RandomCropOp): """ Crop the input image at a random location. diff --git a/tests/ut/cpp/dataset/CMakeLists.txt b/tests/ut/cpp/dataset/CMakeLists.txt index 790f9d0596..f33a6ca49d 100644 --- a/tests/ut/cpp/dataset/CMakeLists.txt +++ b/tests/ut/cpp/dataset/CMakeLists.txt @@ -4,6 +4,7 @@ SET(DE_UT_SRCS common/common.cc common/cvop_common.cc common/bboxop_common.cc + auto_contrast_op_test.cc batch_op_test.cc bit_functions_test.cc storage_container_test.cc @@ -22,6 +23,7 @@ SET(DE_UT_SRCS cut_out_op_test.cc datatype_test.cc decode_op_test.cc + equalize_op_test.cc execution_tree_test.cc global_context_test.cc main_test.cc @@ -36,6 +38,7 @@ SET(DE_UT_SRCS path_test.cc project_op_test.cc queue_test.cc + random_affine_op_test.cc random_crop_op_test.cc random_crop_with_bbox_op_test.cc random_crop_decode_resize_op_test.cc diff --git a/tests/ut/cpp/dataset/auto_contrast_op_test.cc b/tests/ut/cpp/dataset/auto_contrast_op_test.cc new file mode 100644 index 0000000000..b62d5a99c0 --- /dev/null +++ b/tests/ut/cpp/dataset/auto_contrast_op_test.cc @@ -0,0 +1,41 @@ +/** + * 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 "common/common.h" +#include "common/cvop_common.h" +#include "minddata/dataset/kernels/image/auto_contrast_op.h" +#include "minddata/dataset/core/cv_tensor.h" +#include "utils/log_adapter.h" + +using namespace mindspore::dataset; +using mindspore::LogStream; +using mindspore::ExceptionType::NoExceptionType; +using mindspore::MsLogLevel::INFO; + +class MindDataTestAutoContrastOp : public UT::CVOP::CVOpCommon { + public: + MindDataTestAutoContrastOp() : CVOpCommon() {} +}; + +TEST_F(MindDataTestAutoContrastOp, TestOp1) { + MS_LOG(INFO) << "Doing testAutoContrastOp."; + + std::shared_ptr output_tensor; + std::unique_ptr op(new AutoContrastOp(1.0, {0, 255})); + EXPECT_TRUE(op->OneToOne()); + Status s = op->Compute(input_tensor_, &output_tensor); + EXPECT_TRUE(s.IsOk()); + CheckImageShapeAndData(output_tensor, kAutoContrast); +} diff --git a/tests/ut/cpp/dataset/c_api_transforms_test.cc b/tests/ut/cpp/dataset/c_api_transforms_test.cc index 75e8031707..c98b56c4cc 100644 --- a/tests/ut/cpp/dataset/c_api_transforms_test.cc +++ b/tests/ut/cpp/dataset/c_api_transforms_test.cc @@ -521,6 +521,119 @@ TEST_F(MindDataTestPipeline, TestRandomColorAdjust) { iter->Stop(); } +TEST_F(MindDataTestPipeline, TestRandomAffineSuccess1) { + MS_LOG(INFO) << "Doing MindDataTestPipeline-TestRandomAffineSuccess1 with non-default params."; + + // Create an ImageFolder Dataset + std::string folder_path = datasets_root_path_ + "/testPK/data/"; + std::shared_ptr ds = ImageFolder(folder_path, true, RandomSampler(false, 10)); + EXPECT_NE(ds, nullptr); + + // Create a Repeat operation on ds + int32_t repeat_num = 2; + ds = ds->Repeat(repeat_num); + EXPECT_NE(ds, nullptr); + + // Create objects for the tensor ops + std::shared_ptr affine = + vision::RandomAffine({30.0, 30.0}, {0.0, 0.0}, {2.0, 2.0}, {10.0, 10.0, 20.0, 20.0}); + EXPECT_NE(affine, nullptr); + + // Create a Map operation on ds + ds = ds->Map({affine}); + EXPECT_NE(ds, nullptr); + + // Create a Batch operation on ds + int32_t batch_size = 1; + ds = ds->Batch(batch_size); + EXPECT_NE(ds, nullptr); + + // Create an iterator over the result of the above dataset + // This will trigger the creation of the Execution Tree and launch it. + std::shared_ptr iter = ds->CreateIterator(); + EXPECT_NE(iter, nullptr); + + // Iterate the dataset and get each row + std::unordered_map> row; + iter->GetNextRow(&row); + + uint64_t i = 0; + while (row.size() != 0) { + i++; + auto image = row["image"]; + MS_LOG(INFO) << "Tensor image shape: " << image->shape(); + iter->GetNextRow(&row); + } + + EXPECT_EQ(i, 20); + + // Manually terminate the pipeline + iter->Stop(); +} + +TEST_F(MindDataTestPipeline, TestRandomAffineSuccess2) { + MS_LOG(INFO) << "Doing MindDataTestPipeline-TestRandomAffineSuccess2 with default params."; + + // Create an ImageFolder Dataset + std::string folder_path = datasets_root_path_ + "/testPK/data/"; + std::shared_ptr ds = ImageFolder(folder_path, true, RandomSampler(false, 10)); + EXPECT_NE(ds, nullptr); + + // Create a Repeat operation on ds + int32_t repeat_num = 2; + ds = ds->Repeat(repeat_num); + EXPECT_NE(ds, nullptr); + + // Create objects for the tensor ops + std::shared_ptr affine = vision::RandomAffine({0.0, 0.0}); + EXPECT_NE(affine, nullptr); + + // Create a Map operation on ds + ds = ds->Map({affine}); + EXPECT_NE(ds, nullptr); + + // Create a Batch operation on ds + int32_t batch_size = 1; + ds = ds->Batch(batch_size); + EXPECT_NE(ds, nullptr); + + // Create an iterator over the result of the above dataset + // This will trigger the creation of the Execution Tree and launch it. + std::shared_ptr iter = ds->CreateIterator(); + EXPECT_NE(iter, nullptr); + + // Iterate the dataset and get each row + std::unordered_map> row; + iter->GetNextRow(&row); + + uint64_t i = 0; + while (row.size() != 0) { + i++; + auto image = row["image"]; + MS_LOG(INFO) << "Tensor image shape: " << image->shape(); + iter->GetNextRow(&row); + } + + EXPECT_EQ(i, 20); + + // Manually terminate the pipeline + iter->Stop(); +} + +TEST_F(MindDataTestPipeline, TestRandomAffineFail) { + MS_LOG(INFO) << "Doing MindDataTestPipeline-TestRandomAffineFail with invalid params."; + + // Create objects for the tensor ops + std::shared_ptr affine = vision::RandomAffine({0.0, 0.0}, {}); + EXPECT_EQ(affine, nullptr); + // Invalid number of values for translate + affine = vision::RandomAffine({0.0, 0.0}, {1, 1, 1, 1}); + EXPECT_EQ(affine, nullptr); + // Invalid number of values for shear + affine = vision::RandomAffine({30.0, 30.0}, {0.0, 0.0}, {2.0, 2.0}, {10.0, 10.0}); + EXPECT_EQ(affine, nullptr); +} + TEST_F(MindDataTestPipeline, TestRandomRotation) { // Create an ImageFolder Dataset std::string folder_path = datasets_root_path_ + "/testPK/data/"; diff --git a/tests/ut/cpp/dataset/common/cvop_common.cc b/tests/ut/cpp/dataset/common/cvop_common.cc index 28d0c07764..6f9cfde820 100644 --- a/tests/ut/cpp/dataset/common/cvop_common.cc +++ b/tests/ut/cpp/dataset/common/cvop_common.cc @@ -130,6 +130,18 @@ void CVOpCommon::CheckImageShapeAndData(const std::shared_ptr &output_te expect_image_path = dir_path + "imagefolder/apple_expect_changemode.jpg"; actual_image_path = dir_path + "imagefolder/apple_actual_changemode.jpg"; break; + case kRandomAffine: + expect_image_path = dir_path + "imagefolder/apple_expect_randomaffine.jpg"; + actual_image_path = dir_path + "imagefolder/apple_actual_randomaffine.jpg"; + break; + case kAutoContrast: + expect_image_path = dir_path + "imagefolder/apple_expect_autocontrast.jpg"; + actual_image_path = dir_path + "imagefolder/apple_actual_autocontrast.jpg"; + break; + case kEqualize: + expect_image_path = dir_path + "imagefolder/apple_expect_equalize.jpg"; + actual_image_path = dir_path + "imagefolder/apple_actual_equalize.jpg"; + break; default: MS_LOG(INFO) << "Not pass verification! Operation type does not exists."; EXPECT_EQ(0, 1); diff --git a/tests/ut/cpp/dataset/common/cvop_common.h b/tests/ut/cpp/dataset/common/cvop_common.h index 59134091fd..000077d431 100644 --- a/tests/ut/cpp/dataset/common/cvop_common.h +++ b/tests/ut/cpp/dataset/common/cvop_common.h @@ -37,7 +37,10 @@ class CVOpCommon : public Common { kChannelSwap, kChangeMode, kTemplate, - kCrop + kCrop, + kRandomAffine, + kAutoContrast, + kEqualize }; CVOpCommon(); diff --git a/tests/ut/cpp/dataset/equalize_op_test.cc b/tests/ut/cpp/dataset/equalize_op_test.cc new file mode 100644 index 0000000000..1c5cfb03ae --- /dev/null +++ b/tests/ut/cpp/dataset/equalize_op_test.cc @@ -0,0 +1,41 @@ +/** + * 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 "common/common.h" +#include "common/cvop_common.h" +#include "minddata/dataset/kernels/image/equalize_op.h" +#include "minddata/dataset/core/cv_tensor.h" +#include "utils/log_adapter.h" + +using namespace mindspore::dataset; +using mindspore::LogStream; +using mindspore::ExceptionType::NoExceptionType; +using mindspore::MsLogLevel::INFO; + +class MindDataTestEqualizeOp : public UT::CVOP::CVOpCommon { + public: + MindDataTestEqualizeOp() : CVOpCommon() {} +}; + +TEST_F(MindDataTestEqualizeOp, TestOp1) { + MS_LOG(INFO) << "Doing testEqualizeOp."; + + std::shared_ptr output_tensor; + std::unique_ptr op(new EqualizeOp()); + EXPECT_TRUE(op->OneToOne()); + Status s = op->Compute(input_tensor_, &output_tensor); + EXPECT_TRUE(s.IsOk()); + CheckImageShapeAndData(output_tensor, kEqualize); +} diff --git a/tests/ut/cpp/dataset/random_affine_op_test.cc b/tests/ut/cpp/dataset/random_affine_op_test.cc new file mode 100644 index 0000000000..8db93c589a --- /dev/null +++ b/tests/ut/cpp/dataset/random_affine_op_test.cc @@ -0,0 +1,42 @@ +/** + * 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 "common/common.h" +#include "common/cvop_common.h" +#include "minddata/dataset/kernels/image/random_affine_op.h" +#include "minddata/dataset/core/cv_tensor.h" +#include "utils/log_adapter.h" + +using namespace mindspore::dataset; +using mindspore::LogStream; +using mindspore::ExceptionType::NoExceptionType; +using mindspore::MsLogLevel::INFO; + +class MindDataTestRandomAffineOp : public UT::CVOP::CVOpCommon { + public: + MindDataTestRandomAffineOp() : CVOpCommon() {} +}; + +TEST_F(MindDataTestRandomAffineOp, TestOp1) { + MS_LOG(INFO) << "Doing testRandomAffineOp."; + + std::shared_ptr output_tensor; + std::unique_ptr op(new RandomAffineOp({30.0, 30.0}, {0.0, 0.0}, {2.0, 2.0}, {10.0, 10.0, 20.0, 20.0}, + InterpolationMode::kNearestNeighbour, {255, 0, 0})); + EXPECT_TRUE(op->OneToOne()); + Status s = op->Compute(input_tensor_, &output_tensor); + EXPECT_TRUE(s.IsOk()); + CheckImageShapeAndData(output_tensor, kRandomAffine); +} diff --git a/tests/ut/data/dataset/golden/autcontrast_01_result_c.npz b/tests/ut/data/dataset/golden/autocontrast_01_result_c.npz similarity index 100% rename from tests/ut/data/dataset/golden/autcontrast_01_result_c.npz rename to tests/ut/data/dataset/golden/autocontrast_01_result_c.npz diff --git a/tests/ut/data/dataset/golden/autcontrast_01_result_py.npz b/tests/ut/data/dataset/golden/autocontrast_01_result_py.npz similarity index 100% rename from tests/ut/data/dataset/golden/autcontrast_01_result_py.npz rename to tests/ut/data/dataset/golden/autocontrast_01_result_py.npz diff --git a/tests/ut/data/dataset/golden/autocontrast_mnist_result_c.npz b/tests/ut/data/dataset/golden/autocontrast_mnist_result_c.npz new file mode 100644 index 0000000000..683ed673a0 Binary files /dev/null and b/tests/ut/data/dataset/golden/autocontrast_mnist_result_c.npz differ diff --git a/tests/ut/data/dataset/golden/equalize_mnist_result_c.npz b/tests/ut/data/dataset/golden/equalize_mnist_result_c.npz new file mode 100644 index 0000000000..683ed673a0 Binary files /dev/null and b/tests/ut/data/dataset/golden/equalize_mnist_result_c.npz differ diff --git a/tests/ut/data/dataset/golden/random_affine_01_c_result.npz b/tests/ut/data/dataset/golden/random_affine_01_c_result.npz new file mode 100644 index 0000000000..277acd9540 Binary files /dev/null and b/tests/ut/data/dataset/golden/random_affine_01_c_result.npz differ diff --git a/tests/ut/data/dataset/imagefolder/apple_expect_autocontrast.jpg b/tests/ut/data/dataset/imagefolder/apple_expect_autocontrast.jpg new file mode 100644 index 0000000000..a624de836c Binary files /dev/null and b/tests/ut/data/dataset/imagefolder/apple_expect_autocontrast.jpg differ diff --git a/tests/ut/data/dataset/imagefolder/apple_expect_equalize.jpg b/tests/ut/data/dataset/imagefolder/apple_expect_equalize.jpg new file mode 100644 index 0000000000..4cbb5d5e85 Binary files /dev/null and b/tests/ut/data/dataset/imagefolder/apple_expect_equalize.jpg differ diff --git a/tests/ut/data/dataset/imagefolder/apple_expect_randomaffine.jpg b/tests/ut/data/dataset/imagefolder/apple_expect_randomaffine.jpg new file mode 100644 index 0000000000..f383e06bb8 Binary files /dev/null and b/tests/ut/data/dataset/imagefolder/apple_expect_randomaffine.jpg differ diff --git a/tests/ut/python/dataset/test_autocontrast.py b/tests/ut/python/dataset/test_autocontrast.py index 6c3fc671d7..c657c74296 100644 --- a/tests/ut/python/dataset/test_autocontrast.py +++ b/tests/ut/python/dataset/test_autocontrast.py @@ -20,9 +20,10 @@ import mindspore.dataset.engine as de import mindspore.dataset.transforms.vision.py_transforms as F import mindspore.dataset.transforms.vision.c_transforms as C from mindspore import log as logger -from util import visualize_list, diff_mse, save_and_check_md5 +from util import visualize_list, visualize_one_channel_dataset, diff_mse, save_and_check_md5 DATA_DIR = "../data/dataset/testImageNetData/train/" +MNIST_DATA_DIR = "../data/dataset/testMnistData" GENERATE_GOLDEN = False @@ -81,7 +82,7 @@ def test_auto_contrast_py(plot=False): logger.info("MSE= {}".format(str(np.mean(mse)))) # Compare with expected md5 from images - filename = "autcontrast_01_result_py.npz" + filename = "autocontrast_01_result_py.npz" save_and_check_md5(ds_auto_contrast, filename, generate_golden=GENERATE_GOLDEN) if plot: @@ -144,7 +145,7 @@ def test_auto_contrast_c(plot=False): np.testing.assert_equal(np.mean(mse), 0.0) # Compare with expected md5 from images - filename = "autcontrast_01_result_c.npz" + filename = "autocontrast_01_result_c.npz" save_and_check_md5(ds_auto_contrast_c, filename, generate_golden=GENERATE_GOLDEN) if plot: @@ -213,6 +214,34 @@ def test_auto_contrast_one_channel_c(plot=False): visualize_list(images_auto_contrast_c, images_auto_contrast_py, visualize_mode=2) +def test_auto_contrast_mnist_c(plot=False): + """ + Test AutoContrast C op with MNIST dataset (Grayscale images) + """ + logger.info("Test AutoContrast C Op With MNIST Images") + ds = de.MnistDataset(dataset_dir=MNIST_DATA_DIR, num_samples=2, shuffle=False) + ds_auto_contrast_c = ds.map(input_columns="image", + operations=C.AutoContrast(cutoff=1, ignore=(0, 255))) + ds_orig = de.MnistDataset(dataset_dir=MNIST_DATA_DIR, num_samples=2, shuffle=False) + + images = [] + images_trans = [] + labels = [] + for _, (data_orig, data_trans) in enumerate(zip(ds_orig, ds_auto_contrast_c)): + image_orig, label_orig = data_orig + image_trans, _ = data_trans + images.append(image_orig) + labels.append(label_orig) + images_trans.append(image_trans) + + # Compare with expected md5 from images + filename = "autocontrast_mnist_result_c.npz" + save_and_check_md5(ds_auto_contrast_c, filename, generate_golden=GENERATE_GOLDEN) + + if plot: + visualize_one_channel_dataset(images, images_trans, labels) + + def test_auto_contrast_invalid_ignore_param_c(): """ Test AutoContrast C Op with invalid ignore parameter @@ -333,6 +362,7 @@ if __name__ == "__main__": test_auto_contrast_py(plot=True) test_auto_contrast_c(plot=True) test_auto_contrast_one_channel_c(plot=True) + test_auto_contrast_mnist_c(plot=True) test_auto_contrast_invalid_ignore_param_c() test_auto_contrast_invalid_ignore_param_py() test_auto_contrast_invalid_cutoff_param_c() diff --git a/tests/ut/python/dataset/test_equalize.py b/tests/ut/python/dataset/test_equalize.py index 26102ae809..bdc8375b67 100644 --- a/tests/ut/python/dataset/test_equalize.py +++ b/tests/ut/python/dataset/test_equalize.py @@ -21,12 +21,14 @@ import mindspore.dataset.engine as de import mindspore.dataset.transforms.vision.c_transforms as C import mindspore.dataset.transforms.vision.py_transforms as F from mindspore import log as logger -from util import visualize_list, diff_mse, save_and_check_md5 +from util import visualize_list, visualize_one_channel_dataset, diff_mse, save_and_check_md5 DATA_DIR = "../data/dataset/testImageNetData/train/" +MNIST_DATA_DIR = "../data/dataset/testMnistData" GENERATE_GOLDEN = False + def test_equalize_py(plot=False): """ Test Equalize py op @@ -216,6 +218,34 @@ def test_equalize_one_channel(): assert "The shape" in str(e) +def test_equalize_mnist_c(plot=False): + """ + Test Equalize C op with MNIST dataset (Grayscale images) + """ + logger.info("Test Equalize C Op With MNIST Images") + ds = de.MnistDataset(dataset_dir=MNIST_DATA_DIR, num_samples=2, shuffle=False) + ds_equalize_c = ds.map(input_columns="image", + operations=C.Equalize()) + ds_orig = de.MnistDataset(dataset_dir=MNIST_DATA_DIR, num_samples=2, shuffle=False) + + images = [] + images_trans = [] + labels = [] + for _, (data_orig, data_trans) in enumerate(zip(ds_orig, ds_equalize_c)): + image_orig, label_orig = data_orig + image_trans, _ = data_trans + images.append(image_orig) + labels.append(label_orig) + images_trans.append(image_trans) + + # Compare with expected md5 from images + filename = "equalize_mnist_result_c.npz" + save_and_check_md5(ds_equalize_c, filename, generate_golden=GENERATE_GOLDEN) + + if plot: + visualize_one_channel_dataset(images, images_trans, labels) + + def test_equalize_md5_py(): """ Test Equalize py op with md5 check @@ -258,7 +288,7 @@ if __name__ == "__main__": test_equalize_py(plot=False) test_equalize_c(plot=False) test_equalize_py_c(plot=False) + test_equalize_mnist_c(plot=True) test_equalize_one_channel() test_equalize_md5_py() test_equalize_md5_c() - \ No newline at end of file diff --git a/tests/ut/python/dataset/test_random_affine.py b/tests/ut/python/dataset/test_random_affine.py index ec829eb53a..68267030a7 100644 --- a/tests/ut/python/dataset/test_random_affine.py +++ b/tests/ut/python/dataset/test_random_affine.py @@ -18,6 +18,7 @@ Testing RandomAffine op in DE import numpy as np import mindspore.dataset as ds import mindspore.dataset.transforms.vision.py_transforms as py_vision +import mindspore.dataset.transforms.vision.c_transforms as c_vision from mindspore import log as logger from util import visualize_list, save_and_check_md5, \ config_get_set_seed, config_get_set_num_parallel_workers @@ -65,6 +66,39 @@ def test_random_affine_op(plot=False): visualize_list(image_original, image_affine) +def test_random_affine_op_c(plot=False): + """ + Test RandomAffine in C transformations + """ + logger.info("test_random_affine_op_c") + # define map operations + transforms1 = [ + c_vision.Decode(), + c_vision.RandomAffine(degrees=15, translate=(0.1, 0.1), scale=(0.9, 1.1)) + ] + + transforms2 = [ + c_vision.Decode() + ] + + # First dataset + data1 = ds.TFRecordDataset(DATA_DIR, SCHEMA_DIR, columns_list=["image"], shuffle=False) + data1 = data1.map(input_columns=["image"], operations=transforms1) + # Second dataset + data2 = ds.TFRecordDataset(DATA_DIR, SCHEMA_DIR, columns_list=["image"], shuffle=False) + data2 = data2.map(input_columns=["image"], operations=transforms2) + + image_affine = [] + image_original = [] + for item1, item2 in zip(data1.create_dict_iterator(), data2.create_dict_iterator()): + image1 = item1["image"] + image2 = item2["image"] + image_affine.append(image1) + image_original.append(image2) + if plot: + visualize_list(image_original, image_affine) + + def test_random_affine_md5(): """ Test RandomAffine with md5 comparison @@ -94,6 +128,33 @@ def test_random_affine_md5(): ds.config.set_num_parallel_workers((original_num_parallel_workers)) +def test_random_affine_c_md5(): + """ + Test RandomAffine C Op with md5 comparison + """ + logger.info("test_random_affine_c_md5") + original_seed = config_get_set_seed(1) + original_num_parallel_workers = config_get_set_num_parallel_workers(1) + # define map operations + transforms = [ + c_vision.Decode(), + c_vision.RandomAffine(degrees=(-5, 15), translate=(0.1, 0.3), + scale=(0.9, 1.1), shear=(-10, 10, -5, 5)) + ] + + # Generate dataset + data = ds.TFRecordDataset(DATA_DIR, SCHEMA_DIR, columns_list=["image"], shuffle=False) + data = data.map(input_columns=["image"], operations=transforms) + + # check results with md5 comparison + filename = "random_affine_01_c_result.npz" + save_and_check_md5(data, filename, generate_golden=GENERATE_GOLDEN) + + # Restore configuration + ds.config.set_seed(original_seed) + ds.config.set_num_parallel_workers((original_num_parallel_workers)) + + def test_random_affine_exception_negative_degrees(): """ Test RandomAffine: input degrees in negative, expected to raise ValueError @@ -199,7 +260,9 @@ def test_random_affine_exception_shear_size(): if __name__ == "__main__": test_random_affine_op(plot=True) + test_random_affine_op_c(plot=True) test_random_affine_md5() + test_random_affine_c_md5() test_random_affine_exception_negative_degrees() test_random_affine_exception_translation_range() test_random_affine_exception_scale_value() diff --git a/tests/ut/python/dataset/util.py b/tests/ut/python/dataset/util.py index 65ea55824c..74009dbd05 100644 --- a/tests/ut/python/dataset/util.py +++ b/tests/ut/python/dataset/util.py @@ -200,6 +200,23 @@ def diff_me(in1, in2): return mse / 255 * 100 +def visualize_one_channel_dataset(images_original, images_transformed, labels): + """ + Helper function to visualize one channel grayscale images + """ + num_samples = len(images_original) + for i in range(num_samples): + plt.subplot(2, num_samples, i + 1) + # Note: Use squeeze() to convert (H, W, 1) images to (H, W) + plt.imshow(images_original[i].squeeze(), cmap=plt.cm.gray) + plt.title(PLOT_TITLE_DICT[1][0] + ":" + str(labels[i])) + + plt.subplot(2, num_samples, i + num_samples + 1) + plt.imshow(images_transformed[i].squeeze(), cmap=plt.cm.gray) + plt.title(PLOT_TITLE_DICT[1][1] + ":" + str(labels[i])) + plt.show() + + def visualize_list(image_list_1, image_list_2, visualize_mode=1): """ visualizes a list of images using DE op