From 680641660e61bcfa48f84a380353a10b6a926612 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gr=C3=A9goire=20KUBLER?= <gregoire.kubler@proton.me>
Date: Thu, 24 Oct 2024 14:03:53 +0200
Subject: [PATCH] feat : new resize operator

---
 include/aidge/operator/Resize.hpp         | 196 ++++++++++++++--
 python_binding/operator/pybind_Resize.cpp |  53 ++++-
 src/operator/Resize.cpp                   | 263 +++++++++++++---------
 unit_tests/operator/Test_Resize_Op.cpp    | 173 ++++++++++++++
 4 files changed, 547 insertions(+), 138 deletions(-)
 create mode 100644 unit_tests/operator/Test_Resize_Op.cpp

diff --git a/include/aidge/operator/Resize.hpp b/include/aidge/operator/Resize.hpp
index a48b95aff..5e7100075 100644
--- a/include/aidge/operator/Resize.hpp
+++ b/include/aidge/operator/Resize.hpp
@@ -9,60 +9,214 @@
  *
  ********************************************************************************/
 
-#ifndef AIDGE_CORE_OPERATOR_Resize_H_
-#define AIDGE_CORE_OPERATOR_Resize_H_
+#ifndef AIDGE_CORE_OPERATOR_RESIZE_H_
+#define AIDGE_CORE_OPERATOR_RESIZE_H_
 
 #include <memory>
 #include <string>
 #include <vector>
 
 #include "aidge/backend/OperatorImpl.hpp"
+#include "aidge/data/Interpolation.hpp"
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/OperatorTensor.hpp"
 #include "aidge/utils/Registrar.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
 
-class Resize_Op : public OperatorTensor,
-                  public Registrable<Resize_Op, std::string, std::function<std::shared_ptr<OperatorImpl>(const Resize_Op&)>>{
+/* @brief attributes for the aidge operator */
+enum class ResizeAttr {
+    //   antialias,
+    // axes,
+    CoordinateTransformationMode,
+    CubicCoeffA,
+    // excludeOutside,
+    //   extrapolation_value,
+    //   keep_aspect_ratio_policy,
+    InterpolationMode,
+};
 
-public:
+/**
+ * @brief Resize operator, will up/downscale a given tensor given the input.
+ * @verbatim
+ * Output size can be computed in 2 ways :
+ * 1. Image can be rescaled proportionally to the input size :
+ *    output_dimension = floor(input_dimension * (roi_end - roi_start) * scale)
+ * 2. Output dimensions are directly given via the size input(#4)
+ *
+ * Hence, either input Scale or Input Sizes can be defined, if both are
+ * connected, the operator will throw an error.
+ *
+ * Resize takes (up to) 4 different inputs :
+ * #1 Input to resize :
+ *   N-D tensor.
+ *
+ * #2 ROI (optional) :
+ *   1-D tensor of coordinates given as [start1, …, startN, end1, …, endN]
+ *   where N is the rank of X or the length of axes, if provided. The RoIs’
+ *   coordinates are normalized in the coordinate system of the input image.
+ *   If not set default ROI is the entier image.
+ * #3 scales (optional) - tensor(float):
+ *   The scale array along each dimension.
+ *    The number of elements of ‘scales’ should be the same as the rank of
+ * input ‘X’ or the length of ‘axes’, if provided. Accepted values: (0,inf)
+ *    - (0,1)   : downsampling
+ *    - 1       : identity
+ *    - (1,inf) : upsampling
+ * #4. Sizes - tensor(int64):
+ *   Target size of the output tensor.
+ *   Its interpretation depends on the ‘keep_aspect_ratio_policy’ value.
+ *   The number of elements of ‘sizes’ should be the same as either :
+ *   - The rank of input ‘X’
+ *   - The length of ‘axes’ attribute, if provided.
+ * @endverbatim
+ * @warning : Only one of ‘scales’ and ‘sizes’ can be specified.
+ * @param coordinate_transformation_mode
+ * @param cubic_coeff_a the a coefficient of cubic interpolation. Moost often
+ * it is set to -0.75
+ * @param InterpolationMode type of interpolation (currently only support cubic
+ * interpolation)
+ */
+class Resize_Op
+    : public OperatorTensor,
+      public Registrable<
+          Resize_Op,
+          std::string,
+          std::function<std::shared_ptr<OperatorImpl>(const Resize_Op &)>> {
+
+  private:
+    using Attributes_ =
+        StaticAttributes<ResizeAttr,
+                         Interpolation::CoordinateTransformation,
+                         float,
+                         Interpolation::Mode>;
+    template <ResizeAttr e>
+    using attr = typename Attributes_::template attr<e>;
+    const std::shared_ptr<Attributes_> mAttributes;
+
+  public:
     static const std::string Type;
-
-    Resize_Op();
+    /**
+     * @brief creates a resize operator
+     * This node can take 4 different inputs, more details in the class
+     * doxygen.
+     * 1. Input to resize :
+     * 2. ROI NOT SUPPORTED (optional) :
+     * 3. scales (optional) - tensor(float):
+     * 4. sizes - tensor(int64):
+     * @param[in] coordinate_transformation_mode
+     * @param[in] cubic_coeff_a the a coefficient of cubic interpolation. Only
+     * used if interpolation_mode = Interpolation::Mode::Cubic
+     * @param[in] interpolationMode : Type of interpolation used for
+     * up/downsampling
+     * @warning Scales & ROI input cannot be set simultaneously. If bot are
+     * set, forward will fail.
+     * @return NodePtr
+     */
+    explicit Resize_Op(Interpolation::CoordinateTransformation coordTransfoMode,
+              Interpolation::Mode interpol_mode =
+                  Interpolation::Mode::NearestRoundPreferFloor,
+              float cubic_coef_a = -.75f) 
+        : OperatorTensor(Type,
+                         {InputCategory::Data,
+                          InputCategory::OptionalData,
+                          InputCategory::OptionalData,
+                          InputCategory::OptionalData},
+                         1),
+          mAttributes(std::make_shared<Attributes_>(
+              attr<ResizeAttr::CubicCoeffA>(cubic_coef_a),
+              attr<ResizeAttr::CoordinateTransformationMode>(coordTransfoMode),
+              attr<ResizeAttr::InterpolationMode>(interpol_mode))) {}
 
     /**
-     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s),
-     * but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output
+     * tensor(s), but not its input tensors : The new operator has no input
+     * associated).
      * @param op Operator to copy.
      */
-    Resize_Op(const Resize_Op& op);
+    Resize_Op(const Resize_Op &op)
+        : OperatorTensor(op), mAttributes(op.mAttributes) {
+        if (!op.backend().empty()) {
+            SET_IMPL_MACRO(Resize_Op, *this, op.backend());
+        } else {
+            mImpl = nullptr;
+        }
+    }
 
     /**
      * @brief Clone the operator using its copy-constructor.
      * @see Operator::Resize_Op
      */
-    std::shared_ptr<Operator> clone() const override;
+    std::shared_ptr<Operator> clone() const override final {
+        return std::make_shared<Resize_Op>(*this);
+    }
 
     bool dimsForwarded() const override final;
     bool forwardDims(bool allowDataDependency = false) override final;
 
-    void setBackend(const std::string& name, DeviceIdx_t device = 0) override final;
-    std::set<std::string> getAvailableBackends() const override;
+    void setBackend(const std::string &name,
+                    DeviceIdx_t device = 0) override final;
+    std::set<std::string> getAvailableBackends() const override {
+        return Registrar<Resize_Op>::getKeys();
+    }
+
+    Interpolation::CoordinateTransformation &coordinateTransformationMode() {
+        return mAttributes
+            ->template getAttr<ResizeAttr::CoordinateTransformationMode>();
+    }
+    float &cubicCoefA() {
+        return mAttributes->template getAttr<ResizeAttr::CubicCoeffA>();
+    }
+    Interpolation::Mode &interpolationmode() {
+        return mAttributes->template getAttr<ResizeAttr::InterpolationMode>();
+    }
+    // bool &excludeOutside() {
+    //   return mAttributes->template getAttr<ResizeAttr::excludeOutside>();
+    // }
 
-    static const std::vector<std::string> getInputsName(){
+    static const std::vector<std::string> getInputsName() {
         //  roi, scales, sizes, even if considered as const parameters/input
         return {"data_input", "roi ", "scales", "sizes"};
     }
-    static const std::vector<std::string> getOutputsName(){
+    static const std::vector<std::string> getOutputsName() {
         return {"data_output"};
     }
 };
 
-std::shared_ptr<Node> Resize(const std::string &name = "");
-
-}  // namespace Aidge
-
-
-#endif /* AIDGE_CORE_OPERATOR_Resize_H_ */
\ No newline at end of file
+/**
+ * @brief creates a node that contains a resize operator
+ * This node can take 4 different inputs, more details in the class doxygen.
+ * #0 Input to resize
+ * #1 ROI NOT SUPPORTED (optional) - Tensor(double|float|float16)
+ * #2 scales (optional) - tensor(float)
+ * #3 sizes - tensor(int64)
+ * @param[in] coordinate_transformation_mode
+ * @param[in] interpolationMode type of interpolation used in case of
+ * upsampling
+ * @param[in] cubic_coeff_a the "a" coefficient of cubic interpolation. Only
+ * used if interpolation_mode = Interpolation::Mode::Cubic
+ * @warning Scales & ROI input cannot be set simultaneously. If bot are set,
+ * forward will fail.
+ * @return NodePtr
+ */
+std::shared_ptr<Node>
+Resize(Interpolation::CoordinateTransformation coordTransfoMode =
+           Interpolation::CoordinateTransformation::HalfPixel,
+       Interpolation::Mode interpolMode =
+           Interpolation::Mode::NearestRoundPreferFloor,
+       float cubicCoefA = -.75f,
+       const std::string &name = "");
+
+} // namespace Aidge
+
+namespace {
+template <>
+const char *const EnumStrings<Aidge::ResizeAttr>::data[] = {
+    "coordinateTransformationMode",
+    "cubicCoeffA",
+    "InterpolationMode",
+};
+}
+#endif /* AIDGE_CORE_OPERATOR_RESIZE_H_ */
diff --git a/python_binding/operator/pybind_Resize.cpp b/python_binding/operator/pybind_Resize.cpp
index 35321f525..4674c9049 100644
--- a/python_binding/operator/pybind_Resize.cpp
+++ b/python_binding/operator/pybind_Resize.cpp
@@ -11,20 +11,55 @@
 
 #include <pybind11/pybind11.h>
 
-#include "aidge/operator/Resize.hpp"
 #include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/operator/Resize.hpp"
+#include "aidge/utils/Registrar.hpp"
 
 namespace py = pybind11;
 namespace Aidge {
 
-void init_Resize(py::module& m) {
-    py::class_<Resize_Op, std::shared_ptr<Resize_Op>, OperatorTensor>(m, "ResizeOp", py::multiple_inheritance())
-        .def_static("get_inputs_name", &Resize_Op::getInputsName)
-        .def_static("get_outputs_name", &Resize_Op::getOutputsName)
-        .def_readonly_static("Type", &Resize_Op::Type);
+void init_Resize(py::module &m) {
+  auto pyResizeOp =
+      py::class_<Resize_Op, std::shared_ptr<Resize_Op>, OperatorTensor>(
+          m, "ResizeOp", py::multiple_inheritance())
+          .def_static("get_inputs_name", &Resize_Op::getInputsName)
+          .def_static("get_outputs_name", &Resize_Op::getOutputsName)
+          .def_readonly_static("Type", &Resize_Op::Type);
+
+  declare_registrable<Resize_Op>(m, "ResizeOp");
 
-    declare_registrable<Resize_Op>(m, "ResizeOp");
+  // Enum binding under BitShiftOp class
+  py::enum_<Resize_Op::CoordinateTransformation>(pyResizeOp,
+                                                 "coordinate_transformation")
+      .value("half_pixel", Resize_Op::CoordinateTransformation::HalfPixel)
+      .value("half_pixel_symmetric",
+             Resize_Op::CoordinateTransformation::HalfPixelSymmetric)
+      .value("half_pixel_pytorch",
+             Resize_Op::CoordinateTransformation::PytorchHalfPixel)
+      .value("align_corners", Resize_Op::CoordinateTransformation::AlignCorners)
+      .value("asymetric", Resize_Op::CoordinateTransformation::Asymetric)
+      .export_values();
 
-    m.def("Resize", &Resize, py::arg("name") = "");
+  m.def("Resize", &Resize,
+        py::arg("coord_transfo_mode") =
+            Resize_Op::CoordinateTransformation::HalfPixel,
+        py::arg("interpolation_mode") =
+            Interpolation::Mode::NearestRoundPreferFloor,
+        py::arg("cubic_interpolation_coefficient_a") = -.75f,
+        py::arg("name") = "", R"mydelimiter(
+    Initialize a node containing a Resize operator.
+    This node can take 4 different inputs.
+    #0 Input to resize 
+    #1 ROI NOT SUPPORTED (optional) - Tensor(double|float|float16)
+    #2 scales (optional) - tensor(float): #3 sizes - tensor(int64)   
+    #3 sizes - tensor(int64)
+	:type coordinate_transformation_mode : :py:class: List[Int]
+	:param interpolationMode : Type of interpolation used in case of upsampling 
+	:type interpolationMode : Interpolation::Mode
+	:param cubic_coeff_a : "A" coefficient of cubic interpolation. Only used if interpolation_mode = Interpolation::Mode::Cubic
+	:type cubic_coeff_a  : float
+    :param name : name of the node.
+    :type name : str
+)mydelimiter");
 }
-}  // namespace Aidge
+} // namespace Aidge
diff --git a/src/operator/Resize.cpp b/src/operator/Resize.cpp
index 9e5762452..2bde27f14 100644
--- a/src/operator/Resize.cpp
+++ b/src/operator/Resize.cpp
@@ -11,56 +11,29 @@
 
 #include "aidge/operator/Resize.hpp"
 
-#include <cstddef>    // std::size_t
-#include <cstdint>    // std::int64_t
-#include <stdexcept>  // std::runtime_error
+#include <algorithm>
+#include <cstddef>   // std::size_t
+#include <cstdint>   // std::int64_t
+#include <fmt/core.h>
+#include <stdexcept> // std::runtime_error
 #include <string>
 #include <vector>
-#include <fmt/core.h>
 
-#include "aidge/backend/OperatorImpl.hpp"
+#include "aidge/data/Data.hpp"
+#include "aidge/data/Interpolation.hpp"
 #include "aidge/data/Tensor.hpp"
 #include "aidge/utils/ErrorHandling.hpp"
 #include "aidge/utils/Types.h"
 
-const std::string Aidge::Resize_Op::Type = "Resize";
-
-Aidge::Resize_Op::Resize_Op()
-    : OperatorTensor(Type,
-        {InputCategory::Data,
-            InputCategory::OptionalData,
-            InputCategory::OptionalData,
-            InputCategory::OptionalData},
-        1) {}
-
-/**
- * @brief Copy-constructor. Copy the operator attributes and its output tensor(s),
- * but not its input tensors (the new operator has no input associated).
- * @param op Operator to copy.
- */
-
-Aidge::Resize_Op::Resize_Op(const Aidge::Resize_Op& op)
-    : OperatorTensor(op)
-{
-    if (!op.backend().empty()) {
-        SET_IMPL_MACRO(Resize_Op, *this, op.backend());
-    }
-    else {
-        mImpl = nullptr;
-    }
-}
+namespace Aidge {
 
-std::shared_ptr<Aidge::Operator> Aidge::Resize_Op::clone() const {
-    return std::make_shared<Resize_Op>(*this);
-}
+const std::string Resize_Op::Type = "Resize";
 
-bool Aidge::Resize_Op::dimsForwarded() const {
+bool Resize_Op::dimsForwarded() const {
     // in case of ROI add getInput(1) condition
-    if ((getInput(1) && !getInput(1)->undefined())
-        || (getInput(2) && !getInput(2)->undefined())
-        || (getInput(3) && !getInput(3)->undefined())
-        )
-    {
+    if ((getInput(1) && !getInput(1)->undefined()) ||
+        (getInput(2) && !getInput(2)->undefined()) ||
+        (getInput(3) && !getInput(3)->undefined())) {
         // output dims are data dependent
         return false;
     }
@@ -68,93 +41,167 @@ bool Aidge::Resize_Op::dimsForwarded() const {
     return OperatorTensor::dimsForwarded();
 }
 
-bool Aidge::Resize_Op::forwardDims(bool allowDataDependency) {
-    if (inputsAssociated()) {
-        AIDGE_ASSERT(getInput(0)->nbDims() == 4,
-            "input tensor must have dimensions = 4 (batch, channel, height, width).");
-
-        const bool input1ROIPresent           = getInput(1) && !getInput(1)->undefined();
-        const bool input2ScalesPresent        = getInput(2) && !getInput(2)->undefined();
-        const bool input3SizesPresent         = getInput(3) && !getInput(3)->undefined();
-
-        AIDGE_ASSERT(input2ScalesPresent != input3SizesPresent, "Only one of scales and  sizes can be specified.")
+bool Resize_Op::forwardDims(bool allowDataDependency) {
+    if (!inputsAssociated()) {
+        return false;
+    }
 
-        if (input1ROIPresent) {
-            AIDGE_THROW_OR_ABORT(std::runtime_error, "Input #1 (ROI) is given and it is not supported.");
+    /** @brief input #0 */
+    int16_t inDataIdx = 0;
+    /** @brief input #1 */
+    int16_t inROIIdx = 1;
+    /** @brief input #2 */
+    int16_t inScalesIdx = 2;
+    /** @brief input #3 */
+    int16_t inSizesIdx = 3;
+
+    std::vector<DimSize_t> outDims = getInput(inDataIdx)->dims();
+    /////////////////////////////////////////////////////
+    // Ensuring operator is connected properly
+    const bool inputROIPresent =
+        getInput(inROIIdx) && !getInput(inROIIdx)->undefined();
+    const bool inputScalesPresent =
+        getInput(inScalesIdx) && !getInput(inScalesIdx)->undefined();
+    const bool inputSizesPresent =
+        getInput(inSizesIdx) && !getInput(inSizesIdx)->undefined();
+
+    AIDGE_ASSERT(getInput(inDataIdx)->nbDims() == 4,
+                 "{}: Input tensor must have dimensions = 4 (batch, channel, "
+                 "height, width).",
+                 type());
+    AIDGE_ASSERT(
+        inputScalesPresent || inputSizesPresent,
+        "{}: Either input Scales(#2) or input Sizes(#3) must be defined.",
+        type());
+    AIDGE_ASSERT(inputScalesPresent != inputSizesPresent,
+                 "{}: Only one of the two inputs can be defined between input "
+                 "Scales(#2) "
+                 "and Sizes(#3). They cannot be specified at the same time.",
+                 type())
+
+    ////////////////////////////////////////////
+    // Case resize is done using Scales formula
+    if (inputScalesPresent) {
+        if (!allowDataDependency) {
+            Log::warn("{}: cannot execute forwardDims() as the output "
+                      "dimensions depends on the input #2",
+                      type());
+            return false;
         }
-        else if (input2ScalesPresent)  {
-            if (!allowDataDependency) {
-                Log::warn("Resize_Op: cannot execute forwardDims() as the output dimensions depend on the input #2");
-                return false;
-            }
-
-            AIDGE_ASSERT(getInput(0)->nbDims() ==  getInput(2)->size(),
-                "input #0 and input #2 (Scales) must have the same dimensions.");
-
-            std::vector<DimSize_t>      outDims = getInput(0)->dims();
-            const std::vector<DimSize_t> inDims = getInput(0)->dims();
-
-            std::shared_ptr<Tensor> fallback;
-            const auto& scales = getInput(2)->refCastFrom(fallback, NativeType<int64_t>::type, "cpu");
-
-            for (std::size_t dim=0; dim < getInput(2)->size(); ++dim) {
-                outDims[dim] = inDims[dim]*static_cast<int64_t*>(scales.getImpl()->hostPtr())[dim];
-            }
-
-            mOutputs[0]->resize(outDims);
-            return true;
+        std::vector<int> ROI;
+
+        if (inputROIPresent) {
+            AIDGE_THROW_OR_ABORT(
+                std::runtime_error,
+                "{}: input ROI(#{}) is present but it is not supported.",
+                type(),
+                inROIIdx);
+            // ROI = std::vector<int>(0, getInput(inDataIdx)->size() - 1);
+            // // magic numbers explaiend above
+            // size_t ROIExpectedSize = (getInput(inDataIdx)->nbDims() - 1) *
+            // 2; ROI.resize(ROIExpectedSize);
+            // AIDGE_ASSERT(getInput(inROIIdx)->size() == ROIExpectedSize,
+            //            "{}: Input #{} (ROI) should be ordered as following :
+            //            " "1-D tensor given as [start1, …, startN, end1, …,
+            //            endN]," "where N is the rank of input tensor."
+            //            "Hence, its dims should be "
+            //            "input_tensor.nbDims() * 2 = {}"
+            //            "Received following size: {}",
+            //            type(), inROIIdx, ROIExpectedSize,
+            //            getInput(inROIIdx)->size());
         }
-        else if (input3SizesPresent) {
-            if (!allowDataDependency) {
-                Log::warn("Resize_Op: cannot execute forwardDims() as the output dimensions depend on the input #3");
-                return false;
-            }
-
-            AIDGE_ASSERT(getInput(0)->nbDims() ==  getInput(3)->size(),
-                "input #0 and input #3 (Sizes) must have the same dimensions.");
-
-            std::vector<DimSize_t> outDims = getInput(0)->dims();
 
-            std::shared_ptr<Tensor> fallback;
-            const auto& sizes = getInput(3)->refCastFrom(fallback, NativeType<int64_t>::type, "cpu");
-
-            for (std::size_t dim=0; dim < getInput(3)->size(); ++dim) {
-                outDims[dim] = static_cast<int64_t*>(sizes.getImpl()->hostPtr())[dim];
-            }
+        AIDGE_ASSERT(
+            getInput(inDataIdx)->nbDims() == getInput(inScalesIdx)->size(),
+            "{}: input #0 and input #2 (Scales) must have the "
+            "same dimensions.",
+            type());
+        AIDGE_ASSERT(
+            getInput(inScalesIdx)->dataType() == DataType::Float32,
+            "{}: Wrong data type for input Scales(#{}), supported dtype: {}.",
+            type(),
+            inScalesIdx,
+            DataType::Float32);
+
+        std::shared_ptr<Tensor> fallback;
+        const auto &scales =
+            getInput(inScalesIdx)
+                ->refCastFrom(fallback,
+                              DataType::Float32,
+                              getInput(inScalesIdx)->backend());
+
+        const std::vector<DimSize_t> inDims = getInput(inDataIdx)->dims();
+        for (std::size_t dim = 0; dim < getInput(inScalesIdx)->size(); ++dim) {
+            auto scaleAlongDim = scales.get<float>(dim);
+            AIDGE_ASSERT(scaleAlongDim > 0,
+                         "{}: all scales values must be sctricly positive, "
+                         "received {}.",
+                         type(),
+                         scaleAlongDim);
+            outDims[dim] =
+                static_cast<DimSize_t>(inDims[dim] * scaleAlongDim);
+        }
 
-            mOutputs[0]->resize(outDims);
-            return true;
+        ///////////////////////////////////////////////////////////////
+        // case where resize output dims are given via the Size input
+    } else {
+        if (!allowDataDependency) {
+            Log::warn("{}: cannot execute forwardDims() as the output "
+                      "dimensions depend on the input sizes(#{})",
+                      type(),
+                      inSizesIdx);
+            return false;
         }
-        else {
-            AIDGE_THROW_OR_ABORT(std::runtime_error, "Error: Either Input #2 or Input #3 must be present.");
+        AIDGE_ASSERT(
+            getInput(inDataIdx)->nbDims() == getInput(inSizesIdx)->size(),
+            "input #0 and input #3 (Sizes) must have the "
+            "same dimensions.");
+        AIDGE_ASSERT(
+            getInput(inSizesIdx)->dataType() == DataType::Int64,
+            "{}: Wrong data type for input Sizes(#{}), supported dtype: {}.",
+            type(),
+            inSizesIdx,
+            DataType::Int64);
+
+        std::shared_ptr<Tensor> fallback;
+        const auto &sizes = getInput(inSizesIdx)
+                                ->refCastFrom(fallback,
+                                              DataType::Int64,
+                                              getInput(inSizesIdx)->backend());
+
+        for (std::size_t dim = 0; dim < getInput(inSizesIdx)->size(); ++dim) {
+            outDims[dim] = sizes.get<int64_t>(dim);
         }
     }
-
-    return false;
+    mOutputs[0]->resize(outDims);
+    return true;
 }
 
-void Aidge::Resize_Op::setBackend(const std::string& name, Aidge::DeviceIdx_t device) {
+void Resize_Op::setBackend(const std::string &name, DeviceIdx_t device) {
     SET_IMPL_MACRO(Resize_Op, *this, name);
     mOutputs[0]->setBackend(name, device);
 
-    // By default, automatically set backend for all inputs: roi, scales and sizes
-    if(getInput(1)) {
+    // By default, automatically set backend for all inputs: roi, scales and
+    // sizes
+    if (getInput(1)) {
         getInput(1)->setBackend(name, device);
     }
-    if(getInput(2)) {
+    if (getInput(2)) {
         getInput(2)->setBackend(name, device);
     }
-    if(getInput(3)) {
+    if (getInput(3)) {
         getInput(3)->setBackend(name, device);
     }
 }
 
-std::set<std::string> Aidge::Resize_Op::getAvailableBackends() const {
-    return Registrar<Resize_Op>::getKeys();
+std::shared_ptr<Node>
+Resize(Interpolation::CoordinateTransformation coordTransfoMode,
+       Interpolation::Mode interpolMode,
+       float cubicCoefA,
+       const std::string &name) {
+    return std::make_shared<Node>(std::make_shared<Resize_Op>(coordTransfoMode,
+                                                              interpolMode,
+                                                              cubicCoefA),
+                                  name);
 }
-
-/////////////////////////////////////////////
-
-std::shared_ptr<Aidge::Node> Aidge::Resize(const std::string &name) {
-    return std::make_shared<Node>(std::make_shared<Resize_Op>(), name);
-}
\ No newline at end of file
+} // namespace Aidge
diff --git a/unit_tests/operator/Test_Resize_Op.cpp b/unit_tests/operator/Test_Resize_Op.cpp
new file mode 100644
index 000000000..2e84d40e6
--- /dev/null
+++ b/unit_tests/operator/Test_Resize_Op.cpp
@@ -0,0 +1,173 @@
+/********************************************************************************
+ * Copyright (c) 2023 CEA-List
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+#include "aidge/operator/Resize.hpp"
+#include <catch2/catch_test_macros.hpp>
+#include <cstddef> // std::size_t
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "aidge/data/Tensor.hpp"
+#include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/utils/Log.hpp"
+
+namespace Aidge {
+/**
+ * Test the resize operation of the given operator with the specified input
+ * dimensions, scales or sizes, and expected output dimensions.
+ *
+ * @param op The operator to test.
+ * @param input_dims The input dimensions to use for the test.
+ * @param scales_or_sizes The scales or sizes to use for the test.
+ * @param expected_dims The expected output dimensions for the test.
+ * @param use_scales A boolean flag indicating whether to use scales or sizes
+ * for the test.
+ */
+
+void setupTestResize(const std::shared_ptr<OperatorTensor> &op,
+                       const std::vector<Aidge::DimSize_t> &input_dims,
+                       const std::vector<float> &scales,
+                       const std::vector<int64_t> &sizes,
+                       const std::vector<Aidge::DimSize_t> &expected_dims) {
+  Log::setConsoleLevel(Log::Level::Info);
+  Log::info("\n\n\nResize test:");
+  Log::info("\tInput_dims: {}", input_dims);
+  Log::info("\tScales: {}", scales);
+  Log::info("\tSizes: {}", sizes);
+  Log::info("\tExpected output dims: {}", expected_dims);
+  std::shared_ptr<Tensor> input_data = std::make_shared<Tensor>();
+  input_data->setBackend("cpu");
+  input_data->resize(input_dims);
+  input_data->zeros();
+
+  op->associateInput(0, input_data);
+
+  const std::shared_ptr<Tensor> tensor_values = std::make_shared<Tensor>();
+  tensor_values->setBackend("cpu");
+  if (!scales.empty()) {
+    tensor_values->setDataType(DataType::Float32);
+    tensor_values->resize(std::vector<std::size_t>({scales.size()}));
+    tensor_values->getImpl()->copyFromHost(scales.data(), scales.size());
+    op->associateInput(2, tensor_values);
+  }
+  if (!sizes.empty()) {
+    tensor_values->setDataType(DataType::Int64);
+    tensor_values->resize(std::vector<std::size_t>({sizes.size()}));
+    tensor_values->getImpl()->copyFromHost(sizes.data(), sizes.size());
+
+    op->associateInput(3, tensor_values);
+  }
+}
+
+TEST_CASE("[core/operator] Resize_Op(forwardDims)",
+          "[Resize][forwardDimsScales]") {
+  std::vector<Aidge::DimSize_t> input_dims;
+  std::vector<float> scales;
+  std::vector<int64_t> sizes;
+  std::vector<Aidge::DimSize_t> expected_dims;
+
+  std::shared_ptr<Node> myResize = Resize();
+  auto op = std::static_pointer_cast<OperatorTensor>(myResize->getOperator());
+
+  SECTION("Un-connected input leads to failure.") {
+    REQUIRE_THROWS(op->forwardDims());
+  }
+  SECTION("Connecting both Scales & Sizes leads to failure") {
+    input_dims = std::vector<Aidge::DimSize_t>({4, 1, 2, 2});
+    scales = std::vector<float>({.5, 3, 2, 2});
+    sizes = std::vector<int64_t>({});
+    expected_dims = std::vector<Aidge::DimSize_t>({2, 3, 4, 4});
+    setupTestResize(op, input_dims, scales, sizes, expected_dims);
+    REQUIRE_NOTHROW(op->forwardDims(true));
+    REQUIRE(op->getOutput(0)->dims() == expected_dims);
+  }
+
+  SECTION("Input Scales") {
+    SECTION("TEST 1") {
+      input_dims = std::vector<Aidge::DimSize_t>({1, 1, 2, 2});
+      scales = std::vector<float>({1, 1, 2, 2});
+      sizes = std::vector<int64_t>({});
+      expected_dims = std::vector<Aidge::DimSize_t>({1, 1, 4, 4});
+      setupTestResize(op, input_dims, scales, sizes, expected_dims);
+      REQUIRE_NOTHROW(op->forwardDims(true));
+      REQUIRE(op->getOutput(0)->dims() == expected_dims);
+    }
+    SECTION("TEST 2") {
+      input_dims = std::vector<Aidge::DimSize_t>({4, 4, 10, 10});
+      scales = std::vector<float>({1, 1, 2, 3});
+      sizes = std::vector<int64_t>({});
+      expected_dims = std::vector<Aidge::DimSize_t>({4, 4, 20, 30});
+      setupTestResize(op, input_dims, scales, sizes, expected_dims);
+      REQUIRE_NOTHROW(op->forwardDims(true));
+      REQUIRE(op->getOutput(0)->dims() == expected_dims);
+    }
+    SECTION("TEST 3") {
+      input_dims = std::vector<Aidge::DimSize_t>({4, 2, 10, 10});
+      scales = std::vector<float>({1, 1, 0.5, 0.5});
+      sizes = std::vector<int64_t>({});
+      expected_dims = std::vector<Aidge::DimSize_t>({4, 2, 5, 5});
+      setupTestResize(op, input_dims, scales, sizes, expected_dims);
+      REQUIRE_NOTHROW(op->forwardDims(true));
+      REQUIRE(op->getOutput(0)->dims() == expected_dims);
+    }
+    SECTION("TEST 4") {
+      input_dims = std::vector<Aidge::DimSize_t>({11, 11, 4, 4});
+      scales = std::vector<float>({1, 1, 0.3, 0.3});
+      sizes = std::vector<int64_t>({});
+      expected_dims = std::vector<Aidge::DimSize_t>({11, 11, 1, 1});
+      setupTestResize(op, input_dims, scales, sizes, expected_dims);
+      REQUIRE_NOTHROW(op->forwardDims(true));
+      REQUIRE(op->getOutput(0)->dims() == expected_dims);
+    }
+  }
+
+  SECTION("Input Sizes") {
+    SECTION("TEST 1") {
+      input_dims = std::vector<Aidge::DimSize_t>({1, 1, 2, 2});
+      scales = std::vector<float>({});
+      sizes = std::vector<int64_t>({4, 5, 8, 8});
+      expected_dims = std::vector<Aidge::DimSize_t>({4, 5, 8, 8});
+      setupTestResize(op, input_dims, scales, sizes, expected_dims);
+      REQUIRE_NOTHROW(op->forwardDims(true));
+      REQUIRE(op->getOutput(0)->dims() == expected_dims);
+    }
+    SECTION("TEST 2") {
+      input_dims = std::vector<Aidge::DimSize_t>({60, 60, 30, 30});
+      scales = std::vector<float>({});
+      sizes = std::vector<int64_t>({1, 1, 75, 75});
+      expected_dims = std::vector<Aidge::DimSize_t>({1, 1, 75, 75});
+      setupTestResize(op, input_dims, scales, sizes, expected_dims);
+      REQUIRE_NOTHROW(op->forwardDims(true));
+      REQUIRE(op->getOutput(0)->dims() == expected_dims);
+    }
+    SECTION("TEST 3") {
+      input_dims = std::vector<Aidge::DimSize_t>({11, 11, 20, 20});
+      scales = std::vector<float>({});
+      sizes = std::vector<int64_t>({19, 6, 8, 8});
+      expected_dims = std::vector<Aidge::DimSize_t>({19, 6, 8, 8});
+      setupTestResize(op, input_dims, scales, sizes, expected_dims);
+      REQUIRE_NOTHROW(op->forwardDims(true));
+      REQUIRE(op->getOutput(0)->dims() == expected_dims);
+    }
+    SECTION("TEST 4") {
+      input_dims = std::vector<Aidge::DimSize_t>({43, 211, 22, 22});
+      scales = std::vector<float>({});
+      sizes = std::vector<int64_t>({1, 1, 10, 10});
+      expected_dims = std::vector<Aidge::DimSize_t>({1, 1, 10, 10});
+      setupTestResize(op, input_dims, scales, sizes, expected_dims);
+      REQUIRE_NOTHROW(op->forwardDims(true));
+      REQUIRE(op->getOutput(0)->dims() == expected_dims);
+    }
+  }
+}
+
+} // namespace Aidge
-- 
GitLab