chore: import upstream snapshot with attribution
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:14:16 +08:00
commit 8a852e4b4e
36502 changed files with 9277225 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("//tensorflow:tensorflow.default.bzl", "get_compatible_with_portable", "pybind_extension")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = [
"//visibility:public",
],
licenses = ["notice"],
)
cc_library(
name = "perception_ops",
srcs = [
"dense_image_warp.cc",
"max_pool_with_argmax.cc",
"max_unpooling_2d.cc",
"perception_ops.cc",
],
hdrs = [
"perception_ops.h",
],
compatible_with = get_compatible_with_portable(),
deps = [
"//tensorflow/lite:framework",
"//tensorflow/lite/c:common",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/kernels:kernel_util",
"//tensorflow/lite/kernels:padding",
"//tensorflow/lite/kernels/internal:common",
"//tensorflow/lite/kernels/internal:compatibility",
"//tensorflow/lite/kernels/internal:tensor",
"//tensorflow/lite/kernels/internal:types",
"@flatbuffers",
],
)
cc_test(
name = "perception_ops_test",
size = "small",
srcs = [
"dense_image_warp_test.cc",
"max_pool_with_argmax_test.cc",
"max_unpooling_2d_test.cc",
],
deps = [
":perception_ops",
"//tensorflow/lite:framework",
"//tensorflow/lite/core:framework_stable",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/kernels:test_main",
"//tensorflow/lite/kernels:test_util",
"//tensorflow/lite/schema:schema_fbs",
"@com_google_googletest//:gtest",
"@flatbuffers",
],
)
pybind_extension(
name = "pywrap_perception_ops",
srcs = [
"perception_ops_wrapper.cc",
],
hdrs = ["perception_ops.h"],
additional_exported_symbols = ["PerceptionOpsRegisterer"],
enable_stub_generation = True,
link_in_framework = True,
pytype_srcs = [
"pywrap_perception_ops.pyi",
],
deps = [
":perception_ops",
"//tensorflow/lite:mutable_op_resolver",
"//tensorflow/lite/c:common",
"@pybind11",
],
)
@@ -0,0 +1,154 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <algorithm>
#include <cmath>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/kernels/internal/compatibility.h"
#include "tensorflow/lite/kernels/internal/runtime_shape.h"
#include "tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tensorflow/lite/kernels/internal/types.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace ops {
namespace custom {
namespace dense_image_warp {
constexpr int kInputTensor = 0;
constexpr int kFlowTensor = 1;
constexpr int kOutputTensor = 0;
inline void DenseImageWarp(const RuntimeShape& input_shape,
const float* input_data,
const RuntimeShape& flow_shape,
const float* flow_data, float* output_data) {
const int batches = MatchingDim(input_shape, 0, flow_shape, 0);
const int height = MatchingDim(input_shape, 1, flow_shape, 1);
const int width = MatchingDim(input_shape, 2, flow_shape, 2);
const int channels = input_shape.Dims(3);
TFLITE_CHECK_EQ(flow_shape.Dims(3), 2);
// Max values to make sure the indexes are not out of bound.
const int max_floor_y = height - 2;
const int max_floor_x = width - 2;
for (int batch = 0; batch < batches; ++batch) {
for (int in_y = 0; in_y < height; ++in_y) {
for (int in_x = 0; in_x < width; ++in_x) {
float querry_point_y =
in_y - flow_data[Offset(flow_shape, batch, in_y, in_x, 0)];
float querry_point_x =
in_x - flow_data[Offset(flow_shape, batch, in_y, in_x, 1)];
int floor_y =
std::min(std::max(0, static_cast<int>(std::floor(querry_point_y))),
max_floor_y);
int floor_x =
std::min(std::max(0, static_cast<int>(std::floor(querry_point_x))),
max_floor_x);
float alpha_y =
std::min(std::max(0.0f, querry_point_y - floor_y), 1.0f);
float alpha_x =
std::min(std::max(0.0f, querry_point_x - floor_x), 1.0f);
for (int c = 0; c < channels; ++c) {
float top_left =
input_data[Offset(input_shape, batch, floor_y, floor_x, c)];
float top_right =
input_data[Offset(input_shape, batch, floor_y, floor_x + 1, c)];
float bottom_left =
input_data[Offset(input_shape, batch, floor_y + 1, floor_x, c)];
float bottom_right = input_data[Offset(input_shape, batch,
floor_y + 1, floor_x + 1, c)];
float interp_top = alpha_x * (top_right - top_left) + top_left;
float interp_bottom =
alpha_x * (bottom_right - bottom_left) + bottom_left;
float interp = alpha_y * (interp_bottom - interp_top) + interp_top;
output_data[Offset(input_shape, batch, in_y, in_x, c)] = interp;
}
}
}
}
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
// Check inputs and output.
TF_LITE_ENSURE_EQ(context, NumInputs(node), 2);
TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1);
TfLiteTensor* output = GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
const TfLiteTensor* input = GetInput(context, node, kInputTensor);
TF_LITE_ENSURE(context, input != nullptr);
const TfLiteTensor* flow = GetInput(context, node, kFlowTensor);
TF_LITE_ENSURE(context, flow != nullptr);
// Check types.
TF_LITE_ENSURE_TYPES_EQ(context, input->type, kTfLiteFloat32);
TF_LITE_ENSURE_TYPES_EQ(context, output->type, kTfLiteFloat32);
TF_LITE_ENSURE_TYPES_EQ(context, flow->type, kTfLiteFloat32);
// Check shapes. If input has shape of [b, h, w, c], flow must have shape of
// [b, h, w, 2].
TF_LITE_ENSURE_EQ(context, NumDimensions(flow), 4);
TF_LITE_ENSURE_EQ(context, NumDimensions(input), 4);
const RuntimeShape input_shape = GetTensorShape(input);
const RuntimeShape flow_shape = GetTensorShape(flow);
TF_LITE_ENSURE_EQ(context, input_shape.Dims(0), flow_shape.Dims(0));
TF_LITE_ENSURE_EQ(context, input_shape.Dims(1), flow_shape.Dims(1));
TF_LITE_ENSURE_EQ(context, input_shape.Dims(2), flow_shape.Dims(2));
TF_LITE_ENSURE_MSG(context, input_shape.Dims(1) >= 2,
"Image height must be at least 2.");
TF_LITE_ENSURE_MSG(context, input_shape.Dims(2) >= 2,
"Image width must be at least 2.");
TF_LITE_ENSURE_MSG(context, flow_shape.Dims(3) == 2,
"The last dimension of flow tensor must be 2.");
TfLiteIntArray* output_size = TfLiteIntArrayCopy(input->dims);
return context->ResizeTensor(context, output, output_size);
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
TfLiteTensor* output = GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
const TfLiteTensor* input = GetInput(context, node, kInputTensor);
TF_LITE_ENSURE(context, input != nullptr);
const TfLiteTensor* flow = GetInput(context, node, kFlowTensor);
TF_LITE_ENSURE(context, flow != nullptr);
DenseImageWarp(GetTensorShape(input), GetTensorData<float>(input),
GetTensorShape(flow), GetTensorData<float>(flow),
GetTensorData<float>(output));
return kTfLiteOk;
}
} // namespace dense_image_warp
TfLiteRegistration* RegisterDenseImageWarp() {
static TfLiteRegistration reg = {/*init=*/nullptr,
/*free=*/nullptr, dense_image_warp::Prepare,
dense_image_warp::Eval};
return &reg;
}
// Alias for selective build.
TfLiteRegistration* Register_DENSE_IMAGE_WARP() {
return RegisterDenseImageWarp();
}
} // namespace custom
} // namespace ops
} // namespace tflite
@@ -0,0 +1,140 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/kernels/perception/perception_ops.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace ops {
namespace custom {
namespace {
using testing::ElementsAreArray;
class DenseImageWarpOpModel : public SingleOpModel {
public:
DenseImageWarpOpModel(const TensorData& input, const TensorData& flow,
const TensorData& output) {
input_ = AddInput(input);
flow_ = AddInput(flow);
output_ = AddOutput(output);
std::vector<uint8_t> custom_option;
SetCustomOp("DenseImageWarp", custom_option, RegisterDenseImageWarp);
BuildInterpreter({GetShape(input_), GetShape(flow_)});
}
void SetInput(const std::vector<float>& data) {
PopulateTensor(input_, data);
}
void SetFlow(const std::vector<float>& data) { PopulateTensor(flow_, data); }
std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
protected:
int input_;
int flow_;
int output_;
};
TEST(DenseImageWarpOpTest, MismatchedSizeTest) {
EXPECT_DEATH_IF_SUPPORTED(
DenseImageWarpOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 4, 4, 1}},
/*flow=*/{TensorType_FLOAT32, {1, 4, 2, 2}},
/*output=*/{TensorType_FLOAT32, {}});
, "input_shape.Dims.2. != flow_shape.Dims.2. .4 != 2.");
}
TEST(DenseImageWarpOpTest, WrongFlowSizeTest) {
EXPECT_DEATH_IF_SUPPORTED(DenseImageWarpOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 4, 4, 1}},
/*flow=*/{TensorType_FLOAT32, {1, 4, 4, 1}},
/*output=*/{TensorType_FLOAT32, {}});
, "The last dimension of flow tensor must be 2.");
}
TEST(DenseImageWarpOpTest, SimpleTest) {
DenseImageWarpOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 4, 4, 1}},
/*flow=*/{TensorType_FLOAT32, {1, 4, 4, 2}},
/*output=*/{TensorType_FLOAT32, {}});
model.SetInput({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
model.SetFlow({4, 10, 6, 10, 4, 2, 6, 6, 10, -4, 2, -2, 6, 8, 6, 0,
2, -2, 10, 6, 4, 4, 2, -4, -4, 10, -4, -4, -2, 6, 4, 6});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
EXPECT_THAT(model.GetOutput(), ElementsAreArray({0, 0, 0, 0, 3, 3, 0, 3, 2, 0,
0, 3, 12, 15, 12, 0}));
}
TEST(DenseImageWarpOpTest, RoundTest) {
DenseImageWarpOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 4, 4, 1}},
/*flow=*/{TensorType_FLOAT32, {1, 4, 4, 2}},
/*output=*/{TensorType_FLOAT32, {}});
model.SetInput({0.2, 1.5, 2.4, 3.5, 4.6, 5.1, 6.3, 7.2, 8.5, 9.6, 10.9, 11.6,
12.8, 13.2, 14.4, 15.5});
model.SetFlow({4, 10, 6, 10, 4, 2, 6, 6, 10, -4, 2, -2, 6, 8, 6, 0,
2, -2, 10, 6, 4, 4, 2, -4, -4, 10, -4, -4, -2, 6, 4, 6});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 4, 1}));
EXPECT_THAT(model.GetOutput(),
ElementsAreArray({0.2, 0.2, 0.2, 0.2, 3.5, 3.5, 0.2, 3.5, 2.4,
0.2, 0.2, 3.5, 12.8, 15.5, 12.8, 0.2}));
}
TEST(DenseImageWarpOpTest, WithBatchandChannelTest) {
DenseImageWarpOpModel model(
/*input=*/{TensorType_FLOAT32, {2, 4, 4, 3}},
/*flow=*/{TensorType_FLOAT32, {2, 4, 4, 2}},
/*output=*/{TensorType_FLOAT32, {}});
std::vector<float> input_data;
for (int i = 0; i < 96; ++i) input_data.push_back(i);
model.SetInput(input_data);
model.SetFlow({2, -2, 10, 6, 4, 4, 2, -4, -4, 10, -4, -4, -2, 6, 4, 6,
4, 10, 6, 10, 4, 2, 6, 6, 10, -4, 2, -2, 6, 8, 6, 0,
2, -2, 10, 6, 4, 4, 2, -4, -4, 10, -4, -4, -2, 6, 4, 6,
4, 10, 6, 10, 4, 2, 6, 6, 10, -4, 2, -2, 6, 8, 6, 0});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 4, 4, 3}));
EXPECT_THAT(
model.GetOutput(),
ElementsAreArray({6, 7, 8, 0, 1, 2, 0, 1, 2, 9, 10, 11, 36, 37,
38, 45, 46, 47, 36, 37, 38, 0, 1, 2, 0, 1, 2, 0,
1, 2, 0, 1, 2, 0, 1, 2, 9, 10, 11, 21, 22, 23,
0, 1, 2, 9, 10, 11, 54, 55, 56, 48, 49, 50, 48, 49,
50, 57, 58, 59, 84, 85, 86, 93, 94, 95, 84, 85, 86, 48,
49, 50, 48, 49, 50, 48, 49, 50, 48, 49, 50, 48, 49, 50,
57, 58, 59, 69, 70, 71, 48, 49, 50, 57, 58, 59}));
}
} // namespace
} // namespace custom
} // namespace ops
} // namespace tflite
@@ -0,0 +1,261 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <string>
#include "flatbuffers/flexbuffers.h" // from @flatbuffers
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/kernels/internal/common.h"
#include "tensorflow/lite/kernels/internal/compatibility.h"
#include "tensorflow/lite/kernels/internal/runtime_shape.h"
#include "tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tensorflow/lite/kernels/internal/types.h"
#include "tensorflow/lite/kernels/kernel_util.h"
#include "tensorflow/lite/kernels/padding.h"
namespace tflite {
namespace ops {
namespace custom {
namespace max_pool_with_argmax {
namespace {
// TODO(b/175003241): Move this logic to lite/kernels/internal when promoting
// this op to a builtin op.
template <typename T>
inline void MaxPool(const PoolParams& params, const RuntimeShape& input_shape,
const RuntimeShape& output_shape, const T* input_data,
T* output_data, int32_t* indices_data) {
TFLITE_DCHECK_EQ(input_shape.DimensionsCount(), 4);
TFLITE_DCHECK_EQ(output_shape.DimensionsCount(), 4);
const int32_t batches = MatchingDim(input_shape, 0, output_shape, 0);
const int32_t depth = MatchingDim(input_shape, 3, output_shape, 3);
const int32_t input_height = input_shape.Dims(1);
const int32_t input_width = input_shape.Dims(2);
const int32_t output_height = output_shape.Dims(1);
const int32_t output_width = output_shape.Dims(2);
const int32_t stride_height = params.stride_height;
const int32_t stride_width = params.stride_width;
for (int32_t batch = 0; batch < batches; ++batch) {
for (int32_t out_y = 0; out_y < output_height; ++out_y) {
for (int32_t out_x = 0; out_x < output_width; ++out_x) {
for (int32_t channel = 0; channel < depth; ++channel) {
const int32_t in_x_origin =
(out_x * stride_width) - params.padding_values.width;
const int32_t in_y_origin =
(out_y * stride_height) - params.padding_values.height;
// Compute the boundaries of the filter region clamped so as to
// ensure that the filter window fits in the input array.
const int32_t filter_x_start = std::max(0, -in_x_origin);
const int32_t filter_x_end =
std::min(params.filter_width, input_width - in_x_origin);
const int32_t filter_y_start = std::max(0, -in_y_origin);
const int32_t filter_y_end =
std::min(params.filter_height, input_height - in_y_origin);
float max = std::numeric_limits<float>::lowest();
int32_t max_x = 0;
int32_t max_y = 0;
for (int32_t filter_y = filter_y_start; filter_y < filter_y_end;
++filter_y) {
for (int32_t filter_x = filter_x_start; filter_x < filter_x_end;
++filter_x) {
const int32_t in_x = in_x_origin + filter_x;
const int32_t in_y = in_y_origin + filter_y;
float cur =
input_data[Offset(input_shape, batch, in_y, in_x, channel)];
if (cur > max) {
max = cur;
max_x = in_x;
max_y = in_y;
}
}
}
int32_t output_idx =
Offset(output_shape, batch, out_y, out_x, channel);
output_data[output_idx] = ActivationFunctionWithMinMax(
max, params.float_activation_min, params.float_activation_max);
indices_data[output_idx] =
(max_y * input_width + max_x) * depth + channel;
}
}
}
}
}
} // namespace
constexpr int kDataInputTensor = 0;
constexpr int kDataOutputTensor = 0;
constexpr int kIndicesOutputTensor = 1;
constexpr const char kIncludeBatchStr[] = "include_batch_in_index";
constexpr const char kPoolSizeStr[] = "ksize";
constexpr const char kStridesStr[] = "strides";
constexpr const char kPaddingStr[] = "padding";
constexpr const char kPaddingSameStr[] = "SAME";
constexpr const char kPaddingValidStr[] = "VALID";
struct OpData {
TfLitePoolParams params;
bool include_batch_in_index;
};
void* Init(TfLiteContext* context, const char* buffer, size_t length) {
const flexbuffers::Map& m =
flexbuffers::GetRoot(reinterpret_cast<const uint8_t*>(buffer), length)
.AsMap();
OpData* op_data = new OpData;
op_data->params.computed.padding = TfLitePaddingValues{0, 0, 0, 0};
op_data->include_batch_in_index = m[kIncludeBatchStr].AsBool();
op_data->params.activation = kTfLiteActNone;
const std::string padding = m[kPaddingStr].AsString().str();
if (padding == kPaddingValidStr) {
op_data->params.padding = kTfLitePaddingValid;
} else if (padding == kPaddingSameStr) {
op_data->params.padding = kTfLitePaddingSame;
} else {
op_data->params.padding = kTfLitePaddingUnknown;
}
// The first and last element of pool_size are always 1.
const auto pool_size = m[kPoolSizeStr].AsTypedVector();
TFLITE_CHECK_EQ(pool_size.size(), 4);
TFLITE_CHECK_EQ(pool_size[0].AsInt32(), 1);
TFLITE_CHECK_EQ(pool_size[3].AsInt32(), 1);
op_data->params.filter_height = pool_size[1].AsInt32();
op_data->params.filter_width = pool_size[2].AsInt32();
// The first and last element of strides are always 1.
const auto strides = m[kStridesStr].AsTypedVector();
TFLITE_CHECK_EQ(strides.size(), 4);
TFLITE_CHECK_EQ(strides[0].AsInt32(), 1);
TFLITE_CHECK_EQ(strides[3].AsInt32(), 1);
op_data->params.stride_height = strides[1].AsInt32();
op_data->params.stride_width = strides[2].AsInt32();
return op_data;
}
void Free(TfLiteContext* context, void* buffer) {
delete reinterpret_cast<OpData*>(buffer);
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
TF_LITE_ENSURE_EQ(context, NumInputs(node), 1);
TF_LITE_ENSURE_EQ(context, NumOutputs(node), 2);
TfLiteTensor *output, *indices;
TF_LITE_ENSURE_OK(context,
GetOutputSafe(context, node, kDataOutputTensor, &output));
TF_LITE_ENSURE_OK(
context, GetOutputSafe(context, node, kIndicesOutputTensor, &indices));
const TfLiteTensor* input;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kDataInputTensor, &input));
TF_LITE_ENSURE_EQ(context, NumDimensions(input), 4);
TF_LITE_ENSURE_TYPES_EQ(context, input->type, output->type);
TF_LITE_ENSURE_TYPES_EQ(context, input->type, kTfLiteFloat32);
TF_LITE_ENSURE(context, indices->type == kTfLiteInt32);
TF_LITE_ENSURE(context, op_data->params.padding != kTfLitePaddingUnknown);
TF_LITE_ENSURE_MSG(
context, !op_data->include_batch_in_index,
"Include batch dimension in flattened index is not yet supported.");
int batches = input->dims->data[0];
int height = input->dims->data[1];
int width = input->dims->data[2];
int channels_out = input->dims->data[3];
// Matching GetWindowedOutputSize in TensorFlow.
int out_width, out_height;
op_data->params.computed.padding = ComputePaddingHeightWidth(
op_data->params.stride_height, op_data->params.stride_width, 1, 1, height,
width, op_data->params.filter_height, op_data->params.filter_width,
op_data->params.padding, &out_height, &out_width);
TfLiteIntArray* output_size = TfLiteIntArrayCreate(4);
output_size->data[0] = batches;
output_size->data[1] = out_height;
output_size->data[2] = out_width;
output_size->data[3] = channels_out;
TfLiteIntArray* indices_size = TfLiteIntArrayCopy(output_size);
TF_LITE_ENSURE_STATUS(context->ResizeTensor(context, indices, indices_size));
return context->ResizeTensor(context, output, output_size);
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
OpData* op_data = reinterpret_cast<OpData*>(node->user_data);
float activation_min, activation_max;
CalculateActivationRange(op_data->params.activation, &activation_min,
&activation_max);
tflite::PoolParams op_params;
op_params.stride_height = op_data->params.stride_height;
op_params.stride_width = op_data->params.stride_width;
op_params.filter_height = op_data->params.filter_height;
op_params.filter_width = op_data->params.filter_width;
op_params.padding_values.height = op_data->params.computed.padding.height;
op_params.padding_values.width = op_data->params.computed.padding.width;
op_params.float_activation_min = activation_min;
op_params.float_activation_max = activation_max;
TfLiteTensor *output, *indices;
TF_LITE_ENSURE_OK(context,
GetOutputSafe(context, node, kDataOutputTensor, &output));
TF_LITE_ENSURE_OK(
context, GetOutputSafe(context, node, kIndicesOutputTensor, &indices));
const TfLiteTensor* input;
TF_LITE_ENSURE_OK(context,
GetInputSafe(context, node, kDataInputTensor, &input));
switch (input->type) {
case kTfLiteFloat32:
MaxPool<float>(op_params, GetTensorShape(input), GetTensorShape(output),
GetTensorData<float>(input), GetTensorData<float>(output),
GetTensorData<int32_t>(indices));
break;
default:
TF_LITE_KERNEL_LOG(context, "Type %s not currently supported.",
TfLiteTypeGetName(input->type));
return kTfLiteError;
}
return kTfLiteOk;
}
} // namespace max_pool_with_argmax
TfLiteRegistration* RegisterMaxPoolWithArgmax() {
static TfLiteRegistration r = {
max_pool_with_argmax::Init, max_pool_with_argmax::Free,
max_pool_with_argmax::Prepare, max_pool_with_argmax::Eval};
return &r;
}
// Alias for selective build.
TfLiteRegistration* Register_MAX_POOL_WITH_ARGMAX() {
return RegisterMaxPoolWithArgmax();
}
} // namespace custom
} // namespace ops
} // namespace tflite
@@ -0,0 +1,302 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstddef>
#include <cstdint>
#include <memory>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "flatbuffers/flexbuffers.h" // from @flatbuffers
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/kernels/perception/perception_ops.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace ops {
namespace custom {
namespace {
using testing::ElementsAreArray;
class MaxpoolingWithArgMaxOpModel : public SingleOpModel {
public:
MaxpoolingWithArgMaxOpModel(const TensorData& input, int stride_height,
int stride_width, int filter_height,
int filter_width, TfLitePadding padding,
const TensorData& output,
const TensorData& indices) {
input_ = AddInput(input);
output_ = AddOutput(output);
indices_ = AddOutput(indices);
std::vector<uint8_t> custom_option = CreateCustomOptions(
stride_height, stride_width, filter_height, filter_width, padding);
SetCustomOp("MaxPoolWithArgmax", custom_option, RegisterMaxPoolWithArgmax);
BuildInterpreter({GetShape(input_)});
}
void SetInput(const std::vector<float>& data) {
PopulateTensor(input_, data);
}
std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
std::vector<int32_t> GetIndices() { return ExtractVector<int32_t>(indices_); }
std::vector<int> GetIndicesShape() { return GetTensorShape(indices_); }
protected:
int input_;
int output_;
int indices_;
private:
std::vector<uint8_t> CreateCustomOptions(int stride_height, int stride_width,
int filter_height, int filter_width,
TfLitePadding padding) {
auto flex_builder = std::make_unique<flexbuffers::Builder>();
size_t map_start = flex_builder->StartMap();
flex_builder->Bool("include_batch_in_index", false);
if (padding == kTfLitePaddingValid) {
flex_builder->String("padding", "VALID");
} else {
flex_builder->String("padding", "SAME");
}
auto start = flex_builder->StartVector("ksize");
flex_builder->Add(1);
flex_builder->Add(filter_height);
flex_builder->Add(filter_width);
flex_builder->Add(1);
flex_builder->EndVector(start, /*typed=*/true, /*fixed=*/false);
auto strides_start = flex_builder->StartVector("strides");
flex_builder->Add(1);
flex_builder->Add(stride_height);
flex_builder->Add(stride_width);
flex_builder->Add(1);
flex_builder->EndVector(strides_start, /*typed=*/true, /*fixed=*/false);
flex_builder->EndMap(map_start);
flex_builder->Finish();
return flex_builder->GetBuffer();
}
};
TEST(MaxpoolWithArgMaxTest, UnsupportedInt64Test) {
EXPECT_DEATH_IF_SUPPORTED(MaxpoolingWithArgMaxOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 2, 4, 1}},
/*stride_height=*/2, /*stride_width=*/2,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingSame,
/*output=*/{TensorType_FLOAT32, {}},
/*indices=*/{TensorType_INT64, {}});
, "indices->type == kTfLiteInt32 was not true.");
}
TEST(MaxpoolWithArgMaxTest, SimpleTest) {
MaxpoolingWithArgMaxOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 2, 4, 1}},
/*stride_height=*/2, /*stride_width=*/2,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingSame,
/*output=*/{TensorType_FLOAT32, {}},
/*indices=*/{TensorType_INT32, {}});
model.SetInput({0, 13, 2, 0, 0, 1, 4, 0});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 1, 2, 1}));
EXPECT_THAT(model.GetOutput(), ElementsAreArray({13, 4}));
EXPECT_THAT(model.GetIndicesShape(), ElementsAreArray({1, 1, 2, 1}));
EXPECT_THAT(model.GetIndices(), ElementsAreArray({1, 6}));
}
TEST(MaxpoolWithArgMaxTest, Strides2x1Test) {
MaxpoolingWithArgMaxOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 4, 2, 2}},
/*stride_height=*/2, /*stride_width=*/1,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingSame,
/*output=*/{TensorType_FLOAT32, {}},
/*indices=*/{TensorType_INT32, {}});
model.SetInput({1, 0, 0, 2, 3, 0, 0, 4, 5, 0, 0, 6, 7, 0, 0, 8});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 2, 2}));
EXPECT_THAT(model.GetOutput(), ElementsAreArray({3, 4, 0, 4, 7, 8, 0, 8}));
EXPECT_THAT(model.GetIndicesShape(), ElementsAreArray({1, 2, 2, 2}));
EXPECT_THAT(model.GetIndices(),
ElementsAreArray({4, 7, 2, 7, 12, 15, 10, 15}));
}
TEST(MaxpoolWithArgMaxTest, Strides2x2Test) {
MaxpoolingWithArgMaxOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 4, 8, 1}},
/*stride_height=*/2, /*stride_width=*/2,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingSame,
/*output=*/{TensorType_FLOAT32, {}},
/*indices=*/{TensorType_INT32, {}});
model.SetInput({1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 4, 0, 0,
0, 0, 0, 5, 6, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 8});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 4, 1}));
EXPECT_THAT(model.GetOutput(), ElementsAreArray({1, 3, 4, 0, 0, 7, 6, 8}));
EXPECT_THAT(model.GetIndicesShape(), ElementsAreArray({1, 2, 4, 1}));
EXPECT_THAT(model.GetIndices(),
ElementsAreArray({0, 10, 13, 6, 16, 27, 20, 31}));
}
TEST(MaxpoolWithArgMaxTest, Strides2x2UnfitTest) {
MaxpoolingWithArgMaxOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 4, 7, 1}},
/*stride_height=*/2, /*stride_width=*/2,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingSame,
/*output=*/{TensorType_FLOAT32, {}},
/*indices=*/{TensorType_INT32, {}});
model.SetInput({1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 4,
0, 0, 0, 0, 0, 5, 6, 0, 0, 0, 0, 0, 0, 7});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 4, 1}));
EXPECT_THAT(model.GetOutput(), ElementsAreArray({1, 3, 2, 4, 0, 0, 5, 7}));
EXPECT_THAT(model.GetIndicesShape(), ElementsAreArray({1, 2, 4, 1}));
EXPECT_THAT(model.GetIndices(),
ElementsAreArray({0, 10, 5, 13, 14, 16, 19, 27}));
}
TEST(MaxpoolWithArgMaxTest, PaddingValidTest) {
MaxpoolingWithArgMaxOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 4, 5, 1}},
/*stride_height=*/2, /*stride_width=*/2,
/*filter_height=*/2, /*filter_width=*/3,
/*padding=*/kTfLitePaddingValid,
/*output=*/{TensorType_FLOAT32, {}},
/*indices=*/{TensorType_INT32, {}});
model.SetInput(
{0, 0, 0, 0, 0, 0, 7, 0, 0, 10, 0, 0, 0, 0, 0, 0, 20, 0, 0, 19});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 2, 1}));
EXPECT_THAT(model.GetOutput(), ElementsAreArray({7, 10, 20, 19}));
EXPECT_THAT(model.GetIndicesShape(), ElementsAreArray({1, 2, 2, 1}));
EXPECT_THAT(model.GetIndices(), ElementsAreArray({6, 9, 16, 19}));
}
TEST(MaxpoolWithArgMaxTest, PaddingValidUnfitTest) {
MaxpoolingWithArgMaxOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 4, 6, 1}},
/*stride_height=*/2, /*stride_width=*/2,
/*filter_height=*/2, /*filter_width=*/3,
/*padding=*/kTfLitePaddingValid,
/*output=*/{TensorType_FLOAT32, {}},
/*indices=*/{TensorType_INT32, {}});
model.SetInput({0, 0, 0, 0, 0, 0, 7, 0, 0, 10, 0, 0,
0, 0, 0, 0, 20, 0, 0, 19, 24, 1, 2, 44});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 2, 1}));
EXPECT_THAT(model.GetOutput(), ElementsAreArray({7, 10, 24, 24}));
EXPECT_THAT(model.GetIndicesShape(), ElementsAreArray({1, 2, 2, 1}));
EXPECT_THAT(model.GetIndices(), ElementsAreArray({6, 9, 20, 20}));
}
TEST(MaxpoolWithArgMaxTest, InputWithBatchTest) {
MaxpoolingWithArgMaxOpModel model(
/*input=*/{TensorType_FLOAT32, {2, 4, 12, 2}},
/*stride_height=*/2, /*stride_width=*/3,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingSame,
/*output=*/{TensorType_FLOAT32, {}},
/*indices=*/{TensorType_INT32, {}});
model.SetInput({0, 0, 1, 0, 0, 0, 0, 0, 3, 4, 0, 0, 5, 0, 0, 6,
0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 8, 9, 0, 0, 10,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0,
0, 16, 0, 0, 0, 0, 0, 0, 11, 0, 0, 12, 0, 0, 0, 14,
13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17, 18, 0, 0, 0, 30, 0, 20, 0, 0, 0, 0, 0, 0, 21, 0,
0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0,
0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0,
0, 0, 27, 28, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 32,
0, 0, 0, 0, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 2, 4, 2}));
EXPECT_THAT(model.GetOutput(),
ElementsAreArray({1, 0, 3, 4, 5, 6, 9, 8, 11, 12, 13,
14, 15, 0, 0, 0, 17, 18, 19, 20, 21, 0,
23, 24, 27, 28, 29, 0, 31, 32, 25, 26}));
EXPECT_THAT(model.GetIndicesShape(), ElementsAreArray({2, 2, 4, 2}));
EXPECT_THAT(model.GetIndices(),
ElementsAreArray({2, 1, 8, 9, 12, 15, 44, 43, 72, 75, 80,
79, 62, 61, 66, 67, 0, 1, 30, 7, 14, 13,
42, 21, 50, 51, 56, 55, 86, 63, 68, 69}));
}
TEST(MaxpoolWithArgMaxTest, InputWithBatchAndPaddingValidTest) {
MaxpoolingWithArgMaxOpModel model(
/*input=*/{TensorType_FLOAT32, {2, 4, 11, 2}},
/*stride_height=*/2, /*stride_width=*/3,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingValid,
/*output=*/{TensorType_FLOAT32, {}},
/*indices=*/{TensorType_INT32, {}});
model.SetInput({0, 0, 1, 0, 0, 0, 0, 0, 3, 4, 0, 0, 5, 0, 0, 6,
0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 8, 9, 0, 0, 10,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0,
0, 16, 0, 0, 0, 0, 0, 0, 11, 0, 0, 12, 0, 0, 0, 14,
13, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 30, 0, 20,
0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 24, 0, 0,
0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 22, 0, 0, 0, 0,
0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 27, 28, 0, 0, 0, 0,
29, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 25, 26, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 2, 4, 2}));
EXPECT_THAT(model.GetOutput(),
ElementsAreArray({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 0, 31, 32}));
EXPECT_THAT(model.GetIndicesShape(), ElementsAreArray({2, 2, 4, 2}));
EXPECT_THAT(model.GetIndices(),
ElementsAreArray({2, 23, 8, 9, 12, 15, 40, 43, 44, 47, 72,
75, 80, 79, 62, 65, 0, 1, 30, 7, 14, 35,
42, 21, 68, 69, 50, 51, 56, 57, 86, 63}));
}
} // namespace
} // namespace custom
} // namespace ops
} // namespace tflite
@@ -0,0 +1,140 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <cstring>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/kernels/internal/runtime_shape.h"
#include "tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tensorflow/lite/kernels/internal/types.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace ops {
namespace custom {
namespace max_unpooling_2d {
constexpr int kDataInputTensor = 0;
constexpr int kIndicesTensor = 1;
constexpr int kOutputTensor = 0;
// TODO(b/175003241): Move this logic to lite/kernels/internal when promoting
// this op to a builtin op.
inline void MaxUnpooling(const RuntimeShape& input_shape,
const float* input_data, const int32_t* indices_data,
const RuntimeShape& output_shape, float* output_data) {
std::memset(output_data, 0, output_shape.FlatSize() * sizeof(float));
const int batches = MatchingDim(input_shape, 0, output_shape, 0);
const int depth = MatchingDim(input_shape, 3, output_shape, 3);
const int batch_stride =
output_shape.Dims(1) * output_shape.Dims(2) * output_shape.Dims(3);
for (int batch = 0; batch < batches; ++batch) {
for (int in_y = 0; in_y < input_shape.Dims(1); ++in_y) {
for (int in_x = 0; in_x < input_shape.Dims(2); ++in_x) {
for (int channel = 0; channel < depth; ++channel) {
const auto input_offset =
Offset(input_shape, batch, in_y, in_x, channel);
int idx = indices_data[input_offset];
output_data[batch * batch_stride + idx] = input_data[input_offset];
}
}
}
}
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
auto* params =
reinterpret_cast<const TfLitePoolParams*>(node->custom_initial_data);
TF_LITE_ENSURE_EQ(context, NumInputs(node), 2);
TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1);
TfLiteTensor* output = GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
const TfLiteTensor* input = GetInput(context, node, kDataInputTensor);
TF_LITE_ENSURE(context, input != nullptr);
const TfLiteTensor* indices = GetInput(context, node, kIndicesTensor);
TF_LITE_ENSURE(context, indices != nullptr);
TF_LITE_ENSURE_EQ(context, NumDimensions(indices), 4);
TF_LITE_ENSURE_EQ(context, NumDimensions(input), 4);
TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, output->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, indices->type, kTfLiteInt32);
TF_LITE_ENSURE(context, params->padding != kTfLitePaddingUnknown);
// Size of input and indices tensor must match.
const RuntimeShape input_shape = GetTensorShape(input);
const RuntimeShape indices_shape = GetTensorShape(indices);
TF_LITE_ENSURE_MSG(
context, input_shape.DimensionsCount() == indices_shape.DimensionsCount(),
"Input and indices must have the same shape.");
for (int i = 0; i < input_shape.DimensionsCount(); ++i) {
TF_LITE_ENSURE_MSG(context, input_shape.Dims(i) == indices_shape.Dims(i),
"Input and indices must have the same shape.");
}
int batches = input->dims->data[0];
int height = input->dims->data[1];
int width = input->dims->data[2];
int channels_out = input->dims->data[3];
int out_width, out_height;
if (params->padding == kTfLitePaddingSame) {
out_width = width * params->stride_width;
out_height = height * params->stride_height;
} else {
out_width = (width - 1) * params->stride_width + params->filter_width;
out_height = (height - 1) * params->stride_height + params->filter_height;
}
TfLiteIntArray* output_size = TfLiteIntArrayCreate(4);
output_size->data[0] = batches;
output_size->data[1] = out_height;
output_size->data[2] = out_width;
output_size->data[3] = channels_out;
return context->ResizeTensor(context, output, output_size);
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
TfLiteTensor* output = GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
const TfLiteTensor* input = GetInput(context, node, kDataInputTensor);
TF_LITE_ENSURE(context, input != nullptr);
const TfLiteTensor* indices = GetInput(context, node, kIndicesTensor);
TF_LITE_ENSURE(context, indices != nullptr);
MaxUnpooling(GetTensorShape(input), GetTensorData<float>(input),
GetTensorData<int32_t>(indices), GetTensorShape(output),
GetTensorData<float>(output));
return kTfLiteOk;
}
} // namespace max_unpooling_2d
TfLiteRegistration* RegisterMaxUnpooling2D() {
static TfLiteRegistration reg = {/*init=*/nullptr,
/*free=*/nullptr, max_unpooling_2d::Prepare,
max_unpooling_2d::Eval};
return &reg;
}
// Alias for selective build.
TfLiteRegistration* Register_MAX_UNPOOLING2D() {
return RegisterMaxUnpooling2D();
}
} // namespace custom
} // namespace ops
} // namespace tflite
@@ -0,0 +1,261 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/kernels/perception/perception_ops.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace ops {
namespace custom {
namespace {
using testing::ElementsAreArray;
class MaxUnpoolingOpModel : public SingleOpModel {
public:
MaxUnpoolingOpModel(const TensorData& input, const TensorData& indices,
int stride_height, int stride_width, int filter_height,
int filter_width, TfLitePadding padding,
const TensorData& output) {
input_ = AddInput(input);
indices_ = AddInput(indices);
output_ = AddOutput(output);
TfLitePoolParams params{padding, stride_width, stride_height,
filter_width, filter_height, kTfLiteActNone};
uint8_t* params_ptr = reinterpret_cast<uint8_t*>(&params);
std::vector<uint8_t> custom_option;
custom_option.assign(params_ptr, params_ptr + sizeof(TfLitePoolParams));
SetCustomOp("MaxUnpooling2D", custom_option, RegisterMaxUnpooling2D);
BuildInterpreter({GetShape(input_), GetShape(indices_)});
}
void SetInput(const std::vector<float>& data) {
PopulateTensor(input_, data);
}
void SetIndices(const std::vector<int32_t>& data) {
PopulateTensor(indices_, data);
}
std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
protected:
int input_;
int indices_;
int output_;
};
TEST(MaxUnpoolingOpTest, DimensionMisMatchTest) {
EXPECT_DEATH(MaxUnpoolingOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 1, 2, 1}},
/*indices=*/{TensorType_INT32, {1, 2, 2, 1}},
/*stride_height=*/2, /*stride_width=*/2,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingSame,
/*output=*/{TensorType_FLOAT32, {}}),
"Input and indices must have the same shape.");
}
TEST(MaxUnpoolingOpTest, SimpleTest) {
MaxUnpoolingOpModel model(
/*input=*/{TensorType_FLOAT32, {1, 1, 2, 1}},
/*indices=*/{TensorType_INT32, {1, 1, 2, 1}},
/*stride_height=*/2, /*stride_width=*/2,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingSame,
/*output=*/{TensorType_FLOAT32, {}});
model.SetInput({13, 4});
model.SetIndices({1, 6});
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 4, 1}));
EXPECT_THAT(model.GetOutput(), ElementsAreArray({0, 13, 0, 0, 0, 0, 4, 0}));
}
TEST(MaxUnpoolingOpTest, Strides2x1Test) {
constexpr int kInputB = 1;
constexpr int kInputH = 2;
constexpr int kInputW = 2;
constexpr int kInputC = 2;
std::vector<float> input_data{1, 2, 3, 4, 5, 6, 7, 8};
std::vector<int32_t> indices_data{0, 3, 4, 7, 8, 11, 12, 15};
MaxUnpoolingOpModel model(
/*input=*/{TensorType_FLOAT32, {kInputB, kInputH, kInputW, kInputC}},
/*indices=*/{TensorType_INT32, {kInputB, kInputH, kInputW, kInputC}},
/*stride_height=*/2, /*stride_width=*/1,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingSame,
/*output=*/{TensorType_FLOAT32, {}});
model.SetInput(input_data);
model.SetIndices(indices_data);
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 2, 2}));
EXPECT_THAT(model.GetOutput(), ElementsAreArray({1, 0, 0, 2, 3, 0, 0, 4, 5, 0,
0, 6, 7, 0, 0, 8}));
}
TEST(MaxUnpoolingOpTest, Strides2x2Test) {
constexpr int kInputB = 1;
constexpr int kInputH = 2;
constexpr int kInputW = 4;
constexpr int kInputC = 1;
std::vector<float> input_data{1, 2, 3, 4, 5, 6, 7, 8};
std::vector<int32_t> indices_data{0, 5, 10, 13, 19, 20, 27, 31};
MaxUnpoolingOpModel model(
/*input=*/{TensorType_FLOAT32, {kInputB, kInputH, kInputW, kInputC}},
/*indices=*/{TensorType_INT32, {kInputB, kInputH, kInputW, kInputC}},
/*stride_height=*/2, /*stride_width=*/2,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingSame,
/*output=*/{TensorType_FLOAT32, {}});
model.SetInput(input_data);
model.SetIndices(indices_data);
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 8, 1}));
EXPECT_THAT(
model.GetOutput(),
ElementsAreArray({1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 4, 0, 0,
0, 0, 0, 5, 6, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 8}));
}
TEST(MaxUnpoolingOpTest, PaddingValidTest) {
constexpr int kInputB = 1;
constexpr int kInputH = 2;
constexpr int kInputW = 2;
constexpr int kInputC = 1;
std::vector<float> input_data{7, 10, 20, 19};
std::vector<int32_t> indices_data{6, 9, 16, 19};
MaxUnpoolingOpModel model(
/*input=*/{TensorType_FLOAT32, {kInputB, kInputH, kInputW, kInputC}},
/*indices=*/{TensorType_INT32, {kInputB, kInputH, kInputW, kInputC}},
/*stride_height=*/2, /*stride_width=*/2,
/*filter_height=*/2, /*filter_width=*/3,
/*padding=*/kTfLitePaddingValid,
/*output=*/{TensorType_FLOAT32, {}});
model.SetInput(input_data);
model.SetIndices(indices_data);
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 4, 5, 1}));
EXPECT_THAT(model.GetOutput(),
ElementsAreArray({0, 0, 0, 0, 0, 0, 7, 0, 0, 10,
0, 0, 0, 0, 0, 0, 20, 0, 0, 19}));
}
TEST(MaxUnpoolingOpTest, InputWithBatchTest) {
constexpr int kInputB = 2;
constexpr int kInputH = 2;
constexpr int kInputW = 4;
constexpr int kInputC = 2;
std::vector<float> input_data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32};
std::vector<int32_t> indices_data{2, 23, 8, 9, 12, 15, 40, 43, 44, 47, 72,
75, 80, 79, 62, 65, 0, 1, 30, 7, 14, 35,
42, 21, 68, 69, 50, 51, 56, 5, 86, 63};
MaxUnpoolingOpModel model(
/*input=*/{TensorType_FLOAT32, {kInputB, kInputH, kInputW, kInputC}},
/*indices=*/{TensorType_INT32, {kInputB, kInputH, kInputW, kInputC}},
/*stride_height=*/2, /*stride_width=*/3,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingSame,
/*output=*/{TensorType_FLOAT32, {}});
model.SetInput(input_data);
model.SetIndices(indices_data);
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 4, 12, 2}));
EXPECT_THAT(
model.GetOutput(),
ElementsAreArray(
{0, 0, 1, 0, 0, 0, 0, 0, 3, 4, 0, 0, 5, 0, 0, 6, 0, 0,
0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 7, 0, 0, 8, 9, 0, 0, 10, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 16, 0, 0, 0, 0, 0, 0,
11, 0, 0, 12, 0, 0, 0, 14, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 30, 0, 20, 0, 0, 0, 0,
0, 0, 21, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0,
19, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0,
0, 0, 27, 28, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 32, 0, 0,
0, 0, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
}
TEST(MaxUnpoolingOpTest, InputWithBatchAndPaddingValidTest) {
constexpr int kInputB = 2;
constexpr int kInputH = 2;
constexpr int kInputW = 4;
constexpr int kInputC = 2;
std::vector<float> input_data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32};
std::vector<int32_t> indices_data{2, 23, 8, 9, 12, 15, 40, 43, 44, 47, 72,
75, 80, 79, 62, 65, 0, 1, 30, 7, 14, 35,
42, 21, 68, 69, 50, 51, 56, 5, 86, 63};
MaxUnpoolingOpModel model(
/*input=*/{TensorType_FLOAT32, {kInputB, kInputH, kInputW, kInputC}},
/*indices=*/{TensorType_INT32, {kInputB, kInputH, kInputW, kInputC}},
/*stride_height=*/2, /*stride_width=*/3,
/*filter_height=*/2, /*filter_width=*/2,
/*padding=*/kTfLitePaddingValid,
/*output=*/{TensorType_FLOAT32, {}});
model.SetInput(input_data);
model.SetIndices(indices_data);
ASSERT_EQ(model.Invoke(), kTfLiteOk);
EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 4, 11, 2}));
EXPECT_THAT(
model.GetOutput(),
ElementsAreArray(
{0, 0, 1, 0, 0, 0, 0, 0, 3, 4, 0, 0, 5, 0, 0, 6, 0, 0,
0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 7, 0, 0, 8, 9, 0, 0, 10, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 16, 0, 0, 0, 0, 0, 0,
11, 0, 0, 12, 0, 0, 0, 14, 13, 0, 0, 0, 0, 0, 0, 0, 17, 18,
0, 0, 0, 30, 0, 20, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 0, 0,
0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 22, 0, 0,
0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 27, 28, 0, 0, 0, 0,
29, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 25, 26, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 0}));
}
} // namespace
} // namespace custom
} // namespace ops
} // namespace tflite
@@ -0,0 +1,35 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/kernels/perception/perception_ops.h"
#include "tensorflow/lite/mutable_op_resolver.h"
namespace tflite {
namespace ops {
namespace custom {
extern "C" void AddPerceptionOps(::tflite::MutableOpResolver* resolver) {
resolver->AddCustom("DenseImageWarp",
tflite::ops::custom::RegisterDenseImageWarp());
resolver->AddCustom("MaxPoolWithArgmax",
tflite::ops::custom::RegisterMaxPoolWithArgmax());
resolver->AddCustom("MaxUnpooling2D",
tflite::ops::custom::RegisterMaxUnpooling2D());
}
} // namespace custom
} // namespace ops
} // namespace tflite
@@ -0,0 +1,36 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_KERNELS_PERCEPTION_PERCEPTION_OPS_H_
#define TENSORFLOW_LITE_KERNELS_PERCEPTION_PERCEPTION_OPS_H_
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/mutable_op_resolver.h"
namespace tflite {
namespace ops {
namespace custom {
TfLiteRegistration* RegisterDenseImageWarp();
TfLiteRegistration* RegisterMaxPoolWithArgmax();
TfLiteRegistration* RegisterMaxUnpooling2D();
extern "C" void AddPerceptionOps(::tflite::MutableOpResolver* resolver);
} // namespace custom
} // namespace ops
} // namespace tflite
#endif // TENSORFLOW_LITE_KERNELS_PERCEPTION_PERCEPTION_OPS_H_
@@ -0,0 +1,37 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include "pybind11/pybind11.h" // from @pybind11
#include "pybind11/pytypes.h" // from @pybind11
#include "tensorflow/lite/kernels/perception/perception_ops.h"
#include "tensorflow/lite/mutable_op_resolver.h"
PYBIND11_MODULE(pywrap_perception_ops, m) {
m.doc() = R"pbdoc(
pywrap_perception_ops
-----
)pbdoc";
m.def(
"PerceptionOpsRegisterer",
[](uintptr_t resolver) {
tflite::ops::custom::AddPerceptionOps(
reinterpret_cast<tflite::MutableOpResolver*>(resolver));
},
R"pbdoc(
Perception op registerer function with the correct signature. Registers
Perception custom ops.
)pbdoc");
}
@@ -0,0 +1,16 @@
# Copyright 2023 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
def PerceptionOpsRegisterer(arg0: int) -> None: ...