Skip to content

Commit

Permalink
[Op] Add Col2Im-15 implementation to Core (#24197)
Browse files Browse the repository at this point in the history
### Details:
 - Add Core implementation of `Col2Im-15`

### Related PRs
- #23947

### Tickets:
 - CVS-138918

---------

Co-authored-by: Michal Lukaszewski <michal.lukaszewski@intel.com>
  • Loading branch information
p-wysocki and mlukasze authored May 14, 2024
1 parent 7605265 commit 239b578
Show file tree
Hide file tree
Showing 8 changed files with 821 additions and 0 deletions.
54 changes: 54 additions & 0 deletions src/core/include/openvino/op/col2im.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (C) 2018-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include "openvino/op/op.hpp"

namespace ov {
namespace op {
namespace v15 {
/// \brief Operator combining sliding blocks into an image tensor
/// \ingroup ov_ops_cpp_api
class OPENVINO_API Col2Im : public ov::op::Op {
public:
OPENVINO_OP("Col2Im", "opset15", ov::op::Op);

Col2Im() = default;
/// \brief Constructs a Col2Im operation.
///
/// \param data Input tensor with data
/// \param output_size Shape of the spatial dimensions of the output image
/// \param kernel_size Size of the sliding blocks
/// \param strides Stride in the sliding blocks in the input spatial dimensions
/// \param dilations Local stride of the elements
/// \param pads_begin Paddings at the beginning of each spatial axis, if undefined no padding is applied
/// \param pads_end Paddings at the end of each spatial axis, if undefined no padding is applied
Col2Im(const Output<Node>& data,
const Output<Node>& output_size,
const Output<Node>& kernel_size,
const Strides& strides = Strides{1, 1},
const Strides& dilations = Strides{1, 1},
const Shape& pads_begin = Shape{0, 0},
const Shape& pads_end = Shape{0, 0});

bool visit_attributes(AttributeVisitor& visitor) override;
void validate_and_infer_types() override;
std::shared_ptr<Node> clone_with_new_inputs(const OutputVector& new_args) const override;

const Strides& get_strides() const;
const Strides& get_dilations() const;
const Shape& get_pads_begin() const;
const Shape& get_pads_end() const;

private:
Strides m_strides;
Strides m_dilations;
Shape m_pads_begin;
Shape m_pads_end;
};

} // namespace v15
} // namespace op
} // namespace ov
1 change: 1 addition & 0 deletions src/core/include/openvino/op/ops.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "openvino/op/bucketize.hpp"
#include "openvino/op/ceiling.hpp"
#include "openvino/op/clamp.hpp"
#include "openvino/op/col2im.hpp"
#include "openvino/op/concat.hpp"
#include "openvino/op/constant.hpp"
#include "openvino/op/convert.hpp"
Expand Down
125 changes: 125 additions & 0 deletions src/core/shape_inference/include/col2im_shape_inference.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (C) 2018-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#pragma once

#include <cmath>

#include "openvino/op/col2im.hpp"
#include "utils.hpp"

namespace ov {
namespace op {
namespace v15 {
template <class TShape, class TRShape = result_shape_t<TShape>>
std::vector<TRShape> shape_infer(const Col2Im* op,
const std::vector<TShape>& input_shapes,
const ITensorAccessor& tensor_accessor = make_tensor_accessor()) {
NODE_VALIDATION_CHECK(op, input_shapes.size() == 3);

const auto& data_shape = input_shapes[0];
const auto& output_size_shape = input_shapes[1];
const auto& kernel_shape = input_shapes[2];

NODE_SHAPE_INFER_CHECK(op,
input_shapes,
ov::util::is_rank_compatible_any_of(data_shape.rank(), {2, 3}),
"input data must be an unbatched 2D or a batched 3D input. Got: ",
data_shape);

const auto is_two_elem_1d = [](const TShape& shape) -> bool {
static const auto exp_shape = Shape{2};
return shape.rank().is_dynamic() || shape.to_shape() == exp_shape;
};

if (output_size_shape.rank().is_static()) {
NODE_SHAPE_INFER_CHECK(op,
input_shapes,
is_two_elem_1d(output_size_shape),
"output_size must be a 1D input of shape [2]. Got: ",
output_size_shape);
}

if (kernel_shape.rank().is_static()) {
NODE_SHAPE_INFER_CHECK(op,
input_shapes,
is_two_elem_1d(kernel_shape),
"kernel_size must be a 1D input of shape [2].");
}

if (data_shape.rank().is_static()) {
auto output_shapes = std::vector<TRShape>(1);
auto& output_shape = output_shapes[0];
const bool is_batched = data_shape.rank() == 3;
output_shape.resize(is_batched ? 4 : 3);
size_t idx = 0;

// output_shape: (N, C, H, W)
// ^
if (is_batched) {
output_shape[idx] = data_shape[0];
idx++;
}

// output_shape: (N, C, H, W)
// ^
const size_t C_idx = is_batched ? 1 : 0;
const auto kernel_val = ov::op::get_input_const_data_as<TRShape, int64_t>(op, 2, tensor_accessor);
if (kernel_val && data_shape.rank().is_static() && data_shape[C_idx].is_static()) {
const auto& dividend = data_shape[C_idx].get_length();
const auto divisor = ((*kernel_val)[0] * (*kernel_val)[1]);
output_shape[idx] = dividend / divisor;

NODE_SHAPE_INFER_CHECK(op,
input_shapes,
dividend % divisor == 0,
"First non-batch dimension is not evenly divisible by Product(kernel_shape). Got: ",
data_shape[C_idx].get_length());
}

// output_shape: (N, C, H, W)
// ^ ^
if (const auto output_size_val =
ov::op::get_input_const_data_as_shape<TRShape, int64_t>(op, 1, tensor_accessor)) {
idx++;
output_shape[idx] = (*output_size_val)[0];
idx++;
output_shape[idx] = (*output_size_val)[1];
const size_t L_idx = is_batched ? 2 : 1;
if (data_shape.rank().is_static() && data_shape[L_idx].is_static()) {
constexpr size_t spatial_dims = 2;

const auto& pads_begin = op->get_pads_begin();
const auto& pads_end = op->get_pads_end();
const auto& strides = op->get_strides();
const auto& dilations = op->get_dilations();

if (kernel_val) {
using TVal = typename TShape::value_type::value_type;
TVal L_calculated = 1;
for (size_t d = 0; d < spatial_dims; ++d) {
L_calculated *= (((*output_size_val)[d].get_length() + pads_begin[d] + pads_end[d] -
dilations[d] * ((*kernel_val)[d] - 1) - 1) /
strides[d]) +
1;
}
const auto L = data_shape[L_idx].get_length();
NODE_SHAPE_INFER_CHECK(
op,
input_shapes,
L == L_calculated,
"For given inputs and parameters the total number of data blocks must be equal to " +
std::to_string(L_calculated) + ". Got: ",
L);
}
}
}
return output_shapes;
} else {
return {PartialShape::dynamic()};
}
}
} // namespace v15
} // namespace op
} // namespace ov
94 changes: 94 additions & 0 deletions src/core/src/op/col2im.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (C) 2018-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#include "openvino/op/col2im.hpp"

#include "col2im_shape_inference.hpp"
#include "itt.hpp"
#include "openvino/core/validation_util.hpp"
#include "openvino/op/op.hpp"

namespace ov {
namespace op {
namespace v15 {

Col2Im::Col2Im(const Output<Node>& data,
const Output<Node>& output_size,
const Output<Node>& kernel_size,
const Strides& strides,
const Strides& dilations,
const Shape& pads_begin,
const Shape& pads_end)
: Op({data, output_size, kernel_size}),
m_strides(strides),
m_dilations(dilations),
m_pads_begin(pads_begin),
m_pads_end(pads_end) {
constructor_validate_and_infer_types();
}

bool Col2Im::visit_attributes(ov::AttributeVisitor& visitor) {
OV_OP_SCOPE(v15_Col2Im_visit_attributes);
visitor.on_attribute("strides", m_strides);
visitor.on_attribute("dilations", m_dilations);
visitor.on_attribute("pads_begin", m_pads_begin);
visitor.on_attribute("pads_end", m_pads_end);
return true;
}

void Col2Im::validate_and_infer_types() {
OV_OP_SCOPE(v15_Col2Im_validate_and_infer_types);

const auto& data_element_type = get_input_element_type(0);
const auto& output_size_element_type = get_input_element_type(1);
const bool is_valid_output_size_type =
output_size_element_type == element::i32 || output_size_element_type == element::i64;
NODE_VALIDATION_CHECK(this,
is_valid_output_size_type,
"The element type of the output_size tensor must be i32 or i64 type. Got: ",
output_size_element_type);

const auto& kernel_size_element_type = get_input_element_type(2);
const bool is_valid_kernel_size_type =
kernel_size_element_type == element::i32 || kernel_size_element_type == element::i64;
NODE_VALIDATION_CHECK(this,
is_valid_kernel_size_type,
"The element type of the kernel_size tensor must be i32 or i64 type. Got: ",
kernel_size_element_type);

const auto output_shapes = shape_infer(this, ov::util::get_node_input_partial_shapes(*this));
set_output_type(0, data_element_type, output_shapes[0]);
}

std::shared_ptr<Node> Col2Im::clone_with_new_inputs(const ov::OutputVector& new_args) const {
OV_OP_SCOPE(v15_Col2Im_clone_with_new_inputs);
check_new_args_count(this, new_args);
return std::make_shared<Col2Im>(new_args.at(0),
new_args.at(1),
new_args.at(2),
m_strides,
m_dilations,
m_pads_begin,
m_pads_end);
}

const Strides& Col2Im::get_strides() const {
return m_strides;
}

const Strides& Col2Im::get_dilations() const {
return m_dilations;
}

const Shape& Col2Im::get_pads_begin() const {
return m_pads_begin;
}

const Shape& Col2Im::get_pads_end() const {
return m_pads_end;
}

} // namespace v15
} // namespace op
} // namespace ov
Loading

0 comments on commit 239b578

Please sign in to comment.