diff --git a/include/aidge/aidge.hpp b/include/aidge/aidge.hpp index 651a5de69596ee867a97b06ba683f49b05a41303..6ef7441f8d62471fcc7b2425a8b465184778b752 100644 --- a/include/aidge/aidge.hpp +++ b/include/aidge/aidge.hpp @@ -47,6 +47,7 @@ #include "aidge/operator/Gather.hpp" #include "aidge/operator/GenericOperator.hpp" #include "aidge/operator/GlobalAveragePooling.hpp" +#include "aidge/operator/GridSample.hpp" #include "aidge/operator/MatMul.hpp" #include "aidge/operator/MaxPooling.hpp" #include "aidge/operator/MetaOperator.hpp" diff --git a/include/aidge/operator/GridSample.hpp b/include/aidge/operator/GridSample.hpp new file mode 100644 index 0000000000000000000000000000000000000000..af44a5df5de6908d58951b93921d49ec8e7df708 --- /dev/null +++ b/include/aidge/operator/GridSample.hpp @@ -0,0 +1,98 @@ +/******************************************************************************** + * 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 + * + ********************************************************************************/ + +#ifndef AIDGE_CORE_OPERATOR_GRIDSAMPLE_H_ +#define AIDGE_CORE_OPERATOR_GRIDSAMPLE_H_ + +#include <cstddef> // std::size_t +#include <memory> +#include <string> +#include <vector> + +#include "aidge/graph/Node.hpp" +#include "aidge/operator/OperatorTensor.hpp" +#include "aidge/utils/Attributes.hpp" +#include "aidge/utils/Registrar.hpp" +#include "aidge/utils/StaticAttributes.hpp" + +namespace Aidge { + +enum class GridSampleAttr { Mode, PaddingMode, AlignCorners }; + +template <DimIdx_t DIM> +class GridSample_Op : public OperatorTensor, + public Registrable<GridSample_Op<DIM>, std::string, std::shared_ptr<OperatorImpl>(const GridSample_Op<DIM>&)> { + +public: + static const std::string Type; + + enum class Mode { Linear, Nearest, Cubic }; + enum class PaddingMode { Zeros, Border, Reflexion }; + +private: + using Attributes_ = StaticAttributes<GridSampleAttr, Mode, PaddingMode, bool>; + template <GridSampleAttr e> + using attr = typename Attributes_::template attr<e>; + const std::shared_ptr<Attributes_> mAttributes; + +public: + + GridSample_Op(Mode mode = Mode::Linear, + PaddingMode paddingMode = PaddingMode::Zeros, + bool alignCorners = false); + + GridSample_Op(const GridSample_Op<DIM>& other); + ~GridSample_Op() noexcept; + +public: + + std::shared_ptr<Operator> clone() const override; + + bool forwardDims(bool /*allowDataDependencies*/ = false) override final; + + void setBackend(const std::string& name, DeviceIdx_t device = 0) override final; + + inline std::shared_ptr<Attributes> attributes() const override { return mAttributes; } + inline Mode mode() const { return mAttributes->template getAttr<GridSampleAttr::Mode>(); } + inline PaddingMode paddingMode() const { return mAttributes->template getAttr<GridSampleAttr::PaddingMode>(); } + inline bool alignBorders() const { return mAttributes->template getAttr<GridSampleAttr::AlignCorners>(); } + + static const std::vector<std::string> getInputsName() { + return {"data_input", "grid_field"}; + } + static const std::vector<std::string> getOutputsName() { + return {"data_output"}; + } +}; + +extern template class GridSample_Op<1>; +extern template class GridSample_Op<2>; + +template <DimIdx_t DIM> +std::shared_ptr<Node> GridSample( + typename GridSample_Op<DIM>::Mode mode = GridSample_Op<DIM>::Mode::Linear, + typename GridSample_Op<DIM>::PaddingMode paddingMode = GridSample_Op<DIM>::PaddingMode::Zeros, + bool alignCorners = false, + const std::string& name = ""); + +} // namespace Aidge + + +namespace { +template <> +const char* const EnumStrings<Aidge::GridSampleAttr>::data[] = { + "mode", + "padding_mode", + "align_corners" +}; +} + +#endif /* AIDGE_CORE_OPERATOR_GRIDSAMPLE_H_ */ diff --git a/python_binding/operator/pybind_GridSample.cpp b/python_binding/operator/pybind_GridSample.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9c96856fc0c7c1d324b91df435e8d66e98a0240f --- /dev/null +++ b/python_binding/operator/pybind_GridSample.cpp @@ -0,0 +1,82 @@ +/******************************************************************************** + * 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 <pybind11/pybind11.h> +#include <pybind11/stl.h> +#include <string> +#include <vector> +#include <array> + +#include "aidge/backend/OperatorImpl.hpp" +#include "aidge/data/Tensor.hpp" +#include "aidge/operator/GridSample.hpp" +#include "aidge/operator/OperatorTensor.hpp" +#include "aidge/utils/Types.h" +#include "aidge/utils/Registrar.hpp" // declare_registrable + +static Aidge::GridSample_Op::Mode stringToInterpolationMode(const std::string& mode) { + static std::unordered_map<std::string, Aidge::GridSample_Op::Mode> map = { + {"linear", Aidge::GridSample_Op::Mode::Linear}, + {"nearest", Aidge::GridSample_Op::Mode::Nearest}, + {"cubic", Aidge::GridSample_Op::Mode::Cubic} + } + return map[mode]; +} + +static Aidge::GridSample_Op::PaddingMode stringToPaddingMode(const std::string& mode) { + static std::unordered_map<std::string, Aidge::GridSample_Op::PaddingMode> map = { + {"zeros", Aidge::GridSample_Op::PaddingMode::Zeros}, + {"border", Aidge::GridSample_Op::PaddingMode::Border}, + {"reflexion", Aidge::GridSample_Op::PaddingMode::Reflexion} + } + return map[mode]; +} + +namespace py = pybind11; +namespace Aidge { + +template <DimIdx_t DIM> void declare_GridSampleOp(py::module &m) { + const std::string pyClassName("GridSampleOp" + std::to_string(DIM) + "D"); + py::class_<GridSample_Op<DIM>, std::shared_ptr<GridSample_Op<DIM>>, OperatorTensor>( + m, pyClassName.c_str(), + py::multiple_inheritance()) + .def(py::init([](const std::string& mode, + const std::string& padding_mode, + bool align_corners) { + return new GridSample_Op<DIM>(stringToInterpolationMode(mode), stringToPaddingMode(padding_mode), align_corners); + }), py::arg("mode") = "linear", + py::arg("padding_mode") = "zeros", + py::arg("alogn_corners") = false) + .def_static("get_inputs_name", &GridSample_Op<DIM>::getInputsName) + .def_static("get_outputs_name", &GridSample_Op<DIM>::getOutputsName) + ; + + declare_registrable<GridSample_Op<DIM>>(m, pyClassName); + + m.def(("GridSample" + std::to_string(DIM) + "D").c_str(), [](const std::string& mode, + const std::string& padding_mode, + bool align_corners + const std::string& name) { + return GridSample<DIM>(stringToInterpolationMode(mode), stringToPaddingMode(padding_mode), align_corners, name); + }, py::arg("mode"), + py::arg("padding_mode"), + py::arg("align_corners"), + py::arg("name") = ""); +} + + +void init_GridSample(py::module &m) { + declare_GridSampleOp<1>(m); + declare_GridSampleOp<2>(m); +// declare_GridSampleOp<3>(m); +} + +} // namespace Aidge diff --git a/python_binding/pybind_core.cpp b/python_binding/pybind_core.cpp index 9443ed55eaaf6dc04ad9ee4612ed9d491aed54ae..918143213f3dd490ef0e448f086c09135b05f6af 100644 --- a/python_binding/pybind_core.cpp +++ b/python_binding/pybind_core.cpp @@ -39,6 +39,7 @@ void init_FC(py::module&); void init_Gather(py::module&); void init_GenericOperator(py::module&); void init_GlobalAveragePooling(py::module&); +void init_GridSample(py::module&); void init_LeakyReLU(py::module&); void init_MatMul(py::module&); void init_MaxPooling(py::module&); @@ -110,6 +111,7 @@ void init_Aidge(py::module& m) { init_Gather(m); init_GenericOperator(m); init_GlobalAveragePooling(m); + init_GridSample(m); init_LeakyReLU(m); init_MatMul(m); init_MaxPooling(m); diff --git a/src/operator/GridSample.cpp b/src/operator/GridSample.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1b6d4a237623e43e06e3cc945184c467a0070200 --- /dev/null +++ b/src/operator/GridSample.cpp @@ -0,0 +1,112 @@ +/******************************************************************************** + * 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/GridSample.hpp" + +#include <cstddef> // std::size_t +#include <memory> +#include <string> +#include <vector> + +#include "aidge/data/Tensor.hpp" +#include "aidge/utils/ErrorHandling.hpp" +#include "aidge/utils/Registrar.hpp" +#include "aidge/utils/Types.h" + +template <Aidge::DimIdx_t DIM> +const std::string Aidge::GridSample_Op<DIM>::Type = "GridSample"; + +template <Aidge::DimIdx_t DIM> +Aidge::GridSample_Op<DIM>::GridSample_Op( + typename Aidge::GridSample_Op<DIM>::Mode mode, + typename Aidge::GridSample_Op<DIM>::PaddingMode paddingMode, + bool alignCorners) + : OperatorTensor(Type, {InputCategory::Data, InputCategory::Param}, 1), + mAttributes(std::make_shared<Attributes_>( + attr<GridSampleAttr::Mode>(mode), + attr<GridSampleAttr::PaddingMode>(paddingMode), + attr<GridSampleAttr::AlignCorners>(alignCorners))) +{ + // ctor +} + +template <Aidge::DimIdx_t DIM> +Aidge::GridSample_Op<DIM>::GridSample_Op(const Aidge::GridSample_Op<DIM>& other) + : OperatorTensor(other), + mAttributes(other.mAttributes) +{ + if (other.mImpl) { + SET_IMPL_MACRO(GridSample_Op<DIM>, *this, other.backend()); + } else { + mImpl = nullptr; + } +} + +template <Aidge::DimIdx_t DIM> +Aidge::GridSample_Op<DIM>::~GridSample_Op() noexcept = default; + +template <Aidge::DimIdx_t DIM> +std::shared_ptr<Aidge::Operator> Aidge::GridSample_Op<DIM>::clone() const { + return std::make_shared<GridSample_Op<DIM>>(*this); +} + +template <Aidge::DimIdx_t DIM> +bool Aidge::GridSample_Op<DIM>::forwardDims(bool /*allowDataDependency*/) { + // TODO: adapt for other formats than NCHW + if (inputsAssociated()) { + // check data has batch and channel dimensions: (N, C, D0, D1, ..., DN) + AIDGE_ASSERT((getInput(0)->nbDims() == (DIM+2)), + "Wrong input size for {} operator.", type()); + // check grid field + // should be (N, D0_out, D1_out, ..., DN_out, N+1) + AIDGE_ASSERT(((getInput(1)->nbDims() == (DIM+2)) && + (getInput(1)->dims<DIM+2>()[DIM+1] == DIM)), + "Wrong grid size {} for {} operator.", getInput(1)->dims(), type()); + + std::array<DimSize_t, DIM + 2> outputDims{}; + const std::vector<DimSize_t>& inputDims(getInput(1)->dims()); + outputDims[1] = getInput(0)->dims<DIM+2>()[1]; + outputDims[0] = inputDims[0]; + for (std::size_t i = 2; i < DIM+2; ++i) { + outputDims[i] = inputDims[i-1]; + } + + mOutputs[0]->resize(outputDims); + return true; + } + + return false; +} + + +template <Aidge::DimIdx_t DIM> +void Aidge::GridSample_Op<DIM>::setBackend(const std::string &name, Aidge::DeviceIdx_t device) { + SET_IMPL_MACRO(GridSample_Op<DIM>, *this, name); + mOutputs[0]->setBackend(name, device); +} + +template class Aidge::GridSample_Op<1>; +template class Aidge::GridSample_Op<2>; + +template <Aidge::DimIdx_t DIM> +std::shared_ptr<Aidge::Node> Aidge::GridSample( + typename Aidge::GridSample_Op<DIM>::Mode mode, + typename Aidge::GridSample_Op<DIM>::PaddingMode paddingMode, + bool alignCorners, + const std::string& name) +{ + return std::make_shared<Node>( + GridSample_Op<DIM>( + mode, + paddingMode, + alignCorners), + name); +} \ No newline at end of file diff --git a/unit_tests/operator/Test_GridSample_Op.cpp b/unit_tests/operator/Test_GridSample_Op.cpp new file mode 100644 index 0000000000000000000000000000000000000000..06555b27a42ade1798e5f602ea9d48aeb2e0f8bb --- /dev/null +++ b/unit_tests/operator/Test_GridSample_Op.cpp @@ -0,0 +1,89 @@ +/******************************************************************************** + * 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 <cstddef> // std::size_t +#include <memory> +#include <random> // std::mt19937, std::uniform_int_distribution +#include <vector> + +#include <catch2/catch_test_macros.hpp> +#include <catch2/generators/catch_generators_random.hpp> + +#include "aidge/data/Tensor.hpp" +#include "aidge/operator/GridSample.hpp" +#include "aidge/operator/OperatorTensor.hpp" + +namespace Aidge { + +TEST_CASE("[core/operator] GridSample_Op(forwardDims)", "[GridSample][forwardDims]") { + constexpr std::uint16_t NBTRIALS = 10; + + // Create a random number generator + auto rd = Catch::Generators::Detail::getSeed; + std::mt19937 gen(rd()); + std::uniform_int_distribution<std::size_t> dimsDist(1, 10); + std::uniform_int_distribution<std::size_t> nbDimsDist(1, 5); + + // Create GridSample Operator + std::shared_ptr<Node> myGridSample = GridSample<2>(GridSample_Op<2>::Mode::Cubic, GridSample_Op<2>::PaddingMode::Border, false); + auto op = std::static_pointer_cast<OperatorTensor>(myGridSample -> getOperator()); + + // input_0 + std::shared_ptr<Tensor> data_in0 = std::make_shared<Tensor>(); + op -> associateInput(0,data_in0); + // input_1 + std::shared_ptr<Tensor> grid_in1 = std::make_shared<Tensor>(); + op -> associateInput(1,grid_in1); + + SECTION("Valid shape provided") { + for (std::uint16_t trial = 0; trial < NBTRIALS; ++trial) { + + std::size_t N = dimsDist(gen); + std::size_t C = dimsDist(gen); + std::size_t H_data_in0 = dimsDist(gen); + std::size_t W_data_in0 = dimsDist(gen); + std::size_t H_grid_in1 = dimsDist(gen); + std::size_t W_grid_in1 = dimsDist(gen); + + data_in0->resize({N, C, H_data_in0, W_data_in0}); + grid_in1->resize({N, H_grid_in1, W_grid_in1, 2}); + + REQUIRE_NOTHROW(op->forwardDims()); + REQUIRE((op->getOutput(0)->dims()) == std::vector<std::size_t>({N, C, H_grid_in1, W_grid_in1})); + } + } + SECTION("Invalid shape provided") { + std::size_t N_in = dimsDist(gen); + std::size_t C = dimsDist(gen); + std::size_t H_data_in0 = dimsDist(gen); + std::size_t W_data_in0 = dimsDist(gen); + std::size_t H_grid_in1 = dimsDist(gen); + std::size_t W_grid_in1 = dimsDist(gen); + + // different batch number + std::size_t N_out = N_in+1; + data_in0->resize({N_in, C, H_data_in0, W_data_in0}); + grid_in1->resize({N_out, H_grid_in1, W_grid_in1, 2}); + REQUIRE_THROWS(op->forwardDims()); + + // different number of dimensions + data_in0->resize({N_in, C, H_data_in0, W_data_in0}); + grid_in1->resize({N_in, H_grid_in1, W_grid_in1, 2, 2}); + REQUIRE_THROWS(op->forwardDims()); + + // wrong pixel coordinates number + data_in0->resize({N_in, C, H_data_in0, W_data_in0}); + grid_in1->resize({N_in, H_grid_in1, W_grid_in1, 2 + dimsDist(gen)}); + REQUIRE_THROWS(op->forwardDims()); + } +} + +} // namespace Aidge