Skip to content
Snippets Groups Projects
Commit 07c12bbb authored by Thibault Allenet's avatar Thibault Allenet
Browse files

Merge branch 'clipping_node_remove_attr' into 'dev'

[Add] Clip Operator

See merge request !209
parents 1e21aa51 992f9167
No related branches found
No related tags found
3 merge requests!279v0.4.0,!253v0.4.0,!209[Add] Clip Operator
Pipeline #57758 passed
......@@ -41,6 +41,7 @@
#include "aidge/operator/AvgPooling.hpp"
#include "aidge/operator/BatchNorm.hpp"
#include "aidge/operator/BitShift.hpp"
#include "aidge/operator/Clip.hpp"
#include "aidge/operator/Concat.hpp"
#include "aidge/operator/Conv.hpp"
#include "aidge/operator/ConvDepthWise.hpp"
......
/********************************************************************************
* 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_CLIP_H_
#define AIDGE_CORE_OPERATOR_CLIP_H_
#include <memory>
#include <vector>
#include <limits>
#include "aidge/backend/OperatorImpl.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 {
enum class ClipAttr { Min, Max };
class Clip_Op : public OperatorTensor,
public Registrable<Clip_Op, std::string, std::function<std::shared_ptr<OperatorImpl>(const Clip_Op&)>> {
public:
static const std::string Type;
private:
using Attributes_ = StaticAttributes<ClipAttr, float, float>;
template <ClipAttr e> using attr = typename Attributes_::template attr<e>;
const std::shared_ptr<Attributes_> mAttributes;
public:
Clip_Op() = delete;
Clip_Op(float min,float max) :
OperatorTensor(Type, {InputCategory::Data,InputCategory::OptionalData,InputCategory::OptionalData}, 1),
mAttributes(std::make_shared<Attributes_>(attr<ClipAttr::Min>(min), attr<ClipAttr::Max>(max)))
{}
/**
* @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.
*/
Clip_Op(const Clip_Op& op)
: OperatorTensor(op),
mAttributes(op.mAttributes)
{
if (op.mImpl){
SET_IMPL_MACRO(Clip_Op, *this, op.backend());
}else{
mImpl = nullptr;
}
}
/**
* @brief Clone the operator using its copy-constructor.
* @see Operator::Clip_Op
*/
std::shared_ptr<Operator> clone() const override
{
return std::make_shared<Clip_Op>(*this);
}
bool dimsForwarded() const override final;
bool forwardDims(bool allowDataDependency = false) override final;
/**
* @brief Setter to specify the backend to use
*/
void setBackend(const std::string& name, DeviceIdx_t device = 0) override final;
inline std::shared_ptr<Attributes> attributes() const override { return mAttributes; }
/**
* @brief Getter and Setter for min attribute value
* @return float&
*/
inline float& min() const noexcept { return mAttributes->getAttr<ClipAttr::Min>(); }
/**
* @brief Getter and Setter for max attribute value
* @return float&
*/
inline float& max() const noexcept { return mAttributes->getAttr<ClipAttr::Max>(); }
std::set<std::string> getAvailableBackends() const override;
static const std::vector<std::string> getInputsName(){
return {"data_input","min_empty_tensor","max_empty_tensor"};
}
static const std::vector<std::string> getOutputsName(){
return {"data_output"};
}
};
/**
* @brief The Clip operator is a tensor operator that performs a clipping operation on tensor elements.
This class allows limiting tensor values to a specified range, defined by the min
and max parameters (Tensors of empty shapes). Values outside this range are replaced by the corresponding limit values
When ‘min’ is greater than ‘max’, the clip operator sets all the ‘input’ values to the value of ‘max’
* @param[in] name Name of the node
* @param[in] min Min clip value as attribute
* @param[in] max Max clip value as attribute
* @return std::shared_ptr<Node>
*/
std::shared_ptr<Aidge::Node> Clip(
const std::string &name = "",
float min = std::numeric_limits<float>::lowest(),
float max = std::numeric_limits<float>::max()
);
}
namespace {
template <>
const char* const EnumStrings<Aidge::ClipAttr>::data[]
= {"min", "max"};
}
#endif /* AIDGE_CORE_OPERATOR_CLIP_H_ */
/********************************************************************************
* 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 "aidge/data/Tensor.hpp"
#include "aidge/operator/Clip.hpp"
#include "aidge/operator/OperatorTensor.hpp"
#include "aidge/backend/OperatorImpl.hpp"
#include "aidge/utils/Types.h"
namespace py = pybind11;
namespace Aidge {
void init_Clip(py::module& m) {
py::class_<Clip_Op, std::shared_ptr<Clip_Op>, OperatorTensor>(m, "ClipOp", py::multiple_inheritance())
.def(py::init<float, float>(), py::arg("min") = std::numeric_limits<float>::lowest(), py::arg("max") = std::numeric_limits<float>::max())
.def_static("get_inputs_name", &Clip_Op::getInputsName)
.def_static("get_outputs_name", &Clip_Op::getOutputsName)
.def("min",&Clip_Op::min,py::return_value_policy::reference_internal)
.def("max",&Clip_Op::max,py::return_value_policy::reference_internal);
declare_registrable<Clip_Op>(m, "ClipOp");
m.def("Clip", &Clip,py::arg("name") = "",
py::arg("min")= std::numeric_limits<float>::lowest(),
py::arg("max")= std::numeric_limits<float>::max(),
R"mydelimiter(ClipOp is a tensor operator that performs a clipping operation on tensor elements.
This class allows limiting tensor values to a specified range, defined by the min
and max parameters. Values outside this range are replaced by the corresponding
limit values. When 'min' is greater than 'max', the clip operator sets all the 'input' values to the value of 'max'
:param min: minimum clipping value.
:type min: float
:param max: maximum clipping value.
:type max: float
:param name: name of the node.
)mydelimiter");
}
} // namespace Aidge
......@@ -35,6 +35,7 @@ void init_Atan(py::module&);
void init_AvgPooling(py::module&);
void init_BatchNorm(py::module&);
void init_BitShift(py::module&);
void init_Clip(py::module&);
void init_Concat(py::module&);
void init_ConstantOfShape(py::module&);
void init_Conv(py::module&);
......@@ -120,6 +121,7 @@ void init_Aidge(py::module& m) {
init_AvgPooling(m);
init_BatchNorm(m);
init_BitShift(m);
init_Clip(m);
init_Concat(m);
init_Conv(m);
init_ConvDepthWise(m);
......
/********************************************************************************
* 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/Clip.hpp"
#include <memory>
#include <string>
#include "aidge/data/Tensor.hpp"
#include "aidge/utils/Registrar.hpp"
#include "aidge/utils/Types.h"
#include "aidge/operator/Clip.hpp"
const std::string Aidge::Clip_Op::Type = "Clip";
bool Aidge::Clip_Op::dimsForwarded() const {
if ((getInput(1) && !getInput(1)->undefined())
|| (getInput(2) && !getInput(2)->undefined()))
{
// output dims are data dependent
return false;
}
return OperatorTensor::dimsForwarded();
}
bool Aidge::Clip_Op::forwardDims(bool allowDataDependency)
{
if (getInput(1) )
{
if( this->min() != std::numeric_limits<float>::lowest())
{
Log::notice("{} : ignoring non-empty min attribute because input#1 "
"take precedence",
type());
}
if (!allowDataDependency) {
Log::warn("{} : unable to forwardDims() because output dims are data "
"dependent on input#1",
type());
return false;
}
std::shared_ptr<Tensor> fallback;
const auto& minV = mInputs[1]->refCastFrom(fallback, NativeType<float>::type, "cpu");
this->min() = *(static_cast<float*>(minV.getImpl()->hostPtr()));
}
if (getInput(2))
{
if( this->max() != std::numeric_limits<float>::max())
{
Log::notice("{} : ignoring non-empty max attribute because input#2 "
"take precedence",
type());
}
if (!allowDataDependency) {
Log::warn("{} : unable to forwardDims() because output dims are data "
"dependent on input#2",
type());
return false;
}
std::shared_ptr<Tensor> fallback;
const auto& maxV = mInputs[2]->refCastFrom(fallback, NativeType<float>::type, "cpu");
this->max() = *(static_cast<float*>(maxV.getImpl()->hostPtr()));
}
if (!inputsAssociated(false)) {
return false;
}
else if ((getInput(1) && !getInput(1)->empty()) || (getInput(2) && !getInput(2)->empty()))
{
AIDGE_THROW_OR_ABORT(std::runtime_error,"Expected Input#1 and Input#2 to be scalar (Tensors of empty shapes)");
}
mOutputs[0] -> resize(getInput(0)->dims());
return true;
}
void Aidge::Clip_Op::setBackend(const std::string& name, Aidge::DeviceIdx_t device) {
mImpl = Registrar<Clip_Op>::create(name)(*this);
mOutputs[0]->setBackend(name, device);
}
std::set<std::string> Aidge::Clip_Op::getAvailableBackends() const {
return Registrar<Clip_Op>::getKeys();
}
std::shared_ptr<Aidge::Node> Aidge::Clip(const std::string &name,float min,float max)
{
return std::make_shared<Node>(std::make_shared<Clip_Op>(min, max), name);
}
\ No newline at end of file
/********************************************************************************
* 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 <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators_random.hpp>
#include <cstddef> // std::size_t
#include <memory>
#include <random> // std::mt19937, std::uniform_int_distribution
#include <vector>
#include "aidge/data/Tensor.hpp"
#include "aidge/operator/Clip.hpp"
#include "aidge/operator/OperatorTensor.hpp"
namespace Aidge {
TEST_CASE("[core/operator] Clip_Op(forwardDims)", "[Clip][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 Clip Operator
std::shared_ptr<Node> myClip = Clip();
auto op = std::static_pointer_cast<OperatorTensor>(myClip -> getOperator());
// Input tensor
std::shared_ptr<Tensor> T0 = std::make_shared<Tensor>(Array2D<int,2,2> {
{
{1, 2},
{3, 4}
}
});
op -> associateInput(0,T0);
// Tensor representign Min value
std::shared_ptr<Tensor> Tmin = std::make_shared<Tensor>(2.0);
op -> associateInput(1,Tmin);
// Tensor representign Max value
std::shared_ptr<Tensor> Tmax = std::make_shared<Tensor>(4.0);
op -> associateInput(2,Tmax);
/**
* @todo Special case: scalar not handled yet by
* ``OperatorTensor::forwardDims()``
*/
SECTION("Scalar") {
//We set every Input as a Scalar
T0->resize({});
Tmin->resize({});
Tmax->resize({});
REQUIRE_NOTHROW(op->forwardDims(true));
REQUIRE((op->getOutput(0)->dims() == std::vector<std::size_t>()));
}
SECTION("Normal Input") {
// a scalar is compatible with any other Tensor
// input_0
std::shared_ptr<Tensor> T0 = std::make_shared<Tensor>(Array2D<int,2,2> {
{
{1, 2},
{3, 4}
}
});
const std::size_t nb_dims = nbDimsDist(gen);
std::vector<std::size_t> dims(nb_dims);
for (std::size_t i = 0; i < nb_dims; ++i)
{
dims[i] = dimsDist(gen);
}
T0->resize(dims);
op->associateInput(0,T0);
REQUIRE_NOTHROW(op->forwardDims(true));
REQUIRE((op->getOutput(0)->dims()) == dims);
}
SECTION("Min and max attributes ")
{
std::shared_ptr<Node> clip_attr = Clip("",-1.0,2.0);
auto opc = std::static_pointer_cast<OperatorTensor>(clip_attr -> getOperator());
std::shared_ptr<Tensor> T0 = std::make_shared<Tensor>();
opc -> associateInput(0,T0);
std::shared_ptr<Tensor> Tmin = std::make_shared<Tensor>(7);
opc-> associateInput(1,Tmin);
std::shared_ptr<Tensor> Tmax = std::make_shared<Tensor>(4);
opc -> associateInput(2,Tmax);
REQUIRE_NOTHROW(opc->forwardDims(true));
REQUIRE((opc->getOutput(0)->dims() == std::vector<std::size_t>()));
}
SECTION("Min and max attributes (No Input for min and max)")
{
std::shared_ptr<Node> clip = Clip("",-1.0,2.0);
auto opcl = std::static_pointer_cast<OperatorTensor>(clip -> getOperator());
std::shared_ptr<Tensor> T0 = std::make_shared<Tensor>();
opcl -> associateInput(0,T0);
REQUIRE_NOTHROW(opcl->forwardDims());
REQUIRE((opcl->getOutput(0)->dims() == std::vector<std::size_t>()));
}
}
} // namespace Aidge
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment