COMPMID-3539: Ignore align_corners for scaled size of 1

Scale kernels failed to validate when align_corners is true
for scaled output size 1. Change this behavior to ignoring
align_corners value to be aligned with expected behavior of
higher-level frameworks.

Also the minimum output size generated by the fixture for
Scale kernels is changed to 1.

Change-Id: Ib8e479af8bc43de3780005545f0c53fe195dc22e
Signed-off-by: Sang-Hoon Park <sang-hoon.park@arm.com>
Reviewed-on: https://review.mlplatform.org/c/ml/ComputeLibrary/+/3478
Tested-by: Arm Jenkins <bsgcomp@arm.com>
Reviewed-by: Georgios Pinitas <georgios.pinitas@arm.com>
Comments-Addressed: Arm Jenkins <bsgcomp@arm.com>
diff --git a/src/core/CL/kernels/CLScaleKernel.cpp b/src/core/CL/kernels/CLScaleKernel.cpp
index 872ba5b..f3acc3b 100644
--- a/src/core/CL/kernels/CLScaleKernel.cpp
+++ b/src/core/CL/kernels/CLScaleKernel.cpp
@@ -35,6 +35,8 @@
 #include "arm_compute/core/TensorInfo.h"
 #include "support/StringSupport.h"
 
+#include "src/core/utils/ScaleUtils.h"
+
 #include <set>
 #include <string>
 
@@ -54,8 +56,8 @@
     const unsigned int output_width  = output.dimension(idx_width);
     const unsigned int output_height = output.dimension(idx_height);
 
-    float wr = arm_compute::calculate_resize_ratio(input_width, output_width, align_corners);
-    float hr = arm_compute::calculate_resize_ratio(input_height, output_height, align_corners);
+    float wr = arm_compute::scale_utils::calculate_resize_ratio(input_width, output_width, align_corners);
+    float hr = arm_compute::scale_utils::calculate_resize_ratio(input_height, output_height, align_corners);
 
     return std::make_pair(wr, hr);
 }
@@ -68,29 +70,13 @@
     ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_DATA_TYPES(input, output);
     ARM_COMPUTE_RETURN_ERROR_ON_MISMATCHING_QUANTIZATION_INFO(input, output);
     ARM_COMPUTE_RETURN_ERROR_ON(output == input);
-    ARM_COMPUTE_RETURN_ERROR_ON(info.align_corners && !is_align_corners_allowed(info.sampling_policy));
+    ARM_COMPUTE_RETURN_ERROR_ON(info.align_corners && !arm_compute::scale_utils::is_align_corners_allowed_sampling_policy(info.sampling_policy));
 
-    if(info.align_corners)
-    {
-        // For bilinear method with aligned corners, the resize ratio will
-        // be calculated by (input_size - 1)/(output_size - 1). Belows are
-        // checking possible overflows.
-        const auto data_layout  = input->data_layout();
-        const auto width_index  = get_data_layout_dimension_index(data_layout, DataLayoutDimension::WIDTH);
-        const auto height_index = get_data_layout_dimension_index(data_layout, DataLayoutDimension::HEIGHT);
-
-        const auto input_width   = input->dimension(width_index);
-        const auto input_height  = input->dimension(height_index);
-        const auto output_width  = output->dimension(width_index);
-        const auto output_height = output->dimension(height_index);
-
-        ARM_COMPUTE_RETURN_ERROR_ON(input_width == 0 || input_height == 0 || output_width == 0 || output_height == 0);
-        ARM_COMPUTE_RETURN_ERROR_ON((output_width - 1 == 0) || (output_height - 1 == 0));
-    }
+    const bool will_use_align_corners = info.align_corners && arm_compute::scale_utils::is_align_corners_allowed_output_shape(output->tensor_shape(), output->data_layout());
 
     float wr = 0.f;
     float hr = 0.f;
-    std::tie(wr, hr) = calculate_scale_factors(*input, *output, info.align_corners);
+    std::tie(wr, hr) = calculate_scale_factors(*input, *output, will_use_align_corners);
 
     ARM_COMPUTE_RETURN_ERROR_ON(info.interpolation_policy == InterpolationPolicy::AREA && (wr > 1.f || hr > 1.f));
 
@@ -191,7 +177,7 @@
     _output               = output;
     _interpolation_policy = info.interpolation_policy;
     _data_layout          = input->info()->data_layout();
-    _align_corners        = info.align_corners;
+    _align_corners        = info.align_corners && arm_compute::scale_utils::is_align_corners_allowed_output_shape(output->info()->tensor_shape(), _data_layout);
 
     float wr = 0.f;
     float hr = 0.f;
diff --git a/src/core/NEON/kernels/NEScaleKernel.cpp b/src/core/NEON/kernels/NEScaleKernel.cpp
index 857084e..0f329a1 100644
--- a/src/core/NEON/kernels/NEScaleKernel.cpp
+++ b/src/core/NEON/kernels/NEScaleKernel.cpp
@@ -30,6 +30,8 @@
 #include "arm_compute/core/Window.h"
 #include "arm_compute/core/utils/misc/Utility.h"
 
+#include "src/core/utils/ScaleUtils.h"
+
 #include <arm_neon.h>
 
 namespace arm_compute
@@ -68,19 +70,7 @@
         ARM_COMPUTE_RETURN_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(dy, 1, DataType::F32);
     }
 
-    ARM_COMPUTE_RETURN_ERROR_ON(info.align_corners && !is_align_corners_allowed(info.sampling_policy));
-
-    if(info.align_corners && is_align_corners_allowed(info.sampling_policy))
-    {
-        // For bilinear method with aligned corners, the resize ratio will
-        // be calculated by (input_size - 1)/(output_size - 1). Belows are
-        // checking possible overflows.
-        const auto input_width  = input->dimension(width_index);
-        const auto input_height = input->dimension(height_index);
-
-        ARM_COMPUTE_RETURN_ERROR_ON(input_width == 0 || input_height == 0);
-        ARM_COMPUTE_RETURN_ERROR_ON((output_width - 1 == 0) || (output_height - 1 == 0));
-    }
+    ARM_COMPUTE_RETURN_ERROR_ON(info.align_corners && !arm_compute::scale_utils::is_align_corners_allowed_sampling_policy(info.sampling_policy));
 
     if(info.interpolation_policy == InterpolationPolicy::AREA)
     {
@@ -378,7 +368,7 @@
     _border_mode           = info.border_mode;
     _constant_border_value = info.constant_border_value;
     _use_padding           = info.use_padding;
-    _align_corners         = info.align_corners;
+    _align_corners         = info.align_corners && arm_compute::scale_utils::is_align_corners_allowed_output_shape(output->info()->tensor_shape(), data_layout);
 
     if(info.sampling_policy == SamplingPolicy::CENTER)
     {
@@ -386,8 +376,8 @@
     }
 
     // Compute the ratio between source width/height and destination width/height
-    const auto wr = arm_compute::calculate_resize_ratio(input->info()->dimension(idx_width), output->info()->dimension(idx_width), _align_corners);
-    const auto hr = arm_compute::calculate_resize_ratio(input->info()->dimension(idx_height), output->info()->dimension(idx_height), _align_corners);
+    const auto wr = arm_compute::scale_utils::calculate_resize_ratio(input->info()->dimension(idx_width), output->info()->dimension(idx_width), _align_corners);
+    const auto hr = arm_compute::scale_utils::calculate_resize_ratio(input->info()->dimension(idx_height), output->info()->dimension(idx_height), _align_corners);
 
     // Add constant border only on top in case of NHWC layout
     if(data_layout == DataLayout::NHWC)
@@ -437,7 +427,7 @@
     const size_t input_stride = _input->info()->strides_in_bytes()[1];
 
     // Compute the ratio between source height and destination height
-    const auto hr = arm_compute::calculate_resize_ratio(_input->info()->dimension(1), _output->info()->dimension(1), _align_corners);
+    const auto hr = arm_compute::scale_utils::calculate_resize_ratio(_input->info()->dimension(1), _output->info()->dimension(1), _align_corners);
 
     // Don't increment in X and Y direction for the input tensor
     // A pointer to the start of this plane is needed as base for the precomputed offsets
@@ -670,7 +660,7 @@
     ARM_COMPUTE_ERROR_ON_DATA_TYPE_CHANNEL_NOT_IN(_input, 1, DataType::U8, DataType::QASYMM8, DataType::QASYMM8_SIGNED, DataType::S16, DataType::F16, DataType::F32);
 
     // Compute the ratio between source height and destination height
-    const auto hr = arm_compute::calculate_resize_ratio(_input->info()->dimension(1), _output->info()->dimension(1), _align_corners);
+    const auto hr = arm_compute::scale_utils::calculate_resize_ratio(_input->info()->dimension(1), _output->info()->dimension(1), _align_corners);
 
     // Don't increment in X and Y direction for the input tensor
     // A pointer to the start of this plane is needed as base for the precomputed offsets
@@ -971,8 +961,8 @@
     Iterator in(_input, win_in);
     Iterator out(_output, window);
 
-    const auto   wr        = arm_compute::calculate_resize_ratio(_input->info()->dimension(0), _output->info()->dimension(0), _align_corners);
-    const auto   hr        = arm_compute::calculate_resize_ratio(_input->info()->dimension(1), _output->info()->dimension(1), _align_corners);
+    const auto   wr        = arm_compute::scale_utils::calculate_resize_ratio(_input->info()->dimension(0), _output->info()->dimension(0), _align_corners);
+    const auto   hr        = arm_compute::scale_utils::calculate_resize_ratio(_input->info()->dimension(1), _output->info()->dimension(1), _align_corners);
     const auto   w         = _input->info()->dimension(0);
     const auto   h         = _input->info()->dimension(1);
     const size_t in_stride = _input->info()->strides_in_bytes()[1];
@@ -1019,7 +1009,7 @@
     const size_t input_stride_c = _input->info()->strides_in_bytes()[idx_channels];
 
     // Compute the ratio between source height and destination height
-    const auto hr = arm_compute::calculate_resize_ratio(_input->info()->dimension(idx_height), _output->info()->dimension(idx_height), _align_corners);
+    const auto hr = arm_compute::scale_utils::calculate_resize_ratio(_input->info()->dimension(idx_height), _output->info()->dimension(idx_height), _align_corners);
 
     // Don't increment in width/height/channels for the input tensor
     // A pointer to the start of this plane is needed as base for the precomputed offsets
diff --git a/src/core/Utils.cpp b/src/core/Utils.cpp
index bdde082..4d7e964 100644
--- a/src/core/Utils.cpp
+++ b/src/core/Utils.cpp
@@ -456,18 +456,6 @@
     return QuantizationInfo(1.f / 256, 0);
 }
 
-float arm_compute::calculate_resize_ratio(size_t input_size, size_t output_size, bool align_corners)
-{
-    const size_t offset = align_corners ? 1 : 0;
-    const auto   in     = input_size - offset;
-    const auto   out    = output_size - offset;
-
-    ARM_COMPUTE_ERROR_ON((input_size == 0 || output_size == 0) && offset == 1);
-    ARM_COMPUTE_ERROR_ON(out == 0);
-
-    return static_cast<float>(in) / static_cast<float>(out);
-}
-
 std::pair<int32_t, int32_t> arm_compute::get_quantized_activation_min_max(ActivationLayerInfo act_info, DataType data_type, UniformQuantizationInfo oq_info)
 {
     const bool is_qasymm8_signed = is_data_type_quantized_asymmetric_signed(data_type);
diff --git a/src/core/utils/ScaleUtils.cpp b/src/core/utils/ScaleUtils.cpp
new file mode 100644
index 0000000..488b3df
--- /dev/null
+++ b/src/core/utils/ScaleUtils.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2020 ARM Limited.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "src/core/utils/ScaleUtils.h"
+#include "arm_compute/core/Helpers.h"
+
+float arm_compute::scale_utils::calculate_resize_ratio(size_t input_size, size_t output_size, bool align_corners)
+{
+    const size_t offset = align_corners ? 1 : 0;
+    const auto   in     = input_size - offset;
+    const auto   out    = output_size - offset;
+
+    ARM_COMPUTE_ERROR_ON((input_size == 0 || output_size == 0) && offset == 1);
+    ARM_COMPUTE_ERROR_ON(out == 0);
+
+    return static_cast<float>(in) / static_cast<float>(out);
+}
+
+bool arm_compute::scale_utils::is_align_corners_allowed_output_shape(const TensorShape &output_shape, DataLayout layout)
+{
+    const size_t idx_width  = get_data_layout_dimension_index(layout, DataLayoutDimension::WIDTH);
+    const size_t idx_height = get_data_layout_dimension_index(layout, DataLayoutDimension::HEIGHT);
+    return (output_shape[idx_width] > 1) && (output_shape[idx_height] > 1);
+}
\ No newline at end of file
diff --git a/src/core/utils/ScaleUtils.h b/src/core/utils/ScaleUtils.h
new file mode 100644
index 0000000..c3895d4
--- /dev/null
+++ b/src/core/utils/ScaleUtils.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2020 ARM Limited.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef UTILS_CORE_SCALEUTILS_H
+#define UTILS_CORE_SCALEUTILS_H
+
+#include "arm_compute/core/Types.h"
+
+#include <cstdint>
+#include <cstdlib>
+
+namespace arm_compute
+{
+namespace scale_utils
+{
+/** Returns resize ratio between input and output with consideration of aligned corners
+ *
+ * @param[in] input_size    The input size
+ * @param[in] output_size   the output size
+ * @param[in] align_corners True to align corners of input and output. Defaults to false.
+ *
+ * @return The ratio between input and output (i.e., the input size divided by the output size)
+ */
+float calculate_resize_ratio(size_t input_size, size_t output_size, bool align_corners = false);
+
+/** Returns if aligned corners are allowed for the given sampling policy
+ *
+ * @param[in] sampling_policy The sampling policy to consider
+ *
+ * @return True if aligned corners are allowed
+ */
+inline bool is_align_corners_allowed_sampling_policy(SamplingPolicy sampling_policy)
+{
+    return sampling_policy != SamplingPolicy::CENTER;
+}
+
+/** Returns if aligned corners are allowed for the given output shape
+ *
+ * @param[in] output_shape The shape of the scaled output tensor
+ * @param[in] layout       The data layout of the output tensor
+ *
+ * @return True if aligned corners are allowed
+ */
+bool is_align_corners_allowed_output_shape(const TensorShape &output_shape, DataLayout layout);
+} // namespace scale_utils
+} // namespace arm_compute
+#endif /* UTILS_CORE_SCALEUTILS_H */
\ No newline at end of file
diff --git a/src/runtime/NEON/functions/NEScale.cpp b/src/runtime/NEON/functions/NEScale.cpp
index 170b2ee..28fbab4 100644
--- a/src/runtime/NEON/functions/NEScale.cpp
+++ b/src/runtime/NEON/functions/NEScale.cpp
@@ -33,6 +33,8 @@
 #include "arm_compute/runtime/NEON/NEScheduler.h"
 #include "arm_compute/runtime/TensorAllocator.h"
 
+#include "src/core/utils/ScaleUtils.h"
+
 #include <cmath>
 #include <cstddef>
 #include <utility>
@@ -107,7 +109,8 @@
     ARM_COMPUTE_ERROR_THROW_ON(NEScale::validate(input->info(), output->info(), info));
 
     _use_padding                     = info.use_padding;
-    const bool is_align_corners_used = info.align_corners && is_align_corners_allowed(info.sampling_policy);
+    const bool is_align_corners_used = info.align_corners && arm_compute::scale_utils::is_align_corners_allowed_sampling_policy(info.sampling_policy)
+                                       && arm_compute::scale_utils::is_align_corners_allowed_output_shape(output->info()->tensor_shape(), output->info()->data_layout());
 
     // Get data layout and width/height indices
     const DataLayout data_layout = input->info()->data_layout();
@@ -118,8 +121,8 @@
     const TensorShape shape(output->info()->dimension(idx_width), output->info()->dimension(idx_height));
 
     // Compute the ratio between source width/height and destination width/height
-    const auto wr = arm_compute::calculate_resize_ratio(input->info()->dimension(idx_width), output->info()->dimension(idx_width), is_align_corners_used);
-    const auto hr = arm_compute::calculate_resize_ratio(input->info()->dimension(idx_height), output->info()->dimension(idx_height), is_align_corners_used);
+    const auto wr = arm_compute::scale_utils::calculate_resize_ratio(input->info()->dimension(idx_width), output->info()->dimension(idx_width), is_align_corners_used);
+    const auto hr = arm_compute::scale_utils::calculate_resize_ratio(input->info()->dimension(idx_height), output->info()->dimension(idx_height), is_align_corners_used);
 
     // Get the element size of the input image
     const size_t input_element_size = input->info()->element_size();