--- /dev/null
+#include "PoolingSpec.h"
+
+#include <cassert>
+
+PoolingSpec::PoolingSpec(const ::caffe::PoolingParameter ¶m) : _param(param)
+{
+ // DO NOTHING
+}
+
+uint32_t PoolingSpec::window_height(void) const
+{
+ // NOTE Global pooling is not supported, yet
+ // TODO Support global pooling
+ assert(!_param.global_pooling());
+
+ if (_param.has_kernel_h())
+ {
+ return _param.kernel_h();
+ }
+
+ assert(_param.has_kernel_size());
+ return _param.kernel_size();
+}
+
+uint32_t PoolingSpec::window_width(void) const
+{
+ // NOTE Global pooling is not supported, yet
+ // TODO Support global pooling
+ assert(!_param.global_pooling());
+
+ if (_param.has_kernel_w())
+ {
+ return _param.kernel_w();
+ }
+
+ assert(_param.has_kernel_size());
+ return _param.kernel_size();
+}
+
+nncc::core::ADT::tensor::Shape PoolingSpec::ofm_shape(void) const
+{
+ nncc::core::ADT::tensor::Shape res;
+
+ // NOTE Caffe supports only pooling over rank-4 tensor
+ assert(_ifm_shape.rank() == 4);
+ res.resize(4);
+
+ // N (= the number of bacths) SHOULD be same
+ res.dim(0) = _ifm_shape.dim(0);
+ // C (= the number of chaanels) SHOULD be same
+ res.dim(1) = _ifm_shape.dim(1);
+
+ // H and W are derived from IFM, Window, and Padding
+ res.dim(2) = _ifm_shape.dim(2) - window_height() + 1;
+ res.dim(3) = _ifm_shape.dim(3) - window_width() + 1;
+ return res;
+}
--- /dev/null
+#ifndef __POOLING_SPEC_H__
+#define __POOLING_SPEC_H__
+
+#include <caffe/proto/caffe.pb.h>
+
+#include <nncc/core/ADT/tensor/Shape.h>
+
+class PoolingSpec
+{
+public:
+ PoolingSpec(const ::caffe::PoolingParameter ¶m);
+
+public:
+ const nncc::core::ADT::tensor::Shape &ifm_shape(void) const { return _ifm_shape; }
+ void ifm_shape(const nncc::core::ADT::tensor::Shape &shape) { _ifm_shape = shape; }
+
+public:
+ uint32_t window_height(void) const;
+ uint32_t window_width(void) const;
+
+public:
+ nncc::core::ADT::tensor::Shape ofm_shape(void) const;
+
+private:
+ const ::caffe::PoolingParameter &_param;
+ nncc::core::ADT::tensor::Shape _ifm_shape;
+};
+
+#endif // __POOLING_SPEC_H__
--- /dev/null
+#include "PoolingSpec.h"
+#include "Importer.h"
+
+#include <nncc/core/ADT/tensor/Shape.h>
+#include <nncc/foundation/Memory.h>
+
+#include <caffe/net.hpp>
+
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+#include <google/protobuf/text_format.h>
+
+#include <sstream>
+#include <stdexcept>
+
+#include <gtest/gtest.h>
+
+using namespace nncc::core::ADT;
+
+using nncc::foundation::make_unique;
+
+#define STRING(content) #content
+
+namespace
+{
+
+class SequentialBuilder
+{
+public:
+ SequentialBuilder(::caffe::NetParameter *net) : _net{net}
+ {
+ // DO NOTHING
+ }
+
+public:
+ bool addLayer(const std::string &prototxt)
+ {
+ auto layer = _net->add_layer();
+ std::stringstream ss{prototxt};
+ ::google::protobuf::io::IstreamInputStream iis{&ss};
+ return google::protobuf::TextFormat::Parse(&iis, layer);
+ }
+
+ bool addInputLayer(const tensor::Shape &shape)
+ {
+ auto param = new ::caffe::InputParameter;
+ {
+ auto s = param->add_shape();
+ for (uint32_t n = 0; n < shape.rank(); ++n)
+ {
+ s->add_dim(shape.dim(n));
+ }
+ }
+
+ auto layer = _net->add_layer();
+
+ layer->set_name("data");
+ layer->set_type("Input");
+ layer->add_top("data");
+ layer->set_allocated_input_param(param);
+
+ return true;
+ }
+
+private:
+ ::caffe::NetParameter *_net;
+};
+
+} // namespace
+
+namespace
+{
+
+class PoolingSpecTest : public ::testing::Test
+{
+protected:
+ tensor::Shape as_tensor_shape(const std::vector<int> &dims)
+ {
+ const uint32_t rank = dims.size();
+
+ tensor::Shape res;
+
+ res.resize(rank);
+
+ for (uint32_t axis = 0; axis < rank; ++axis)
+ {
+ res.dim(axis) = dims.at(axis);
+ }
+
+ return res;
+ }
+};
+} // namespace
+
+TEST_F(PoolingSpecTest, ifm_shape)
+{
+ ::caffe::PoolingParameter param;
+ PoolingSpec spec{param};
+
+ const tensor::Shape ifm_shape{1, 3, 244, 244};
+
+ spec.ifm_shape(ifm_shape);
+
+ ASSERT_EQ(spec.ifm_shape(), ifm_shape);
+}
+
+namespace
+{
+} // namespace
+
+TEST_F(PoolingSpecTest, kernel_size_same_for_all)
+{
+ const tensor::Shape ifm_shape{1, 3, 16, 16};
+
+ ::caffe::NetParameter param;
+ {
+ SequentialBuilder builder{¶m};
+
+ builder.addInputLayer(ifm_shape);
+
+ // clang-format off
+ const char *prototxt = STRING(
+ name : "pool"
+ type : "Pooling"
+ bottom : "data"
+ top : "pool"
+ pooling_param { kernel_size : 3 }
+ );
+ // clang-format on
+
+ builder.addLayer(prototxt);
+ }
+
+ ::caffe::Net<float> net{param};
+
+ PoolingSpec spec{param.layer(1).pooling_param()};
+
+ spec.ifm_shape(ifm_shape);
+
+ ASSERT_EQ(spec.window_height(), 3);
+ ASSERT_EQ(spec.window_width(), 3);
+
+ // Check 'ofm_shape'
+ {
+ auto expected = as_tensor_shape(net.blob_by_name("pool")->shape());
+ auto obtained = spec.ofm_shape();
+
+ ASSERT_EQ(expected, obtained);
+ }
+}