/** * \file src/opr/test/imgproc.cpp * MegEngine is Licensed under the Apache License, Version 2.0 (the "License") * * Copyright (c) 2014-2020 Megvii Inc. All rights reserved. * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include "megbrain/opr/basic_arith.h" #include "megbrain/opr/imgproc.h" #include "megbrain/opr/tensor_manip.h" #include "dnn/legacy_checker.h" #include "megbrain/test/autocheck.h" #include "megbrain/test/helper.h" #include "megbrain/test/megdnn_helper.h" #include #include #include #include #include using namespace mgb; namespace { megdnn::thin_function warp_perspective_mat_gen( size_t N, size_t INP_H, size_t INP_W) { static std::mt19937 rng(next_rand_seed()); auto rand_real = [&](double lo, double hi) { return rng() / (std::mt19937::max() + 1.0) * (hi - lo) + lo; }; auto rand_real2 = [&](double range) { return rand_real(-range, range); }; auto gen = [N, INP_H, INP_W, rand_real, rand_real2](HostTensorND& mat) { auto ptr = mat.ptr(); for (size_t i = 0; i < N; ++i) { auto rot = rand_real(0, M_PI * 2), scale = rand_real(0.8, 1.2), sheer = rand_real(0.9, 1.1), dy = rand_real2(INP_H * 0.5), dx = rand_real2(INP_W * 0.5), ky = rand_real2(0.1 / INP_H), kx = rand_real2(0.1 / INP_W), kb = rand_real2(0.1) + 1; ptr[0] = ptr[4] = cos(rot) * scale; ptr[1] = -(ptr[3] = sin(rot) * scale); ptr[3] *= sheer; ptr[4] *= sheer; ptr[2] = dx; ptr[5] = dy; ptr[6] = kx; ptr[7] = ky; ptr[8] = kb; ptr += 9; } mgb_assert(ptr == mat.ptr() + mat.shape().total_nr_elems()); }; return gen; } } // namespace TEST(TestOprImgproc, WarpPerspective) { set_rand_seed(20190813); // a seed that can pass the test constexpr size_t INP_H = 6, INP_W = 4, N = 2, C = 3; using Checker = AutoOprChecker<2, 1>; TensorShape out_shp{N, C, 9, 10}; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { return {opr::WarpPerspective::make(inputs[0], inputs[1], TensorShape{out_shp.shape[2], out_shp.shape[3]})}; }; auto fwd = [&](Checker::NumOutArray &dest, Checker::NumInpArray inp) { auto opr = megdnn_naive_handle()->create_operator< megdnn::WarpPerspective>(); dest[0].resize(out_shp); opr->exec(inp[0]->as_megdnn(), inp[1]->as_megdnn(), dest[0].as_megdnn(), {}); }; auto dump_mat = [&](const Checker::NumInpArray &inp) -> std::string { std::ostringstream ostr; ostr << std::setprecision(3); auto &&mat = *inp[1]; mgb_assert(mat.shape().ndim == 3); auto ptr = mat.ptr(); for (size_t n = 0; n < mat.shape().shape[0]; ++ n) { ostr << "mat " << n << ":\n"; for (size_t i = 0; i < 3; ++ i) { for (size_t j = 0; j < 3; ++ j) { ostr << std::setw(10) << *(ptr ++); } ostr << '\n'; } } return ostr.str(); }; Checker::RunOptions opt; opt.numdiff_eps_single_inp[1] = 1e-5; opt.numdiff_max_err_single_inp[1] = 0.5; Checker(make_graph, fwd). set_input_generator(1, warp_perspective_mat_gen(N, INP_H, INP_W)). set_input_dump_on_error(dump_mat). run({TensorShape{N, C, 4, 5}, {N, 3, 3}}, opt). run({TensorShape{N, C, 6, 5}, {N, 3, 3}}, opt). run({TensorShape{N, C, 10, 9}, {N, 3, 3}}, opt); } TEST(TestOprImgproc, WarpPerspective_NCHW4) { set_rand_seed(19931102); constexpr size_t INP_H = 6, INP_W = 4, N = 2, C = 12; using Checker = AutoOprChecker<2, 1>; TensorShape out_shp{N, C / 4, 9, 10, 4}; opr::WarpPerspective::Param param; param.format = opr::WarpPerspective::Param::Format::NCHW4; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { auto x = opr::TypeCvt::make(inputs[0], dtype::QuantizedS8(0.01f)); auto y = opr::WarpPerspective::make(x, inputs[1], TensorShape{out_shp.shape[2], out_shp.shape[3]}, param); return {opr::TypeCvt::make(y, dtype::Float32())}; }; auto fwd = [&](Checker::NumOutArray &dest, Checker::NumInpArray inp) { auto opr = megdnn_naive_handle()->create_operator< megdnn::WarpPerspective>(); opr->param() = param; auto typecvt = megdnn_naive_handle()->create_operator(); HostTensorND host_x{CompNode::load("xpux"), inp[0]->shape(), dtype::QuantizedS8(0.01f)}; HostTensorND host_y{CompNode::load("xpux"), out_shp, dtype::QuantizedS8(0.01f)}; typecvt->exec(inp[0]->as_megdnn(), host_x.as_megdnn()); dest[0].resize(out_shp); opr->exec(host_x.as_megdnn(), inp[1]->as_megdnn(), host_y.as_megdnn(), {}); typecvt->exec(host_y.as_megdnn(), dest[0].as_megdnn()); }; auto dump_mat = [&](const Checker::NumInpArray &inp) -> std::string { std::ostringstream ostr; ostr << std::setprecision(3); auto &&mat = *inp[1]; mgb_assert(mat.shape().ndim == 3); auto ptr = mat.ptr(); for (size_t n = 0; n < mat.shape().shape[0]; ++ n) { ostr << "mat " << n << ":\n"; for (size_t i = 0; i < 3; ++ i) { for (size_t j = 0; j < 3; ++ j) { ostr << std::setw(10) << *(ptr ++); } ostr << '\n'; } } return ostr.str(); }; Checker::RunOptions opt; opt.outputs_max_err = 2e-2; Checker(make_graph, fwd). disable_grad_check(). set_input_generator(1, warp_perspective_mat_gen(N, INP_H, INP_W)). set_input_dump_on_error(dump_mat). run({TensorShape{N, C / 4, 4, 5, 4}, {N, 3, 3}}, opt). run({TensorShape{N, C / 4, 6, 5, 4}, {N, 3, 3}}, opt). run({TensorShape{N, C / 4, 10, 9, 4}, {N, 3, 3}}, opt); } TEST(TestOprImgproc, WarpPerspectiveWithMatIdx) { constexpr size_t INP_H = 13, INP_W = 9, N_MAT = 23, N_SRC = 5, C = 3; std::mt19937 rng(next_rand_seed()); auto rand_real = [&](double lo, double hi) { return rng() / (std::mt19937::max() + 1.0) * (hi - lo) + lo; }; auto rand_real2 = [&](double range) { return rand_real(-range, range); }; using Checker = AutoOprChecker<3, 1>; TensorShape out_shp{N_MAT, C, 9, 10}; auto make_graph = [&](const Checker::SymInpArray& inputs) -> Checker::SymOutArray { return {opr::WarpPerspective::make( inputs[0], inputs[1], inputs[2], cg::var_from_tensor_shape( inputs[0], {out_shp.shape[2], out_shp.shape[3]}))}; }; auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) { auto opr = megdnn_naive_handle() ->create_operator(); dest[0].resize(out_shp); opr->exec(inp[0]->as_megdnn(), inp[1]->as_megdnn(), inp[2]->as_megdnn(), dest[0].as_megdnn(), {}); }; auto gen_mat = [&](HostTensorND& mat) { auto ptr = mat.ptr(); for (size_t i = 0; i < N_MAT; ++i) { auto rot = rand_real(0, M_PI * 2), scale = rand_real(0.8, 1.2), sheer = rand_real(0.9, 1.1), dy = rand_real2(INP_H * 0.5), dx = rand_real2(INP_W * 0.5), ky = rand_real2(0.1 / INP_H), kx = rand_real2(0.1 / INP_W), kb = rand_real2(0.1) + 1; ptr[0] = ptr[4] = cos(rot) * scale; ptr[1] = -(ptr[3] = sin(rot) * scale); ptr[3] *= sheer; ptr[4] *= sheer; ptr[2] = dx; ptr[5] = dy; ptr[6] = kx; ptr[7] = ky; ptr[8] = kb; ptr += 9; } mgb_assert(ptr == mat.ptr() + mat.shape().total_nr_elems()); }; HostTensorGenerator gen_mat_idx_rng{0, N_SRC}; auto gen_mat_idx = [&](HostTensorND& mat) { mat = *gen_mat_idx_rng(mat.shape()); }; Checker(make_graph, fwd) .set_input_generator(1, gen_mat) .set_input_generator(2, gen_mat_idx) .set_input_dtype(2, dtype::Int32{}) /*! it's hard to make the grad check success, the cuda implementation is grad sum */ .disable_grad_check() .set_input_allow_grad(2,false) .run({TensorShape{N_SRC, C, 4, 5}, {N_MAT, 3, 3}, {N_MAT}}) .run({TensorShape{N_SRC, C, 6, 5}, {N_MAT, 3, 3}, {N_MAT}}) .run({TensorShape{N_SRC, C, 22, 19}, {N_MAT, 3, 3}, {N_MAT}}); } TEST(TestOprImgproc, WarpPerspective_NHWC) { constexpr size_t INP_H = 6, INP_W = 4, N = 2, C = 3; std::mt19937 rng(next_rand_seed()); auto rand_real = [&](double lo, double hi) { return rng() / (std::mt19937::max() + 1.0) * (hi - lo) + lo; }; auto rand_real2 = [&](double range) { return rand_real(-range, range); }; using Checker = AutoOprChecker<2, 1>; TensorShape out_shp{N, 9, 10, C}; opr::WarpPerspective::Param param; param.format = opr::WarpPerspective::Param::Format::NHWC; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { return {opr::WarpPerspective::make(inputs[0], inputs[1], TensorShape{out_shp.shape[1], out_shp.shape[2]}, param)}; }; auto fwd = [&](Checker::NumOutArray &dest, Checker::NumInpArray inp) { auto opr = megdnn_naive_handle()->create_operator< megdnn::WarpPerspective>(); dest[0].resize(out_shp); opr->param() = param; opr->exec(inp[0]->as_megdnn(), inp[1]->as_megdnn(), dest[0].as_megdnn(), {}); }; auto gen_mat = [&](HostTensorND &mat) { auto ptr = mat.ptr(); for (size_t i = 0; i < N; ++ i) { auto rot = rand_real(0, M_PI * 2), scale = rand_real(0.8, 1.2), sheer = rand_real(0.9, 1.1), dy = rand_real2(INP_H * 0.5), dx = rand_real2(INP_W * 0.5), ky = rand_real2(0.1 / INP_H), kx = rand_real2(0.1 / INP_W), kb = rand_real2(0.1) + 1; ptr[0] = ptr[4] = cos(rot) * scale; ptr[1] = -(ptr[3] = sin(rot) * scale); ptr[3] *= sheer; ptr[4] *= sheer; ptr[2] = dx; ptr[5] = dy; ptr[6] = kx; ptr[7] = ky; ptr[8] = kb; ptr += 9; } mgb_assert(ptr == mat.ptr() + mat.shape().total_nr_elems()); }; auto dump_mat = [&](const Checker::NumInpArray &inp) -> std::string { std::ostringstream ostr; ostr << std::setprecision(3); auto &&mat = *inp[1]; mgb_assert(mat.shape().ndim == 3); auto ptr = mat.ptr(); for (size_t n = 0; n < mat.shape().shape[0]; ++ n) { ostr << "mat " << n << ":\n"; for (size_t i = 0; i < 3; ++ i) { for (size_t j = 0; j < 3; ++ j) { ostr << std::setw(10) << *(ptr ++); } ostr << '\n'; } } return ostr.str(); }; Checker::RunOptions opt; opt.outputs_max_err = 0.1; // cuda NHWC impl is different from naive opt.numdiff_eps_single_inp[1] = 1e-5; opt.numdiff_max_err_single_inp[1] = 0.5; Checker(make_graph, fwd). set_input_generator(1, gen_mat). set_output_allow_grad(0, false). set_input_dump_on_error(dump_mat). run({TensorShape{N, 4, 5, C}, {N, 3, 3}}, opt). run({TensorShape{N, 6, 5, C}, {N, 3, 3}}, opt). run({TensorShape{N, 10, 9, C}, {N, 3, 3}}, opt); } TEST(TestOprImgproc, RotateForward) { constexpr size_t N = 2, C = 3; opr::Rotate::Param param; using Checker = AutoOprChecker<1, 1>; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { return {opr::Rotate::make(inputs[0], param)}; }; auto fwd = [&](Checker::NumOutArray &dest, Checker::NumInpArray inp) { auto out_shape = inp[0]->shape(); std::swap(out_shape[1], out_shape[2]); dest[0].resize(out_shape); auto opr = megdnn_naive_handle()->create_operator(); opr->param() = param; opr->exec(inp[0]->as_megdnn(), dest[0].as_megdnn(), {}); }; Checker::RunOptions opt; Checker(make_graph, fwd, CompNode::load("cpu1")). set_output_allow_grad(0, false). run({TensorShape{N, 4, 5, C}}, opt). run({TensorShape{N, 6, 5, C}}, opt). run({TensorShape{N, 10, 9, C}}, opt); } TEST(TestOprImgproc, CvtColorForward) { constexpr size_t N = 2, C = 3; opr::CvtColor::Param param; using Checker = AutoOprChecker<1, 1>; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { return {opr::CvtColor::make(inputs[0], param)}; }; auto fwd = [&](Checker::NumOutArray &dest, Checker::NumInpArray inp) { TensorLayout out_layout; auto opr = megdnn_naive_handle()->create_operator(); opr->param() = param; opr->deduce_layout(inp[0]->layout(), out_layout); dest[0].resize(out_layout); opr->exec(inp[0]->as_megdnn(), dest[0].as_megdnn(), {}); }; Checker::RunOptions opt; Checker(make_graph, fwd, CompNode::load("cpu1")). set_output_allow_grad(0, false). run({TensorShape{N, 4, 5, C}}, opt). run({TensorShape{N, 6, 5, C}}, opt). run({TensorShape{N, 10, 9, C}}, opt); } TEST(TestOprImgproc, GaussianBlurForward) { constexpr size_t N = 2, C = 3; opr::GaussianBlur::Param param; param.kernel_height = param.kernel_width = 5; using Checker = AutoOprChecker<1, 1>; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { return {opr::GaussianBlur::make(inputs[0], param)}; }; auto fwd = [&](Checker::NumOutArray &dest, Checker::NumInpArray inp) { TensorLayout out_layout; auto opr = megdnn_naive_handle()->create_operator(); opr->param() = param; opr->deduce_layout(inp[0]->layout(), out_layout); dest[0].resize(out_layout); opr->exec(inp[0]->as_megdnn(), dest[0].as_megdnn(), {}); }; Checker::RunOptions opt; Checker(make_graph, fwd, CompNode::load("cpu1")). set_output_allow_grad(0, false). run({TensorShape{N, 4, 5, C}}, opt). run({TensorShape{N, 6, 5, C}}, opt). run({TensorShape{N, 10, 9, C}}, opt); } TEST(TestOprImgproc, ResizeForward) { constexpr size_t N = 2, C = 3; opr::Resize::Param param; using Checker = AutoOprChecker<1, 1>; TensorShape out_shp{N, 9, 10, C}; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { return {opr::Resize::make(inputs[0], TensorShape{out_shp.shape[1], out_shp.shape[2]}, param)}; }; auto fwd = [&](Checker::NumOutArray &dest, Checker::NumInpArray inp) { auto opr = megdnn_naive_handle()->create_operator(); opr->param() = param; dest[0].resize(out_shp); opr->exec(inp[0]->as_megdnn(), dest[0].as_megdnn(), {}); }; Checker::RunOptions opt; Checker(make_graph, fwd, CompNode::load("cpu1")). set_output_allow_grad(0, false). run({TensorShape{N, 4, 5, C}}, opt). run({TensorShape{N, 6, 5, C}}, opt). run({TensorShape{N, 10, 9, C}}, opt); } TEST(TestOprImgproc, ResizeForward_NCHW) { constexpr size_t N = 2, C = 8; opr::Resize::Param param; using Checker = AutoOprChecker<1, 1>; TensorShape out_shp{N, C, 9, 10}; param.format = opr::Resize::Param::Format::NCHW; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { return {opr::Resize::make(inputs[0], TensorShape{out_shp.shape[2], out_shp.shape[3]}, param)}; }; auto fwd = [&](Checker::NumOutArray &dest, Checker::NumInpArray inp) { auto opr = megdnn_naive_handle()->create_operator(); opr->param() = param; dest[0].resize(out_shp); opr->exec(inp[0]->as_megdnn(), dest[0].as_megdnn(), {}); }; Checker::RunOptions opt; Checker(make_graph, fwd) .run({TensorShape{N, C, 4, 5}}, opt) .run({TensorShape{N, C, 6, 5}}, opt) .run({TensorShape{N, C, 10, 9}}, opt); } TEST(TestOprImgproc, ResizeForward_NCHW_NonContiguous) { opr::Resize::Param param; param.format = opr::Resize::Param::Format::NCHW; using Checker = AutoOprChecker<1, 2>; SymbolVar sub, sub_rev, input; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { input = inputs[0]; auto graph = input.node()->owner_graph(); auto cn = input.node()->comp_node(); auto tshp = SymbolVar::make_scalar(3, *graph, cn).broadcast({2}); auto zero = SymbolVar::make_scalar(0, *graph, cn); auto one = SymbolVar::make_scalar(1, *graph, cn); auto minus_one = SymbolVar::make_scalar(-1, *graph, cn); auto src_h = opr::GetVarShape::make(input, 2); auto src_w = opr::GetVarShape::make(input, 3); sub = opr::Subtensor::make(input, { opr::Subtensor::AxisIndexer::make_interval( 2, one, src_h - 1, None), opr::Subtensor::AxisIndexer::make_interval( 3, one, src_w - 1, None)}); sub_rev = opr::Subtensor::make(input, { opr::Subtensor::AxisIndexer::make_interval( 2, src_h - 2, zero, minus_one), opr::Subtensor::AxisIndexer::make_interval( 3, src_w - 2, zero, minus_one)}); auto dst = opr::Resize::make(sub, tshp, param); auto dst_rev = opr::Resize::make(sub_rev, tshp, param); return {dst, dst_rev}; }; auto fwd = [&](Checker::NumOutArray &out, Checker::NumInpArray in) { auto cn = in[0]->comp_node(); TensorShape in_shp = in[0]->shape(); TensorShape sub_shp{2, 3, in_shp[2] - 2, in_shp[3] - 2}; auto sub = std::make_shared(cn, sub_shp); auto sub_rev = std::make_shared(cn, sub_shp); float* in_ptr = in[0]->ptr(); const ptrdiff_t* in_stride = in[0]->layout().stride; float* sub_ptr = sub->ptr(); float* sub_rev_ptr = sub_rev->ptr(); // get subtensor manually and make it contiguous for (size_t n = 0; n < sub_shp[0]; ++ n) for (size_t c = 0; c < sub_shp[1]; ++ c) for (size_t h = 0; h < sub_shp[2]; ++ h) for (size_t w = 0; w < sub_shp[3]; ++ w) { *(sub_ptr ++) = in_ptr[n * in_stride[0] + c * in_stride[1] + (h + 1) * in_stride[2] + (w + 1) * in_stride[3]]; *(sub_rev_ptr ++) = in_ptr[ n * in_stride[0] + c * in_stride[1] + (in_shp[2] - 2 - h) * in_stride[2] + (in_shp[3] - 2 - w) * in_stride[3]]; } auto opr = megdnn_naive_handle()->create_operator(); opr->param() = param; out[0].resize({2, 3, 3, 3}); out[1].resize({2, 3, 3, 3}); opr->exec(sub->as_megdnn(), out[0].as_megdnn(), {}); opr->exec(sub_rev->as_megdnn(), out[1].as_megdnn(), {}); }; Checker checker(make_graph, fwd); checker.disable_grad_check() .disable_graph_opt(); auto test = [&](TensorShape&& shape) { checker.run({shape}); auto inp_dev_ptr = static_cast(prev_dev_ptr(input)); ASSERT_EQ(inp_dev_ptr + shape[3] + 1, static_cast(prev_dev_ptr(sub))); ASSERT_EQ(inp_dev_ptr + (shape[2] - 1) * shape[3] - 2, static_cast(prev_dev_ptr(sub_rev))); }; test(TensorShape{2, 3, 4, 4}); test(TensorShape{2, 3, 5, 5}); test(TensorShape{2, 3, 6, 7}); } TEST(TestOprImgproc, ResizeForward_NCHW4) { constexpr size_t N = 2, C = 8; opr::Resize::Param param; using Checker = AutoOprChecker<1, 1>; TensorShape out_shp{N, C / 4, 9, 10, 4}; param.format = opr::Resize::Param::Format::NCHW4; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { auto x = opr::TypeCvt::make(inputs[0], dtype::QuantizedS8(0.01f)); auto y = opr::Resize::make(x, TensorShape{out_shp.shape[2], out_shp.shape[3]}, param); return {opr::TypeCvt::make(y, dtype::Float32())}; }; auto fwd = [&](Checker::NumOutArray& dest, Checker::NumInpArray inp) { auto opr = megdnn_naive_handle()->create_operator(); auto typecvt = megdnn_naive_handle()->create_operator(); HostTensorND host_x{CompNode::load("xpux"), inp[0]->shape(), dtype::QuantizedS8(0.01f)}; HostTensorND host_y{CompNode::load("xpux"), out_shp, dtype::QuantizedS8(0.01f)}; typecvt->exec(inp[0]->as_megdnn(), host_x.as_megdnn()); opr->param() = param; opr->exec(host_x.as_megdnn(), host_y.as_megdnn(), {}); dest[0].resize(out_shp); typecvt->exec(host_y.as_megdnn(), dest[0].as_megdnn()); }; Checker::RunOptions opt; opt.outputs_max_err = 2e-2; Checker(make_graph, fwd) .disable_grad_check() .run({TensorShape{N, C / 4, 4, 5, 4}}, opt) .run({TensorShape{N, C / 4, 6, 5, 4}}, opt) .run({TensorShape{N, C / 4, 10, 9, 4}}, opt); } TEST(TestOprImgproc, ResizeBackward) { opr::Resize::Param param; param.format = opr::Resize::Param::Format::NCHW; opr::test::BackwardChecker backward_checker( {{10, 8, 8, 4}, {10, 8, 4, 8}}, param, 1e-1, 1e-2); } TEST(TestOprImgproc, WarpAffineForward) { constexpr size_t INP_H = 6, INP_W = 4, N = 2, C = 3; opr::WarpAffine::Param param; using Checker = AutoOprChecker<2, 1>; TensorShape out_shp{N, 9, 10, C}; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { return {opr::WarpAffine::make(inputs[0], inputs[1], TensorShape{out_shp.shape[1], out_shp.shape[2]}, param)}; }; auto fwd = [&](Checker::NumOutArray &dest, Checker::NumInpArray inp) { auto opr = megdnn_naive_handle()->create_operator(); opr->param() = param; dest[0].resize(out_shp); opr->exec(inp[0]->as_megdnn(), inp[1]->as_megdnn(), dest[0].as_megdnn(), {}); }; std::mt19937 rng(next_rand_seed()); auto rand_real = [&](double lo, double hi) { return rng() / (std::mt19937::max() + 1.0) * (hi - lo) + lo; }; auto rand_real2 = [&](double range) { return rand_real(-range, range); }; auto gen_mat = [&](HostTensorND& mat) { auto ptr = mat.ptr(); for (size_t i = 0; i < N; ++ i) { auto rot = rand_real(0, M_PI * 2), scale = rand_real(0.8, 1.2), dy = rand_real2(INP_H * 0.5), dx = rand_real2(INP_W * 0.5); ptr[0] = cos(rot) * scale; ptr[1] = -(sin(rot) * scale); ptr[2] = dx; ptr[3] = sin(rot) * scale; ptr[4] = cos(rot) * scale; ptr[5] = dy; ptr += 6; } mgb_assert(ptr == mat.ptr() + mat.shape().total_nr_elems()); }; auto dump_mat = [&](const Checker::NumInpArray &inp) -> std::string { std::ostringstream ostr; ostr << std::setprecision(3); auto &&mat = *inp[1]; mgb_assert(mat.shape().ndim == 3); auto ptr = mat.ptr(); for (size_t n = 0; n < mat.shape().shape[0]; ++ n) { ostr << "mat " << n << ":\n"; for (size_t i = 0; i < 2; ++ i) { for (size_t j = 0; j < 3; ++ j) { ostr << std::setw(10) << *(ptr ++); } ostr << '\n'; } } return ostr.str(); }; Checker::RunOptions opt; opt.outputs_max_err = 0.08; Checker(make_graph, fwd, CompNode::load("cpu1")). set_input_generator(1, gen_mat). set_output_allow_grad(0, false). set_input_dump_on_error(dump_mat). run({TensorShape{N, 4, 5, C}, {N, 2, 3}}, opt). run({TensorShape{N, 6, 5, C}, {N, 2, 3}}, opt). run({TensorShape{N, 10, 9, C}, {N, 2, 3}}, opt); } TEST(TestOprImgproc, Remap_NCHW) { constexpr size_t N = 2, C = 8, OH = 10, OW = 10; opr::Remap::Param param; using Checker = AutoOprChecker<2, 1>; TensorShape out_shp{N, C, OH, OW}; param.format = opr::Remap::Param::Format::NCHW; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { return {opr::Remap::make(inputs[0], inputs[1], param)}; }; auto fwd = [&](Checker::NumOutArray &dest, Checker::NumInpArray inp) { auto opr = megdnn_naive_handle()->create_operator(); opr->param() = param; dest[0].resize(out_shp); opr->exec(inp[0]->as_megdnn(), inp[1]->as_megdnn(), dest[0].as_megdnn(), {}); }; std::mt19937 rng(next_rand_seed()); auto rand_real = [&](double lo, double hi) { auto real = rng() / (std::mt19937::max() + 1.0) * (hi - lo) + lo; if(std::abs(std::round(real) - real) <= 1e-2) return real + 1e-1; return real; }; auto rand_real2 = [&](double range) { return rand_real(-range, range); }; auto gen_mat = [&](HostTensorND& mat) { auto ptr = mat.ptr(); for (size_t i = 0; i < N; ++ i) { for(size_t j = 0; j < OH * OW * 2; j++) { //! undifferentiable when map is an integer ptr[j] = static_cast(rand_real2(20)); } ptr += OH * OW * 2; } mgb_assert(ptr == mat.ptr() + mat.shape().total_nr_elems()); }; Checker::RunOptions opt; Checker(make_graph, fwd, CompNode::load("cpu1")) .set_input_generator(1, gen_mat) .run({TensorShape{N, C, 3, 20}, TensorShape{N, OH, OW, 2}}, opt) .run({TensorShape{N, C, 6, 5}, TensorShape{N, OH, OW, 2}}, opt) .run({TensorShape{N, C, 20, 20}, TensorShape{N, OH, OW, 2}}, opt); } TEST(TestOprImgproc, Remap_NHWC) { constexpr size_t N = 2, C = 8; opr::Remap::Param param; using Checker = AutoOprChecker<2, 1>; TensorShape out_shp{N, 10, 10, C}; param.format = opr::Remap::Param::Format::NHWC; auto make_graph = [&](const Checker::SymInpArray &inputs) -> Checker::SymOutArray { return {opr::Remap::make(inputs[0], inputs[1], param)}; }; auto fwd = [&](Checker::NumOutArray &dest, Checker::NumInpArray inp) { auto opr = megdnn_naive_handle()->create_operator(); opr->param() = param; dest[0].resize(out_shp); opr->exec(inp[0]->as_megdnn(), inp[1]->as_megdnn(), dest[0].as_megdnn(), {}); }; Checker::RunOptions opt; Checker(make_graph, fwd, CompNode::load("cpu1")) .disable_grad_check() .run({TensorShape{N, 3, 20, C}, TensorShape{N, 10, 10, 2}}, opt) .run({TensorShape{N, 6, 5, C}, TensorShape{N, 10, 10, 2}}, opt) .run({TensorShape{N, 20, 20, C}, TensorShape{N, 10, 10, 2}}, opt); } // vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}}