--- /dev/null
+file(GLOB_RECURSE SOURCES "src/*.cpp")
+file(GLOB_RECURSE TESTS "src/*.test.cpp")
+list(REMOVE_ITEM SOURCES ${TESTS})
+
+add_library(morph STATIC ${SOURCES})
+set_target_properties(morph PROPERTIES POSITION_INDEPENDENT_CODE ON)
+target_include_directories(morph PUBLIC include)
+target_link_libraries(morph PRIVATE nncc_common)
+target_link_libraries(morph PUBLIC nncc_core)
+
+nncc_find_package(GTest QUIET)
+
+if(NOT GTest_FOUND)
+ return()
+endif(NOT GTest_FOUND)
+
+add_executable(morph_test ${TESTS})
+target_link_libraries(morph_test morph)
+target_link_libraries(morph_test gtest_main)
+add_test(morph_test morph_test)
--- /dev/null
+# morph
+
+``morph`` is a collection of shape conversion routines for various NN frameworks, such as Caffe.
--- /dev/null
+#ifndef __MORPH_CAFFE_H__
+#define __MORPH_CAFFE_H__
+
+#include <nncc/core/ADT/tensor/Shape.h>
+#include <nncc/core/ADT/feature/Shape.h>
+#include <nncc/core/ADT/kernel/Shape.h>
+
+namespace morph
+{
+
+nncc::core::ADT::tensor::Shape as_tensor_shape(const nncc::core::ADT::feature::Shape &);
+nncc::core::ADT::tensor::Shape as_tensor_shape(const nncc::core::ADT::kernel::Shape &);
+
+nncc::core::ADT::feature::Shape as_feature_shape(const nncc::core::ADT::tensor::Shape &);
+nncc::core::ADT::kernel::Shape as_kernel_shape(const nncc::core::ADT::tensor::Shape &);
+
+} // namespace morph
+
+#endif // __MORPH_CAFFE_H__
--- /dev/null
+#include "morph/caffe.h"
+
+#include <cassert>
+
+using namespace nncc::core::ADT;
+
+namespace morph
+{
+
+tensor::Shape as_tensor_shape(const feature::Shape &shape)
+{
+ tensor::Shape res;
+
+ res.resize(4);
+ res.dim(0) = 1;
+ res.dim(1) = shape.depth();
+ res.dim(2) = shape.height();
+ res.dim(3) = shape.width();
+
+ return res;
+}
+
+tensor::Shape as_tensor_shape(const kernel::Shape &shape)
+{
+ tensor::Shape res;
+
+ res.resize(4);
+ res.dim(0) = shape.count();
+ res.dim(1) = shape.depth();
+ res.dim(2) = shape.height();
+ res.dim(3) = shape.width();
+
+ return res;
+}
+
+feature::Shape as_feature_shape(const tensor::Shape &shape)
+{
+ assert(shape.rank() == 4);
+ assert(shape.dim(0) == 1);
+ return feature::Shape{shape.dim(1), shape.dim(2), shape.dim(3)};
+}
+
+kernel::Shape as_kernel_shape(const tensor::Shape &shape)
+{
+ assert(shape.rank() == 4);
+ return kernel::Shape{shape.dim(0), shape.dim(1), shape.dim(2), shape.dim(3)};
+}
+
+} // namespace morph
--- /dev/null
+#include "morph/caffe.h"
+
+#include <gtest/gtest.h>
+
+using namespace nncc::core::ADT;
+
+TEST(MORPH_CAFFE, as_feature_shape)
+{
+ auto shape = morph::as_feature_shape(tensor::Shape{1, 3, 4, 5});
+
+ ASSERT_EQ(shape.depth(), 3);
+ ASSERT_EQ(shape.height(), 4);
+ ASSERT_EQ(shape.width(), 5);
+}
+
+TEST(MORPH_CAFFE, as_kernel_shape)
+{
+ auto shape = morph::as_kernel_shape(tensor::Shape{2, 3, 4, 5});
+
+ ASSERT_EQ(shape.count(), 2);
+ ASSERT_EQ(shape.depth(), 3);
+ ASSERT_EQ(shape.height(), 4);
+ ASSERT_EQ(shape.width(), 5);
+}
+
+TEST(MORPH_CAFFE, as_tensor_shape)
+{
+ // From feature::Shape
+ {
+ auto shape = morph::as_tensor_shape(feature::Shape{2, 3, 4});
+
+ ASSERT_EQ(shape.rank(), 4);
+ ASSERT_EQ(shape.dim(0), 1);
+ ASSERT_EQ(shape.dim(1), 2);
+ ASSERT_EQ(shape.dim(2), 3);
+ ASSERT_EQ(shape.dim(3), 4);
+ }
+
+ // From kernel::Shape
+ {
+ auto shape = morph::as_tensor_shape(kernel::Shape{2, 3, 4, 5});
+
+ ASSERT_EQ(shape.rank(), 4);
+ ASSERT_EQ(shape.dim(0), 2);
+ ASSERT_EQ(shape.dim(1), 3);
+ ASSERT_EQ(shape.dim(2), 4);
+ ASSERT_EQ(shape.dim(3), 5);
+ }
+}