mediapipe/third_party/org_tensorflow_custom_ops.diff

3047 lines
120 KiB
Diff
Raw Permalink Normal View History

diff --git a/tensorflow/lite/delegates/gpu/common/BUILD b/tensorflow/lite/delegates/gpu/common/BUILD
index c49f2ce731d..d72773c0a5b 100644
--- a/tensorflow/lite/delegates/gpu/common/BUILD
+++ b/tensorflow/lite/delegates/gpu/common/BUILD
@@ -173,7 +173,7 @@ cc_library(
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
- ] + tf_platform_alias("custom_parsers", "//tensorflow/lite/delegates/gpu/common/"),
+ ] + ["//tensorflow/lite/delegates/gpu/common/mediapipe:custom_parsers"],
)
cc_test(
diff --git a/tensorflow/lite/delegates/gpu/common/mediapipe/BUILD b/tensorflow/lite/delegates/gpu/common/mediapipe/BUILD
new file mode 100644
index 00000000000..58967ddbb66
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/mediapipe/BUILD
@@ -0,0 +1,93 @@
+package(
+ default_visibility = ["//visibility:public"],
+ licenses = ["notice"],
+)
+
+cc_library(
+ name = "custom_parsers",
+ srcs = ["custom_parsers.cc"],
+ hdrs = ["//tensorflow/lite/delegates/gpu/common:custom_parsers.h"],
+ deps = [
+ ":landmarks_to_transform_matrix",
+ ":transform_landmarks",
+ ":transform_tensor_bilinear",
+ "//tensorflow/lite/delegates/gpu/common:operation_parser",
+ "//tensorflow/lite/delegates/gpu/common:shape",
+ "//tensorflow/lite/delegates/gpu/common:status",
+ "//tensorflow/lite/delegates/gpu/common:unimplemented_operation_parser",
+ "@com_google_absl//absl/memory",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/types:any",
+ ],
+)
+
+cc_library(
+ name = "custom_transformations",
+ srcs = ["custom_transformations.cc"],
+ hdrs = ["//tensorflow/lite/delegates/gpu/common:custom_transformations.h"],
+ deps = [
+ ":landmarks_to_transform_matrix",
+ ":transform_landmarks",
+ ":transform_tensor_bilinear",
+ "//tensorflow/lite/delegates/gpu/common:model_transformer",
+ "@com_google_absl//absl/memory",
+ ],
+)
+
+cc_library(
+ name = "landmarks_to_transform_matrix",
+ srcs = ["landmarks_to_transform_matrix.cc"],
+ hdrs = ["landmarks_to_transform_matrix.h"],
+ deps = [
+ "//tensorflow/lite/c:common",
+ "//tensorflow/lite/delegates/gpu/common:model",
+ "//tensorflow/lite/delegates/gpu/common:model_builder_helper",
+ "//tensorflow/lite/delegates/gpu/common:model_transformer",
+ "//tensorflow/lite/delegates/gpu/common:object_reader",
+ "//tensorflow/lite/delegates/gpu/common:operation_parser",
+ "//tensorflow/lite/delegates/gpu/common:shape",
+ "//tensorflow/lite/delegates/gpu/common:status",
+ "//tensorflow/lite/delegates/gpu/common:tensor",
+ "//tensorflow/lite/delegates/gpu/common:types",
+ "@com_google_absl//absl/types:any",
+ "@flatbuffers",
+ ],
+)
+
+cc_library(
+ name = "transform_landmarks",
+ srcs = ["transform_landmarks.cc"],
+ hdrs = ["transform_landmarks.h"],
+ deps = [
+ "//tensorflow/lite/c:common",
+ "//tensorflow/lite/delegates/gpu/common:model",
+ "//tensorflow/lite/delegates/gpu/common:model_builder_helper",
+ "//tensorflow/lite/delegates/gpu/common:model_transformer",
+ "//tensorflow/lite/delegates/gpu/common:object_reader",
+ "//tensorflow/lite/delegates/gpu/common:operation_parser",
+ "//tensorflow/lite/delegates/gpu/common:shape",
+ "//tensorflow/lite/delegates/gpu/common:status",
+ "//tensorflow/lite/delegates/gpu/common:tensor",
+ "@com_google_absl//absl/types:any",
+ "@flatbuffers",
+ ],
+)
+
+cc_library(
+ name = "transform_tensor_bilinear",
+ srcs = ["transform_tensor_bilinear.cc"],
+ hdrs = ["transform_tensor_bilinear.h"],
+ deps = [
+ "//tensorflow/lite/c:common",
+ "//tensorflow/lite/delegates/gpu/common:model",
+ "//tensorflow/lite/delegates/gpu/common:model_builder_helper",
+ "//tensorflow/lite/delegates/gpu/common:model_transformer",
+ "//tensorflow/lite/delegates/gpu/common:object_reader",
+ "//tensorflow/lite/delegates/gpu/common:operation_parser",
+ "//tensorflow/lite/delegates/gpu/common:shape",
+ "//tensorflow/lite/delegates/gpu/common:status",
+ "//tensorflow/lite/delegates/gpu/common:tensor",
+ "@com_google_absl//absl/types:any",
+ "@flatbuffers",
+ ],
+)
diff --git a/tensorflow/lite/delegates/gpu/common/mediapipe/custom_parsers.cc b/tensorflow/lite/delegates/gpu/common/mediapipe/custom_parsers.cc
new file mode 100644
index 00000000000..52c11b90fc8
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/mediapipe/custom_parsers.cc
@@ -0,0 +1,34 @@
+#include "tensorflow/lite/delegates/gpu/common/custom_parsers.h"
+
+#include <memory>
+#include <string>
+
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/landmarks_to_transform_matrix.h"
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/transform_landmarks.h"
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/transform_tensor_bilinear.h"
+#include "tensorflow/lite/delegates/gpu/common/operation_parser.h"
+#include "tensorflow/lite/delegates/gpu/common/unimplemented_operation_parser.h"
+
+namespace tflite {
+namespace gpu {
+
+std::unique_ptr<TFLiteOperationParser> NewCustomOperationParser(
+ absl::string_view op_name) {
+ if (op_name == "Landmarks2TransformMatrix" ||
+ op_name == "Landmarks2TransformMatrixV2") {
+ return std::make_unique<LandmarksToTransformMatrixOperationParser>();
+ }
+ if (op_name == "TransformLandmarks") {
+ return std::make_unique<TransformLandmarksOperationParser>();
+ }
+ if (op_name == "TransformTensor" /*for version 1*/ ||
+ op_name == "TransformTensorBilinear" /*for version 2*/) {
+ return std::make_unique<TransformTensorBilinearOperationParser>();
+ }
+ return absl::make_unique<UnimplementedOperationParser>(op_name);
+}
+
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/common/mediapipe/custom_transformations.cc b/tensorflow/lite/delegates/gpu/common/mediapipe/custom_transformations.cc
new file mode 100644
index 00000000000..1509ea3bcf3
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/mediapipe/custom_transformations.cc
@@ -0,0 +1,24 @@
+#include "tensorflow/lite/delegates/gpu/common/custom_transformations.h"
+
+#include "absl/memory/memory.h"
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/landmarks_to_transform_matrix.h"
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/transform_landmarks.h"
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/transform_tensor_bilinear.h"
+#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
+
+namespace tflite {
+namespace gpu {
+bool ApplyCustomTransformations(ModelTransformer* transformer) {
+ return transformer->Apply(
+ "transform_landmarks_v2_to_v1",
+ absl::make_unique<TransformLandmarksV2ToV1>().get()) &&
+ transformer->Apply(
+ "transform_tensor_bilinear_v2_to_v1",
+ absl::make_unique<TransformTensorBilinearV2ToV1>().get()) &&
+ transformer->Apply(
+ "landmarks_to_transform_matrix_v2_with_mul",
+ absl::make_unique<LandmarksToTransformMatrixV2ToV2WithMul>()
+ .get());
+}
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/common/mediapipe/landmarks_to_transform_matrix.cc b/tensorflow/lite/delegates/gpu/common/mediapipe/landmarks_to_transform_matrix.cc
new file mode 100644
index 00000000000..4e73cf649e6
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/mediapipe/landmarks_to_transform_matrix.cc
@@ -0,0 +1,182 @@
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/landmarks_to_transform_matrix.h"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "absl/types/any.h"
+#include "flatbuffers/flexbuffers.h"
+#include "tensorflow/lite/delegates/gpu/common/model.h"
+#include "tensorflow/lite/delegates/gpu/common/model_builder_helper.h"
+#include "tensorflow/lite/delegates/gpu/common/object_reader.h"
+#include "tensorflow/lite/delegates/gpu/common/operation_parser.h"
+#include "tensorflow/lite/delegates/gpu/common/shape.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/tensor.h"
+#include "tensorflow/lite/delegates/gpu/common/types.h"
+
+namespace tflite {
+namespace gpu {
+
+absl::Status LandmarksToTransformMatrixOperationParser::IsSupported(
+ const TfLiteContext* context, const TfLiteNode* tflite_node,
+ const TfLiteRegistration* registration) {
+ RETURN_IF_ERROR(CheckMaxSupportedOpVersion(registration, 2));
+ return CheckInputsOutputs(context, tflite_node, /*runtime_inputs=*/1,
+ /*outputs=*/1);
+}
+
+absl::Status LandmarksToTransformMatrixOperationParser::Parse(
+ const TfLiteNode* tflite_node, const TfLiteRegistration* registration,
+ GraphFloat32* graph, ObjectReader* reader) {
+ Node* node = graph->NewNode();
+ RETURN_IF_ERROR(reader->AddInput(node, 0)); // landmarks
+ RETURN_IF_ERROR(reader->AddOutputs(node)); // transform matrix
+
+ node->operation.type = kLandmarksToTransformMatrixType;
+ BHWC output_shape;
+ if (registration->version == 2) {
+ LandmarksToTransformMatrixV2Attributes attr;
+ RETURN_IF_ERROR(ParseLandmarksToTransformMatrixV2Attributes(
+ tflite_node->custom_initial_data, tflite_node->custom_initial_data_size,
+ &attr, &output_shape));
+ node->operation.attributes = attr;
+ } else if (registration->version == 1) {
+ LandmarksToTransformMatrixV1Attributes attr;
+ RETURN_IF_ERROR(ParseLandmarksToTransformMatrixV1Attributes(
+ tflite_node->custom_initial_data, tflite_node->custom_initial_data_size,
+ &attr, &output_shape));
+ node->operation.attributes = attr;
+ } else {
+ return absl::UnimplementedError(
+ "Landmarks To Transform Matrix operation can be of version 1 or 2 "
+ "only.");
+ }
+
+ auto output_value = graph->FindOutputs(node->id)[0];
+ output_value->tensor.shape = output_shape;
+ return absl::OkStatus();
+}
+
+absl::Status ParseLandmarksToTransformMatrixV1Attributes(
+ const void* data, uint32_t data_size,
+ LandmarksToTransformMatrixV1Attributes* attr, BHWC* output_shape) {
+ const flexbuffers::Map m =
+ flexbuffers::GetRoot(reinterpret_cast<const uint8_t*>(data), data_size)
+ .AsMap();
+
+ const auto input_hw = m["input_hw"].AsTypedVector();
+ attr->input_hw = HW(input_hw[0].AsInt32(), input_hw[1].AsInt32());
+
+ const auto output_hw = m["output_hw"].AsTypedVector();
+ attr->output_hw = HW(output_hw[0].AsInt32(), output_hw[1].AsInt32());
+
+ attr->dimensions = m["dimensions"].AsInt32();
+ attr->landmarks_range = m["landmarks_range"].AsInt32();
+ attr->bbox_size_multiplier = m["bbox_size_multiplier"].AsFloat();
+ attr->left_rotation_idx = m["left_rotation_idx"].AsInt32();
+ attr->right_rotation_idx = m["right_rotation_idx"].AsInt32();
+
+ const auto subset = m["subset"].AsTypedVector();
+ for (int i = 0; i < subset.size() / 2; i++) {
+ attr->subset.emplace_back(subset[i * 2].AsInt32(),
+ subset[i * 2 + 1].AsInt32());
+ }
+ if (subset.size() % 2 != 0) {
+ attr->subset.emplace_back(subset[subset.size() - 1].AsInt32(),
+ subset[subset.size() - 1].AsInt32());
+ }
+ *output_shape = BHWC(1, 1, 4, 4);
+ return absl::OkStatus();
+}
+
+absl::Status ParseLandmarksToTransformMatrixV2Attributes(
+ const void* data, uint32_t data_size,
+ LandmarksToTransformMatrixV2Attributes* attr, BHWC* output_shape) {
+ const flexbuffers::Map m =
+ flexbuffers::GetRoot(reinterpret_cast<const uint8_t*>(data), data_size)
+ .AsMap();
+ const auto subset_idxs = m["subset_idxs"].AsTypedVector();
+ int amount = subset_idxs.size();
+ for (int i = 0; i < amount / 2; i++) {
+ attr->subset_idxs.emplace_back(subset_idxs[i * 2].AsInt32(),
+ subset_idxs[i * 2 + 1].AsInt32());
+ }
+ if (amount % 2 != 0) {
+ int previous = amount - 1;
+ attr->subset_idxs.emplace_back(subset_idxs[previous].AsInt32(),
+ subset_idxs[previous].AsInt32());
+ }
+ attr->left_rotation_idx = m["left_rotation_idx"].AsInt32();
+ attr->right_rotation_idx = m["right_rotation_idx"].AsInt32();
+ attr->target_rotation_radians = m["target_rotation_radians"].AsFloat();
+ attr->output_height = m["output_height"].AsInt32();
+ attr->output_width = m["output_width"].AsInt32();
+ attr->scale_x = m["scale_x"].AsFloat();
+ attr->scale_y = m["scale_y"].AsFloat();
+
+ *output_shape = BHWC(1, 1, 4, 4);
+ return absl::OkStatus();
+}
+
+TransformResult LandmarksToTransformMatrixV2ToV2WithMul::ApplyToNode(
+ Node* node, GraphFloat32* graph) {
+ // Recognize Landmarks2TransformMatrix.v2 as a root operation of this
+ // transformation.
+ if (node->operation.type != kLandmarksToTransformMatrixType) {
+ return {TransformStatus::SKIPPED, ""};
+ }
+ auto* landmarks2tm_attr =
+ absl::any_cast<LandmarksToTransformMatrixV2Attributes>(
+ &node->operation.attributes);
+ if (!landmarks2tm_attr) {
+ return {TransformStatus::SKIPPED, ""};
+ }
+ auto node_inputs = graph->FindInputs(node->id);
+ if (node_inputs.size() != 1) {
+ return {TransformStatus::SKIPPED, ""};
+ }
+ // Recognize preeceding scalar Mul operation and save the value.
+ auto mul = graph->FindProducer(node_inputs[0]->id);
+ if (mul->operation.type != ToString(OperationType::MUL)) {
+ return {TransformStatus::SKIPPED, ""};
+ }
+ const auto& mul_attr =
+ absl::any_cast<const ElementwiseAttributes&>(mul->operation.attributes);
+ float scalar = 0.0;
+ if (!absl::holds_alternative<float>(mul_attr.param)) {
+ return {TransformStatus::SKIPPED, ""};
+ } else {
+ scalar = absl::get<float>(mul_attr.param);
+ }
+ auto mul_inputs = graph->FindInputs(mul->id);
+ if (mul_inputs.size() != 1) {
+ return {TransformStatus::SKIPPED, ""};
+ }
+ // Recognize preceding reshape.
+ auto reshape = graph->FindProducer(mul_inputs[0]->id);
+ if (reshape->operation.type != ToString(OperationType::RESHAPE)) {
+ return {TransformStatus::SKIPPED, ""};
+ }
+ // Start modifying the graph.
+ {
+ absl::Status status = RemoveSimpleNodeKeepInput(graph, reshape);
+ if (!status.ok()) {
+ return {TransformStatus::INVALID,
+ "Unable to remove a node: " + std::string(status.message())};
+ }
+ }
+ {
+ absl::Status status = RemoveSimpleNodeKeepInput(graph, mul);
+ if (!status.ok()) {
+ return {TransformStatus::INVALID,
+ "Unable to remove a node: " + std::string(status.message())};
+ }
+ }
+ // Update LandmarksToTransformMatrix attributes with a stored multiplier.
+ landmarks2tm_attr->multiplier = scalar;
+ return {TransformStatus::APPLIED, ""};
+}
+
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/common/mediapipe/landmarks_to_transform_matrix.h b/tensorflow/lite/delegates/gpu/common/mediapipe/landmarks_to_transform_matrix.h
new file mode 100644
index 00000000000..78c72aea123
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/mediapipe/landmarks_to_transform_matrix.h
@@ -0,0 +1,96 @@
+#ifndef TENSORFLOW_LITE_DELEGATES_GPU_COMMON_MEDIAPIPE_LANDMARKS_TO_TRANSFORM_MATRIX_H_
+#define TENSORFLOW_LITE_DELEGATES_GPU_COMMON_MEDIAPIPE_LANDMARKS_TO_TRANSFORM_MATRIX_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "absl/types/any.h"
+#include "tensorflow/lite/delegates/gpu/common/model.h"
+#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
+#include "tensorflow/lite/delegates/gpu/common/object_reader.h"
+#include "tensorflow/lite/delegates/gpu/common/operation_parser.h"
+#include "tensorflow/lite/delegates/gpu/common/shape.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/types.h"
+
+namespace tflite {
+namespace gpu {
+
+constexpr const char kLandmarksToTransformMatrixType[] =
+ "landmarks_to_transform_matrix";
+
+struct LandmarksToTransformMatrixV1Attributes {
+ int dimensions;
+ int landmarks_range;
+ int left_rotation_idx;
+ int right_rotation_idx;
+ float bbox_size_multiplier;
+ HW input_hw;
+ HW output_hw;
+ std::vector<int2> subset;
+};
+
+struct LandmarksToTransformMatrixV2Attributes {
+ std::vector<int2> subset_idxs;
+ int left_rotation_idx;
+ int right_rotation_idx;
+ float target_rotation_radians;
+ int output_height;
+ int output_width;
+ float scale_x;
+ float scale_y;
+ float multiplier = 1.0;
+};
+
+class LandmarksToTransformMatrixOperationParser : public TFLiteOperationParser {
+ public:
+ absl::Status IsSupported(const TfLiteContext* context,
+ const TfLiteNode* tflite_node,
+ const TfLiteRegistration* registration) final;
+ absl::Status Parse(const TfLiteNode* tflite_node,
+ const TfLiteRegistration* registration,
+ GraphFloat32* graph, ObjectReader* reader) final;
+};
+
+absl::Status ParseLandmarksToTransformMatrixV1Attributes(
+ const void* data, uint32_t data_size,
+ LandmarksToTransformMatrixV1Attributes* attr, BHWC* output_shape);
+
+absl::Status ParseLandmarksToTransformMatrixV2Attributes(
+ const void* data, uint32_t data_size,
+ LandmarksToTransformMatrixV2Attributes* attr, BHWC* output_shape);
+
+// Converts subgraph of Reshape + Mul + Landmarks2TransformMatrix.v2 into
+// Landmarks2TransformMatrix.v2 with multiplier:
+// Source subgraph:
+//
+// Value_0 [1, 1, 1, 30]
+// |
+// Reshape
+// |
+// Value_1 [1, 10, 3]
+// |
+// Mul (* 0.25)
+// |
+// Value_2 [1, 10, 3]
+// |
+// Landmarks2TransformMatrix.v2
+// |
+// Value_3 [1, 1, 4]
+//
+// Resulting subgraph:
+//
+// Value_0 [1, 1, 1, 30]
+// |
+// Landmarks2TransformMatrix.v2
+// |
+// Value_3 [1, 1, 4]
+class LandmarksToTransformMatrixV2ToV2WithMul : public NodeTransformation {
+ public:
+ TransformResult ApplyToNode(Node* node, GraphFloat32* graph) final;
+};
+
+} // namespace gpu
+} // namespace tflite
+
+#endif // TENSORFLOW_LITE_DELEGATES_GPU_COMMON_MEDIAPIPE_LANDMARKS_TO_TRANSFORM_MATRIX_H_
diff --git a/tensorflow/lite/delegates/gpu/common/mediapipe/transform_landmarks.cc b/tensorflow/lite/delegates/gpu/common/mediapipe/transform_landmarks.cc
new file mode 100644
index 00000000000..fba7e742998
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/mediapipe/transform_landmarks.cc
@@ -0,0 +1,169 @@
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/transform_landmarks.h"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "absl/types/any.h"
+#include "flatbuffers/flexbuffers.h"
+#include "tensorflow/lite/delegates/gpu/common/model.h"
+#include "tensorflow/lite/delegates/gpu/common/model_builder_helper.h"
+#include "tensorflow/lite/delegates/gpu/common/object_reader.h"
+#include "tensorflow/lite/delegates/gpu/common/operation_parser.h"
+#include "tensorflow/lite/delegates/gpu/common/shape.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/tensor.h"
+
+namespace tflite {
+namespace gpu {
+
+absl::Status TransformLandmarksOperationParser::IsSupported(
+ const TfLiteContext* context, const TfLiteNode* tflite_node,
+ const TfLiteRegistration* registration) {
+ RETURN_IF_ERROR(CheckMaxSupportedOpVersion(registration, 2));
+ RETURN_IF_ERROR(CheckInputsOutputs(context, tflite_node,
+ /*runtime_inputs=*/2, /*outputs=*/1));
+ return absl::OkStatus();
+}
+
+absl::Status TransformLandmarksOperationParser::Parse(
+ const TfLiteNode* tflite_node, const TfLiteRegistration* registration,
+ GraphFloat32* graph, ObjectReader* reader) {
+ Node* node = graph->NewNode();
+ RETURN_IF_ERROR(reader->AddInput(node, 0)); // data
+ RETURN_IF_ERROR(reader->AddInput(node, 1)); // bbox
+ RETURN_IF_ERROR(reader->AddOutputs(node));
+ node->operation.type = kTransformLandmarksType;
+ BHWC output_shape = graph->FindOutputs(node->id)[0]->tensor.shape;
+ if (registration->version == 2) {
+ TransformLandmarksAttributes attr;
+ RETURN_IF_ERROR(ParseTransformLandmarksV2Attributes(
+ tflite_node->custom_initial_data, tflite_node->custom_initial_data_size,
+ &attr, &output_shape));
+ node->operation.attributes = attr;
+ } else if (registration->version == 1) {
+ TransformLandmarksAttributes attr;
+ RETURN_IF_ERROR(ParseTransformLandmarksV1Attributes(
+ tflite_node->custom_initial_data, tflite_node->custom_initial_data_size,
+ &attr, &output_shape));
+ node->operation.attributes = attr;
+ } else {
+ return absl::UnimplementedError(
+ "Transform Landmarks operation can be of version 1 or 2 only.");
+ }
+
+ auto output_value = graph->FindOutputs(node->id)[0];
+
+ output_value->tensor.shape = graph->FindInputs(node->id)[0]->tensor.shape;
+ return absl::OkStatus();
+}
+
+absl::Status ParseTransformLandmarksV1Attributes(
+ const void* data, uint32_t data_size, TransformLandmarksAttributes* attr,
+ BHWC* output_shape) {
+ attr->version = 1;
+
+ const flexbuffers::Map m =
+ flexbuffers::GetRoot(reinterpret_cast<const uint8_t*>(data), data_size)
+ .AsMap();
+ const flexbuffers::TypedVector keys = m.Keys();
+
+ for (int k = 0; k < keys.size(); ++k) {
+ const std::string key = keys[k].ToString();
+ const auto value = m[key];
+ if (key == "dimensions") {
+ attr->dimensions = value.AsInt32();
+ }
+ if (key == "scale") {
+ attr->scale = value.AsFloat();
+ }
+ }
+ return absl::OkStatus();
+}
+
+absl::Status ParseTransformLandmarksV2Attributes(
+ const void* data, uint32_t data_size, TransformLandmarksAttributes* attr,
+ BHWC* output_shape) {
+ attr->version = 2;
+ attr->dimensions = output_shape->c;
+ attr->scale = 1.0;
+
+ return absl::OkStatus();
+}
+
+TransformResult TransformLandmarksV2ToV1::ApplyToNode(Node* node,
+ GraphFloat32* graph) {
+ // Recognize suitable Transform Landmarks operation.
+ if (node->operation.type != kTransformLandmarksType) {
+ return {TransformStatus::SKIPPED, ""};
+ }
+ TransformLandmarksAttributes transform_landmarks_attr =
+ absl::any_cast<TransformLandmarksAttributes>(node->operation.attributes);
+ if (transform_landmarks_attr.version != 2) {
+ return {TransformStatus::SKIPPED,
+ "Transform Landmarks operation should be of version 2."};
+ }
+
+ // Recognize suitable preceding Reshape.
+ std::vector<Value*> transform_landmarks_inputs = graph->FindInputs(node->id);
+ if (transform_landmarks_inputs.size() != 2) {
+ return {TransformStatus::SKIPPED,
+ "Transform Landmarks operation should have two inputs."};
+ }
+ Value* landmarks_input_tensor = transform_landmarks_inputs[1];
+ if (transform_landmarks_inputs[1]->tensor.shape == BHWC(1, 1, 4, 4)) {
+ landmarks_input_tensor = transform_landmarks_inputs[0];
+ }
+ Node* preceding_reshape = graph->FindProducer(landmarks_input_tensor->id);
+ if (preceding_reshape->operation.type != ToString(OperationType::RESHAPE)) {
+ return {TransformStatus::SKIPPED,
+ "Expected Reshape node to be a producer of the transformation "
+ "matrix input."};
+ }
+
+ // Recognize suitable succeeding Reshape.
+ std::vector<Value*> transform_landmarks_outputs =
+ graph->FindOutputs(node->id);
+ if (transform_landmarks_outputs.size() != 1) {
+ return {TransformStatus::SKIPPED,
+ "Transform Landmarks operation should have one output."};
+ }
+ Value* landmarks_output_tensor = transform_landmarks_outputs[0];
+ std::vector<Node*> landmarks__output_consumers =
+ graph->FindConsumers(landmarks_output_tensor->id);
+ if (landmarks__output_consumers.size() != 1) {
+ return {TransformStatus::SKIPPED,
+ "Transform Landmarks output should be consumed by one operation."};
+ }
+ Node* succeeding_reshape = landmarks__output_consumers[0];
+ if (succeeding_reshape->operation.type != ToString(OperationType::RESHAPE)) {
+ return {TransformStatus::SKIPPED,
+ "Expected Reshape node to be a consumer of the Transform "
+ "Landmarks operation's output value."};
+ }
+
+ // Delete preceding and succeding Reshape operations.
+ absl::Status removed_preceding =
+ RemoveSimpleNodeKeepInput(graph, preceding_reshape);
+ if (!removed_preceding.ok()) {
+ return {TransformStatus::INVALID,
+ "Unable to remove a preceding Reshape node: " +
+ std::string(removed_preceding.message())};
+ }
+ absl::Status removed_succeeding =
+ RemoveSimpleNodeKeepOutput(graph, succeeding_reshape);
+ if (!removed_succeeding.ok()) {
+ return {TransformStatus::INVALID,
+ "Unable to remove a succeeding Reshape node: " +
+ std::string(removed_succeeding.message())};
+ }
+
+ // Switch Transform Landmarks operation back to version 1.
+ transform_landmarks_attr.version = 1;
+ node->operation.attributes = transform_landmarks_attr;
+
+ return {TransformStatus::APPLIED, ""};
+}
+
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/common/mediapipe/transform_landmarks.h b/tensorflow/lite/delegates/gpu/common/mediapipe/transform_landmarks.h
new file mode 100644
index 00000000000..f804e14e55d
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/mediapipe/transform_landmarks.h
@@ -0,0 +1,74 @@
+#ifndef TENSORFLOW_LITE_DELEGATES_GPU_COMMON_MEDIAPIPE_TRANSFORM_LANDMARKS_H_
+#define TENSORFLOW_LITE_DELEGATES_GPU_COMMON_MEDIAPIPE_TRANSFORM_LANDMARKS_H_
+
+#include <cstdint>
+
+#include "absl/types/any.h"
+#include "tensorflow/lite/delegates/gpu/common/model.h"
+#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
+#include "tensorflow/lite/delegates/gpu/common/object_reader.h"
+#include "tensorflow/lite/delegates/gpu/common/operation_parser.h"
+#include "tensorflow/lite/delegates/gpu/common/shape.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+
+namespace tflite {
+namespace gpu {
+
+constexpr const char kTransformLandmarksType[] = "transform_landmarks";
+
+struct TransformLandmarksAttributes {
+ int dimensions = 3;
+ float scale = 1.0;
+ int version = 0;
+};
+
+class TransformLandmarksOperationParser : public TFLiteOperationParser {
+ public:
+ absl::Status IsSupported(const TfLiteContext* context,
+ const TfLiteNode* tflite_node,
+ const TfLiteRegistration* registration) final;
+ absl::Status Parse(const TfLiteNode* tflite_node,
+ const TfLiteRegistration* registration,
+ GraphFloat32* graph, ObjectReader* reader) final;
+};
+
+absl::Status ParseTransformLandmarksV1Attributes(
+ const void* data, uint32_t data_size, TransformLandmarksAttributes* attr,
+ BHWC* output_shape);
+
+absl::Status ParseTransformLandmarksV2Attributes(
+ const void* data, uint32_t data_size, TransformLandmarksAttributes* attr,
+ BHWC* output_shape);
+
+// Removes reshapes from subgraph:
+//
+// Value_0 [1, 1, 1, 240]
+// |
+// Reshape
+// |
+// Value_1 [1, 1, 80, 3] Value_2 [1, 1, 4, 4]
+// \ /
+// TransformLandmarks.version_2
+// |
+// Value_3 [1, 1, 80, 3]
+// |
+// Reshape
+// |
+// Value_4 [1, 1, 1, 240]
+//
+// Resulting subgraph is:
+//
+// Value_0 [1, 1, 1, 240] Value_2 [1, 1, 4, 4]
+// \ /
+// TransformLandmarks.version_1
+// |
+// Value_4 [1, 1, 1, 240]
+class TransformLandmarksV2ToV1 : public NodeTransformation {
+ public:
+ TransformResult ApplyToNode(Node* node, GraphFloat32* graph) final;
+};
+
+} // namespace gpu
+} // namespace tflite
+
+#endif // TENSORFLOW_LITE_DELEGATES_GPU_COMMON_MEDIAPIPE_TRANSFORM_LANDMARKS_H_
diff --git a/tensorflow/lite/delegates/gpu/common/mediapipe/transform_tensor_bilinear.cc b/tensorflow/lite/delegates/gpu/common/mediapipe/transform_tensor_bilinear.cc
new file mode 100644
index 00000000000..704ce7d4a47
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/mediapipe/transform_tensor_bilinear.cc
@@ -0,0 +1,142 @@
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/transform_tensor_bilinear.h"
+
+#include <cstdint>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/types/any.h"
+#include "flatbuffers/flexbuffers.h"
+#include "tensorflow/lite/delegates/gpu/common/model.h"
+#include "tensorflow/lite/delegates/gpu/common/model_builder_helper.h"
+#include "tensorflow/lite/delegates/gpu/common/object_reader.h"
+#include "tensorflow/lite/delegates/gpu/common/operation_parser.h"
+#include "tensorflow/lite/delegates/gpu/common/shape.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/tensor.h"
+
+namespace tflite {
+namespace gpu {
+
+absl::Status TransformTensorBilinearOperationParser::IsSupported(
+ const TfLiteContext* context, const TfLiteNode* tflite_node,
+ const TfLiteRegistration* registration) {
+ RETURN_IF_ERROR(CheckMaxSupportedOpVersion(registration, 2));
+ RETURN_IF_ERROR(CheckInputsOutputs(context, tflite_node,
+ /*runtime_inputs=*/2, /*outputs=*/1));
+ return absl::OkStatus();
+}
+
+absl::Status TransformTensorBilinearOperationParser::Parse(
+ const TfLiteNode* tflite_node, const TfLiteRegistration* registration,
+ GraphFloat32* graph, ObjectReader* reader) {
+ Node* node = graph->NewNode();
+ RETURN_IF_ERROR(reader->AddInput(node, 0)); // data
+ RETURN_IF_ERROR(reader->AddInput(node, 1)); // bbox
+ RETURN_IF_ERROR(reader->AddOutputs(node));
+
+ node->operation.type = kTransformTensorBilinearType;
+ BHWC output_shape;
+ if (registration->version == 2) {
+ TransformTensorBilinearAttributes attr;
+ RETURN_IF_ERROR(ParseTransformTensorBilinearV2Attributes(
+ tflite_node->custom_initial_data, tflite_node->custom_initial_data_size,
+ &attr, &output_shape));
+ node->operation.attributes = attr;
+ } else if (registration->version == 1) {
+ TransformTensorBilinearAttributes attr;
+ RETURN_IF_ERROR(ParseTransformTensorBilinearV1Attributes(
+ tflite_node->custom_initial_data, tflite_node->custom_initial_data_size,
+ &attr, &output_shape));
+ node->operation.attributes = attr;
+ } else {
+ return absl::UnimplementedError(
+ "Transform Tensor Bilinear operation can be of version 1 or 2 only.");
+ }
+
+ auto output_value = graph->FindOutputs(node->id)[0];
+
+ output_value->tensor.shape =
+ BHWC(1, output_shape.h, output_shape.w,
+ graph->FindInputs(node->id)[0]->tensor.shape.c);
+ return absl::OkStatus();
+}
+
+absl::Status ParseTransformTensorBilinearV1Attributes(
+ const void* data, uint32_t data_size,
+ TransformTensorBilinearAttributes* attr, BHWC* output_shape) {
+ attr->version = 1;
+
+ const flexbuffers::Map m =
+ flexbuffers::GetRoot(reinterpret_cast<const uint8_t*>(data), data_size)
+ .AsMap();
+ const flexbuffers::TypedVector keys = m.Keys();
+
+ for (int k = 0; k < keys.size(); ++k) {
+ const std::string key = keys[k].ToString();
+ const auto value = m[key];
+ if (key == "mode") {
+ if (value.AsString().str() != "bilinear") {
+ return absl::UnimplementedError(
+ "TransformTensor operation supports only bilinear interpolation.");
+ }
+ }
+
+ if (key == "output_size") {
+ attr->output_size = HW(value.AsTypedVector()[0].AsInt32(),
+ value.AsTypedVector()[1].AsInt32());
+ }
+ }
+ attr->align_corners = false;
+ *output_shape = BHWC(1, attr->output_size.h, attr->output_size.w, 1);
+ return absl::OkStatus();
+}
+
+absl::Status ParseTransformTensorBilinearV2Attributes(
+ const void* data, uint32_t data_size,
+ TransformTensorBilinearAttributes* attr, BHWC* output_shape) {
+ attr->version = 2;
+
+ const flexbuffers::Map m =
+ flexbuffers::GetRoot(reinterpret_cast<const uint8_t*>(data), data_size)
+ .AsMap();
+ const flexbuffers::TypedVector keys = m.Keys();
+ HW output_size;
+ for (int k = 0; k < keys.size(); ++k) {
+ const std::string key = keys[k].ToString();
+ const auto value = m[key];
+ if (key == "output_height") {
+ output_size.h = value.AsInt32();
+ }
+ if (key == "output_width") {
+ output_size.w = value.AsInt32();
+ }
+ }
+ attr->output_size = std::move(output_size);
+ attr->align_corners = true;
+ *output_shape = BHWC(1, attr->output_size.h, attr->output_size.w, 1);
+ return absl::OkStatus();
+}
+
+TransformResult TransformTensorBilinearV2ToV1::ApplyToNode(
+ Node* node, GraphFloat32* graph) {
+ if (node->operation.type != kTransformTensorBilinearType) {
+ return {TransformStatus::SKIPPED, ""};
+ }
+ TransformTensorBilinearAttributes transform_tensor_attr =
+ absl::any_cast<TransformTensorBilinearAttributes>(
+ node->operation.attributes);
+
+ if (transform_tensor_attr.version != 2) {
+ return {TransformStatus::SKIPPED,
+ "Transform Tensor Bilinear operation should be of version 2."};
+ }
+ transform_tensor_attr.version = 1;
+ transform_tensor_attr.align_corners = true;
+ node->operation.attributes = transform_tensor_attr;
+
+ return {TransformStatus::APPLIED, ""};
+}
+
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/common/mediapipe/transform_tensor_bilinear.h b/tensorflow/lite/delegates/gpu/common/mediapipe/transform_tensor_bilinear.h
new file mode 100644
index 00000000000..8a1f840c12f
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/mediapipe/transform_tensor_bilinear.h
@@ -0,0 +1,54 @@
+#ifndef TENSORFLOW_LITE_DELEGATES_GPU_COMMON_MEDIAPIPE_TRANSFORM_TENSOR_BILINEAR_H_
+#define TENSORFLOW_LITE_DELEGATES_GPU_COMMON_MEDIAPIPE_TRANSFORM_TENSOR_BILINEAR_H_
+
+#include <cstdint>
+
+#include "absl/types/any.h"
+#include "tensorflow/lite/delegates/gpu/common/model.h"
+#include "tensorflow/lite/delegates/gpu/common/model_transformer.h"
+#include "tensorflow/lite/delegates/gpu/common/object_reader.h"
+#include "tensorflow/lite/delegates/gpu/common/operation_parser.h"
+#include "tensorflow/lite/delegates/gpu/common/shape.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+
+namespace tflite {
+namespace gpu {
+
+constexpr const char kTransformTensorBilinearType[] =
+ "transform_tensor_bilinear";
+
+struct TransformTensorBilinearAttributes {
+ HW output_size;
+ bool align_corners = false;
+ int version = 0;
+};
+
+class TransformTensorBilinearOperationParser : public TFLiteOperationParser {
+ public:
+ absl::Status IsSupported(const TfLiteContext* context,
+ const TfLiteNode* tflite_node,
+ const TfLiteRegistration* registration) final;
+ absl::Status Parse(const TfLiteNode* tflite_node,
+ const TfLiteRegistration* registration,
+ GraphFloat32* graph, ObjectReader* reader) final;
+};
+
+absl::Status ParseTransformTensorBilinearV1Attributes(
+ const void* data, uint32_t data_size,
+ TransformTensorBilinearAttributes* attr, BHWC* output_shape);
+
+absl::Status ParseTransformTensorBilinearV2Attributes(
+ const void* data, uint32_t data_size,
+ TransformTensorBilinearAttributes* attr, BHWC* output_shape);
+
+// Converts Transform Tensor Bilinear operation of version 2 to version 1 with
+// align corners parameter set to true.
+class TransformTensorBilinearV2ToV1 : public NodeTransformation {
+ public:
+ TransformResult ApplyToNode(Node* node, GraphFloat32* graph) final;
+};
+
+} // namespace gpu
+} // namespace tflite
+
+#endif // TENSORFLOW_LITE_DELEGATES_GPU_COMMON_MEDIAPIPE_TRANSFORM_TENSOR_BILINEAR_H_
diff --git a/tensorflow/lite/delegates/gpu/common/selectors/BUILD b/tensorflow/lite/delegates/gpu/common/selectors/BUILD
index ec6c2281b9e..26cf9aab1a9 100644
--- a/tensorflow/lite/delegates/gpu/common/selectors/BUILD
+++ b/tensorflow/lite/delegates/gpu/common/selectors/BUILD
@@ -45,9 +45,9 @@ cc_library(
"//tensorflow/lite/delegates/gpu/common:model",
"//tensorflow/lite/delegates/gpu/common:model_hints",
"//tensorflow/lite/delegates/gpu/common:status",
+ "//tensorflow/lite/delegates/gpu/common/selectors/mediapipe:default_selector",
"//tensorflow/lite/delegates/gpu/common/task:gpu_operation",
"//tensorflow/lite/delegates/gpu/common/task:tensor_desc",
- _selectors_package + ":default_selector",
],
)
diff --git a/tensorflow/lite/delegates/gpu/common/selectors/mediapipe/BUILD b/tensorflow/lite/delegates/gpu/common/selectors/mediapipe/BUILD
new file mode 100644
index 00000000000..d5a28d6f72e
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/selectors/mediapipe/BUILD
@@ -0,0 +1,21 @@
+package(
+ default_visibility = ["//visibility:public"],
+ licenses = ["notice"],
+)
+
+cc_library(
+ name = "default_selector",
+ srcs = ["default_selector.cc"],
+ deps = [
+ "//tensorflow/lite/delegates/gpu/common:model",
+ "//tensorflow/lite/delegates/gpu/common:model_hints",
+ "//tensorflow/lite/delegates/gpu/common:operations",
+ "//tensorflow/lite/delegates/gpu/common:status",
+ "//tensorflow/lite/delegates/gpu/common/selectors:subgraph",
+ "//tensorflow/lite/delegates/gpu/common/task:gpu_operation",
+ "//tensorflow/lite/delegates/gpu/common/tasks/mediapipe:landmarks_to_transform_matrix",
+ "//tensorflow/lite/delegates/gpu/common/tasks/mediapipe:transform_landmarks",
+ "//tensorflow/lite/delegates/gpu/common/tasks/mediapipe:transform_tensor_bilinear",
+ "@com_google_absl//absl/strings",
+ ],
+)
diff --git a/tensorflow/lite/delegates/gpu/common/selectors/mediapipe/default_selector.cc b/tensorflow/lite/delegates/gpu/common/selectors/mediapipe/default_selector.cc
new file mode 100644
index 00000000000..9c93149f95b
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/selectors/mediapipe/default_selector.cc
@@ -0,0 +1,48 @@
+#include <memory>
+
+#include "absl/strings/str_cat.h"
+#include "tensorflow/lite/delegates/gpu/common/model.h"
+#include "tensorflow/lite/delegates/gpu/common/model_hints.h"
+#include "tensorflow/lite/delegates/gpu/common/operations.h"
+#include "tensorflow/lite/delegates/gpu/common/selectors/subgraph.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/task/gpu_operation.h"
+#include "tensorflow/lite/delegates/gpu/common/tasks/mediapipe/landmarks_to_transform_matrix.h"
+#include "tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_landmarks.h"
+#include "tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_tensor_bilinear.h"
+
+namespace tflite {
+namespace gpu {
+namespace {
+
+absl::Status CustomGPUOperationFromNode(
+ const GpuInfo& gpu_info, const OperationDef& op_def, ModelHints hints,
+ const std::vector<Value*>& inputs, const std::vector<Value*>& outputs,
+ const Node& node, GPUOperationsSubgraph* gpu_subgraph) {
+ std::unique_ptr<GPUOperation>* gpu_op =
+ InitSingleOpSubgraph(inputs, outputs, gpu_subgraph);
+ if (node.operation.type == kLandmarksToTransformMatrixType) {
+ return CreateLandmarksToTransformMatrixFromNode(op_def, node, gpu_op);
+ }
+ if (node.operation.type == kTransformLandmarksType) {
+ return CreateTransformLandmarksFromNode(op_def, node, gpu_op);
+ }
+ if (node.operation.type == kTransformTensorBilinearType) {
+ return CreateTransformTensorBilinearFromNode(op_def, node, gpu_op);
+ }
+
+ return absl::UnimplementedError(
+ absl::StrCat("No selector for ", node.operation.type));
+}
+} // namespace
+
+absl::Status SelectDefault(const GpuInfo& gpu_info, const OperationDef& op_def,
+ ModelHints hints, const std::vector<Value*>& inputs,
+ const std::vector<Value*>& outputs, const Node& node,
+ GPUOperationsSubgraph* gpu_subgraph) {
+ return CustomGPUOperationFromNode(gpu_info, op_def, hints, inputs, outputs,
+ node, gpu_subgraph);
+}
+
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/BUILD b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/BUILD
new file mode 100644
index 00000000000..9df0735f0eb
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/BUILD
@@ -0,0 +1,39 @@
+package(
+ default_visibility = ["//visibility:public"],
+ licenses = ["notice"],
+)
+
+cc_library(
+ name = "landmarks_to_transform_matrix",
+ srcs = ["landmarks_to_transform_matrix.cc"],
+ hdrs = ["landmarks_to_transform_matrix.h"],
+ deps = [
+ "//tensorflow/lite/delegates/gpu/common:status",
+ "//tensorflow/lite/delegates/gpu/common/mediapipe:landmarks_to_transform_matrix",
+ "//tensorflow/lite/delegates/gpu/common/task:gpu_operation",
+ ],
+)
+
+cc_library(
+ name = "transform_landmarks",
+ srcs = ["transform_landmarks.cc"],
+ hdrs = ["transform_landmarks.h"],
+ deps = [
+ "//tensorflow/lite/delegates/gpu/common:status",
+ "//tensorflow/lite/delegates/gpu/common/mediapipe:transform_landmarks",
+ "//tensorflow/lite/delegates/gpu/common/task:gpu_operation",
+ "//tensorflow/lite/delegates/gpu/common/task:work_group_picking",
+ ],
+)
+
+cc_library(
+ name = "transform_tensor_bilinear",
+ srcs = ["transform_tensor_bilinear.cc"],
+ hdrs = ["transform_tensor_bilinear.h"],
+ deps = [
+ "//tensorflow/lite/delegates/gpu/common:status",
+ "//tensorflow/lite/delegates/gpu/common/mediapipe:transform_tensor_bilinear",
+ "//tensorflow/lite/delegates/gpu/common/task:gpu_operation",
+ "//tensorflow/lite/delegates/gpu/common/task:work_group_picking",
+ ],
+)
diff --git a/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/landmarks_to_transform_matrix.cc b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/landmarks_to_transform_matrix.cc
new file mode 100644
index 00000000000..18f28b19361
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/landmarks_to_transform_matrix.cc
@@ -0,0 +1,368 @@
+#include "tensorflow/lite/delegates/gpu/common/tasks/mediapipe/landmarks_to_transform_matrix.h"
+
+#include <string>
+#include <utility>
+
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+
+namespace tflite {
+namespace gpu {
+namespace {
+
+std::string GetLandmarksToTransformMatrixV1KernelCode(
+ const OperationDef& op_def,
+ const LandmarksToTransformMatrixV1Attributes& attr) {
+ const std::string batch_id = op_def.IsBatchSupported() ? "B" : "";
+ std::string c;
+ c += "#define MAT_MUL_3x3(R0, R1, R2, A0, A1, A2, B0, B1, B2) \\\n";
+ c += " R0.x = A0.x * B0.x + A1.x * B0.y + A2.x * B0.z; \\\n";
+ c += " R0.y = A0.y * B0.x + A1.y * B0.y + A2.y * B0.z; \\\n";
+ c += " R0.z = A0.z * B0.x + A1.z * B0.y + A2.z * B0.z; \\\n";
+ c += " R1.x = A0.x * B1.x + A1.x * B1.y + A2.x * B1.z; \\\n";
+ c += " R1.y = A0.y * B1.x + A1.y * B1.y + A2.y * B1.z; \\\n";
+ c += " R1.z = A0.z * B1.x + A1.z * B1.y + A2.z * B1.z; \\\n";
+ c += " R2.x = A0.x * B2.x + A1.x * B2.y + A2.x * B2.z; \\\n";
+ c += " R2.y = A0.y * B2.x + A1.y * B2.y + A2.y * B2.z; \\\n";
+ c += " R2.z = A0.z * B2.x + A1.z * B2.y + A2.z * B2.z; \n";
+
+ c += "MAIN_FUNCTION($0) {\n";
+ // temporary
+ c += " int dummy_var = GLOBAL_ID_0;\n";
+ if (op_def.IsBatchSupported()) {
+ c += " int B = GLOBAL_ID_0;\n";
+ c += " if (B >= args.dst_tensor.Batch()) return;\n";
+ c += " args.dst_tensor.SetBatchRef(B);\n";
+ c += " args.src_tensor.SetBatchRef(B);\n";
+ }
+ // reads x and y coords only.
+ auto read_landmark = [&](const std::string& result, const std::string& id) {
+ c += " {\n";
+ c += " int start = " + id + " * " + std::to_string(attr.dimensions) +
+ ";\n";
+ c += " int ZC = start / 4;\n";
+ if (attr.dimensions == 2) {
+ c += " float4 t_res = args.src_tensor.Read<float>(0, 0, ZC);\n";
+ c += " " + result + ".xy = t_res.xy;\n";
+ } else if (attr.dimensions == 3) {
+ c += " float4 t_res = args.src_tensor.Read<float>(0, 0, ZC);\n";
+ c += " int rem = start % 4;\n";
+ c += " if (rem == 0) {\n";
+ c += " " + result + ".xy = t_res.xy;\n";
+ c += " } else if (rem == 1) {\n";
+ c += " " + result + ".xy = t_res.yz;\n";
+ c += " } else if (rem == 2) {\n";
+ c += " " + result + ".xy = t_res.zw;\n";
+ c += " } else {\n";
+ c += " float4 t_res_next = args.src_tensor.Read<float>(0, 0, ZC + "
+ "1);\n";
+ c += " " + result + ".xy = INIT_FLOAT2v2(t_res.w, t_res_next.x);\n";
+ c += " }\n";
+ }
+ c += " }\n";
+ };
+ c += " float2 l_pt, r_pt;\n";
+ read_landmark("l_pt", "args.rotations_idx_x");
+ read_landmark("r_pt", "args.rotations_idx_y");
+ c += " float alpha = -atan2(r_pt.y - l_pt.y, r_pt.x - l_pt.x);\n";
+ c += " float cosa = cos(alpha);\n";
+ c += " float sina = sin(alpha);\n";
+ c += " float2 max_value = INIT_FLOAT2v2(-100000.0f, -100000.0f);\n";
+ c += " float2 min_value = INIT_FLOAT2v2(100000.0f, 100000.0f);\n";
+ c += " for (int i = 0; i < args.subset_size; i++) {\n";
+ c += " float2 p0, p1;\n";
+ c += " int2 subset_v = args.subset.Read(i);\n";
+ read_landmark("p0", "subset_v.x");
+ read_landmark("p1", "subset_v.y");
+ c += " // rotation\n";
+ c +=
+ " p0 = INIT_FLOAT2v2(p0.x*cosa - p0.y*sina, p0.x*sina + p0.y*cosa);\n";
+ c +=
+ " p1 = INIT_FLOAT2v2(p1.x*cosa - p1.y*sina, p1.x*sina + p1.y*cosa);\n";
+ c += " max_value.x = max(max(p0.x, p1.x), max_value.x);\n";
+ c += " max_value.y = max(max(p0.y, p1.y), max_value.y);\n";
+ c += " min_value.x = min(min(p0.x, p1.x), min_value.x);\n";
+ c += " min_value.y = min(min(p0.y, p1.y), min_value.y);\n";
+ c += " }\n";
+ c += " float2 bbox_size = (max_value - min_value) * "
+ "args.bbox_size_multiplier;\n";
+ c +=
+ " float3 scale_mat_c0 = INIT_FLOAT3v3(bbox_size.x / args.l_range, 0.0f, "
+ "0.0f);\n";
+ c +=
+ " float3 scale_mat_c1 = INIT_FLOAT3v3(0.0f, bbox_size.y / args.l_range, "
+ "0.0f);\n";
+ c += " float3 scale_mat_c2 = INIT_FLOAT3v3(0.0f, 0.0f, 1.0f);\n";
+ c += " float2 middle = (max_value + min_value) * 0.5f;\n";
+ c += " float2 rotated_middle;\n";
+ c += " float cosnega = cos(-alpha);\n";
+ c += " float sinnega = sin(-alpha);\n";
+ c += " rotated_middle.x = middle.x * cosnega - middle.y * sinnega;\n";
+ c += " rotated_middle.y = middle.x * sinnega + middle.y * cosnega;\n";
+ c += " float3 rot_mat_c0 = INIT_FLOAT3v3(cosnega, sinnega, 0.0f);\n";
+ c += " float3 rot_mat_c1 = INIT_FLOAT3v3(-sinnega, cosnega, 0.0f);\n";
+ c += " float3 rot_mat_c2 = INIT_FLOAT3v3(rotated_middle.x / args.l_range * "
+ "2.0f - "
+ "1.0f, rotated_middle.y / args.l_range * 2.0f - 1.0f, 1.0f);\n";
+ c += " float3 to_relative_c0 = INIT_FLOAT3v3(2.0f / (args.output_size_x - "
+ "1.0f), 0.0f, 0.0f);\n";
+ c += " float3 to_relative_c1 = INIT_FLOAT3v3(0.0f, 2.0f / "
+ "(args.output_size_y - 1.0f), 0.0f);\n";
+ c += " float3 to_relative_c2 = INIT_FLOAT3v3(-1.0f, -1.0f, 1.0f);\n";
+ c += " float3 to_absolute_c0 = INIT_FLOAT3v3((args.input_size_x - 1.0f) / "
+ "2.0f, 0.0f, 0.0f);\n";
+ c += " float3 to_absolute_c1 = INIT_FLOAT3v3(0.0f, (args.input_size_y - "
+ "1.0f) / 2.0f, 0.0f);\n";
+ c += " float3 to_absolute_c2 = INIT_FLOAT3v3((args.input_size_x - 1.0f) / "
+ "2.0f, (args.input_size_y - 1.0f) / 2.0f, 1.0f);\n";
+ c += " float3 t0;\n";
+ c += " float3 t1;\n";
+ c += " float3 t2;\n";
+ c += " // t0 = to_absolute * rotation_matrix\n";
+ c += " MAT_MUL_3x3(t0, t1, t2, to_absolute_c0, to_absolute_c1, "
+ "to_absolute_c2, rot_mat_c0, rot_mat_c1, rot_mat_c2);\n";
+ c += " float3 u0;\n";
+ c += " float3 u1;\n";
+ c += " float3 u2;\n";
+ c += " // u0 = t0 * scale_matrix\n";
+ c += " MAT_MUL_3x3(u0, u1, u2, t0, t1, t2, scale_mat_c0, scale_mat_c1, "
+ "scale_mat_c2);\n";
+ c += " float3 res_c0;\n";
+ c += " float3 res_c1;\n";
+ c += " float3 res_c2;\n";
+ c += " MAT_MUL_3x3(res_c0, res_c1, res_c2, u0, u1, u2, to_relative_c0, "
+ "to_relative_c1, to_relative_c2);\n";
+ c += " FLT4 r0 = INIT_FLT4v4(res_c0.x, res_c1.x, 0.0f, res_c2.x);\n";
+ c += " FLT4 r1 = INIT_FLT4v4(res_c0.y, res_c1.y, 0.0f, res_c2.y);\n";
+ c += " FLT4 r2 = INIT_FLT4v4(res_c0.z, res_c1.z, res_c2.z, 0.0f);\n";
+ c += " FLT4 r3 = INIT_FLT4v4( 0.0f, 0.0f, 0.0f, 1.0f);\n";
+ c += " args.dst_tensor.Write(r0, 0, 0, 0);\n";
+ c += " args.dst_tensor.Write(r1, 1, 0, 0);\n";
+ c += " args.dst_tensor.Write(r2, 2, 0, 0);\n";
+ c += " args.dst_tensor.Write(r3, 3, 0, 0);\n";
+ c += "}\n";
+ return c;
+}
+
+std::string GetLandmarksToTransformMatrixV2KernelCode(
+ const OperationDef& op_def,
+ const LandmarksToTransformMatrixV2Attributes& attr) {
+ std::string c;
+ c += "#define MAT_MUL_3x3(R0, R1, R2, A0, A1, A2, B0, B1, B2) \\\n";
+ c += " R0.x = A0.x * B0.x + A1.x * B0.y + A2.x * B0.z; \\\n";
+ c += " R0.y = A0.y * B0.x + A1.y * B0.y + A2.y * B0.z; \\\n";
+ c += " R0.z = A0.z * B0.x + A1.z * B0.y + A2.z * B0.z; \\\n";
+ c += " R1.x = A0.x * B1.x + A1.x * B1.y + A2.x * B1.z; \\\n";
+ c += " R1.y = A0.y * B1.x + A1.y * B1.y + A2.y * B1.z; \\\n";
+ c += " R1.z = A0.z * B1.x + A1.z * B1.y + A2.z * B1.z; \\\n";
+ c += " R2.x = A0.x * B2.x + A1.x * B2.y + A2.x * B2.z; \\\n";
+ c += " R2.y = A0.y * B2.x + A1.y * B2.y + A2.y * B2.z; \\\n";
+ c += " R2.z = A0.z * B2.x + A1.z * B2.y + A2.z * B2.z; \n";
+
+ c += "MAIN_FUNCTION($0) {\n";
+ // temporary
+ c += " int dummy_var = GLOBAL_ID_0;\n";
+ if (op_def.IsBatchSupported()) {
+ c += " int B = GLOBAL_ID_0;\n";
+ c += " if (B >= args.dst_tensor.Batch()) return;\n";
+ c += " args.dst_tensor.SetBatchRef(B);\n";
+ c += " args.src_tensor.SetBatchRef(B);\n";
+ }
+ // reads x and y coords only.
+ auto read_landmark = [&](const std::string& result, const std::string& id) {
+ c += " {\n";
+ c += " int start = " + id + " * 3; // only 3 dimensional landmarks\n";
+ c += " int ZC = start / 4;\n";
+ c += " float4 t_res = args.src_tensor.Read<float>(0, 0, ZC);\n";
+ c += " int rem = start % 4;\n";
+ c += " if (rem == 0) {\n";
+ c += " " + result + ".xy = t_res.xy;\n";
+ c += " } else if (rem == 1) {\n";
+ c += " " + result + ".xy = t_res.yz;\n";
+ c += " } else if (rem == 2) {\n";
+ c += " " + result + ".xy = t_res.zw;\n";
+ c += " } else {\n";
+ c += " float4 t_res_next = args.src_tensor.Read<float>(0, 0, ZC + "
+ "1);\n";
+ c += " " + result + ".xy = INIT_FLOAT2v2(t_res.w, t_res_next.x);\n";
+ c += " }\n";
+ c += " " + result + " *= args.multiplier;\n";
+ c += " }\n";
+ };
+ c += " float2 left_landmark, right_landmark;\n";
+ read_landmark("left_landmark", "args.left_rotation_idx");
+ read_landmark("right_landmark", "args.right_rotation_idx");
+ c += " float diff_y = right_landmark.y - left_landmark.y;\n";
+ c += " float diff_x = right_landmark.x - left_landmark.x;\n";
+ c += " float rotation = 0.0;\n";
+ c += " if (diff_y != 0.0 && diff_x != 0.0) {"
+ " rotation = atan2(diff_y, diff_x);\n"
+ " }";
+ c += " float r = args.target_rotation_radians - rotation;\n";
+ c += " float cosr = cos(r);\n";
+ c += " float sinr = sin(r);\n";
+ c += " float2 max_value = INIT_FLOAT2v2(-100000.0f, -100000.0f);\n";
+ c += " float2 min_value = INIT_FLOAT2v2(100000.0f, 100000.0f);\n";
+ c += " for (int i = 0; i < args.subset_idxs_size; i++) {\n";
+ c += " float2 p0, p1;\n";
+ c += " int2 subset_idxs_v = args.subset_idxs.Read(i);\n";
+ read_landmark("p0", "subset_idxs_v.x");
+ read_landmark("p1", "subset_idxs_v.y");
+ c += " // rotation\n";
+ c +=
+ " p0 = INIT_FLOAT2v2(p0.x*cosr - p0.y*sinr, p0.x*sinr + p0.y*cosr);\n";
+ c +=
+ " p1 = INIT_FLOAT2v2(p1.x*cosr - p1.y*sinr, p1.x*sinr + p1.y*cosr);\n";
+ c += " max_value.x = max(max(p0.x, p1.x), max_value.x);\n";
+ c += " max_value.y = max(max(p0.y, p1.y), max_value.y);\n";
+ c += " min_value.x = min(min(p0.x, p1.x), min_value.x);\n";
+ c += " min_value.y = min(min(p0.y, p1.y), min_value.y);\n";
+ c += " }\n";
+ c += " float crop_width = max_value.x - min_value.x;\n";
+ c += " float crop_height = max_value.y - min_value.y;\n";
+ c += " float2 crop_xy1 = (max_value + min_value) / 2.0f;\n";
+ c += " float crop_x = cos(-r) * crop_xy1.x - sin(-r) * crop_xy1.y;\n";
+ c += " float crop_y = sin(-r) * crop_xy1.x + cos(-r) * crop_xy1.y;\n";
+ c += " float3 shift_c0 = INIT_FLOAT3v3(1.0, 0.0, 0.0);\n";
+ c += " float3 shift_c1 = INIT_FLOAT3v3(0.0, 1.0, 0.0);\n";
+ c += " float3 shift_c2 = INIT_FLOAT3v3(crop_x, crop_y, 1.0);\n";
+ c += " r = -r;\n";
+ c += " float3 rotation_c0 = INIT_FLOAT3v3(cos(r), sin(r), 0.0);\n";
+ c += " float3 rotation_c1 = INIT_FLOAT3v3(-sin(r), cos(r), 0.0);\n";
+ c += " float3 rotation_c2 = INIT_FLOAT3v3(0.0, 0.0, 1.0);\n";
+ c += " float3 t0;\n";
+ c += " float3 t1;\n";
+ c += " float3 t2;\n";
+ c += " MAT_MUL_3x3(t0, t1, t2, shift_c0, shift_c1, shift_c2, "
+ " rotation_c0, rotation_c1, rotation_c2);\n";
+ c += " float cs_x = args.scale_x * crop_width / args.output_width;\n";
+ c += " float cs_y = args.scale_y * crop_height / args.output_height;\n";
+ c += " float3 scale_c0 = INIT_FLOAT3v3(cs_x, 0.0, 0.0);\n";
+ c += " float3 scale_c1 = INIT_FLOAT3v3(0.0, cs_y, 0.0);\n";
+ c += " float3 scale_c2 = INIT_FLOAT3v3(0.0, 0.0, 1.0);\n";
+ c += " MAT_MUL_3x3(t0, t1, t2, t0, t1, t2, "
+ " scale_c0, scale_c1, scale_c2);\n";
+ c += " float shift_x = -1.0 * (args.output_width / 2.0);\n";
+ c += " float shift_y = -1.0 * (args.output_height / 2.0);\n";
+ c += " float3 shift2_c0 = INIT_FLOAT3v3(1.0, 0.0, 0.0);\n";
+ c += " float3 shift2_c1 = INIT_FLOAT3v3(0.0, 1.0, 0.0);\n";
+ c += " float3 shift2_c2 = INIT_FLOAT3v3(shift_x, shift_y, 1.0);\n";
+ c += " MAT_MUL_3x3(t0, t1, t2, t0, t1, t2, "
+ " shift2_c0, shift2_c1, shift2_c2);\n";
+ c += " FLT4 r0 = INIT_FLT4v4(t0.x, t1.x, 0.0f, t2.x);\n";
+ c += " FLT4 r1 = INIT_FLT4v4(t0.y, t1.y, 0.0f, t2.y);\n";
+ c += " FLT4 r2 = INIT_FLT4v4(t0.z, t1.z, t2.z, 0.0f);\n";
+ c += " FLT4 r3 = INIT_FLT4v4(0.0f, 0.0f, 0.0f, 1.0f);\n";
+ c += " args.dst_tensor.Write(r0, 0, 0, 0);\n";
+ c += " args.dst_tensor.Write(r1, 1, 0, 0);\n";
+ c += " args.dst_tensor.Write(r2, 2, 0, 0);\n";
+ c += " args.dst_tensor.Write(r3, 3, 0, 0);\n";
+ c += "}\n";
+ return c;
+}
+
+} // namespace
+
+absl::Status CreateLandmarksToTransformMatrixFromNode(
+ const OperationDef& op_def, const Node& node,
+ std::unique_ptr<GPUOperation>* gpu_op) {
+ auto* attr_v1 = absl::any_cast<LandmarksToTransformMatrixV1Attributes>(
+ &node.operation.attributes);
+ if (attr_v1) {
+ GPUOperation operation =
+ CreateLandmarksToTransformMatrixV1(op_def, *attr_v1);
+ *gpu_op = absl::make_unique<GPUOperation>(std::move(operation));
+ return absl::OkStatus();
+ }
+ auto* attr_v2 = absl::any_cast<LandmarksToTransformMatrixV2Attributes>(
+ &node.operation.attributes);
+ if (attr_v2) {
+ GPUOperation operation =
+ CreateLandmarksToTransformMatrixV2(op_def, *attr_v2);
+ *gpu_op = absl::make_unique<GPUOperation>(std::move(operation));
+ return absl::OkStatus();
+ }
+ return absl::InvalidArgumentError(
+ "Landmarks To Transform Matrix operation supports only version 1 or "
+ "2.");
+}
+
+GPUOperation CreateLandmarksToTransformMatrixV1(
+ const OperationDef& definition,
+ const LandmarksToTransformMatrixV1Attributes& attr) {
+ std::vector<int32_t> data(attr.subset.size() * 2);
+ for (int i = 0; i < attr.subset.size(); ++i) {
+ data[i * 2 + 0] = attr.subset[i].x;
+ data[i * 2 + 1] = attr.subset[i].y;
+ }
+
+ BufferDescriptor desc;
+ desc.element_type = DataType::INT32;
+ desc.element_size = 2;
+ desc.memory_type = MemoryType::GLOBAL;
+ desc.size = attr.subset.size() * sizeof(int32_t) * 2;
+ desc.data.resize(desc.size);
+ memcpy(desc.data.data(), data.data(), desc.size);
+
+ GPUOperation result(definition);
+ result.AddSrcTensor("src_tensor", definition.src_tensors[0]);
+ result.AddDstTensor("dst_tensor", definition.dst_tensors[0]);
+ result.args_.AddFloat("l_range", attr.landmarks_range);
+ result.args_.AddFloat("bbox_size_multiplier", attr.bbox_size_multiplier);
+ result.args_.AddInt("rotations_idx_x", attr.left_rotation_idx);
+ result.args_.AddInt("rotations_idx_y", attr.right_rotation_idx);
+ result.args_.AddFloat("input_size_x", attr.input_hw.w);
+ result.args_.AddFloat("input_size_y", attr.input_hw.h);
+ result.args_.AddFloat("output_size_x", attr.output_hw.w);
+ result.args_.AddFloat("output_size_y", attr.output_hw.h);
+ result.args_.AddInt("subset_size", attr.subset.size());
+ result.args_.AddObject("subset",
+ absl::make_unique<BufferDescriptor>(std::move(desc)));
+ result.code_ = GetLandmarksToTransformMatrixV1KernelCode(definition, attr);
+ result.work_group_size_ = int3(1, 1, 1);
+ result.tensor_to_grid_ = TensorToGrid::kBToX_YIs1_ZIs1;
+
+ return result;
+}
+
+GPUOperation CreateLandmarksToTransformMatrixV2(
+ const OperationDef& definition,
+ const LandmarksToTransformMatrixV2Attributes& attr) {
+ std::vector<int32_t> data(attr.subset_idxs.size() * 2);
+ for (int i = 0; i < attr.subset_idxs.size(); ++i) {
+ data[i * 2 + 0] = attr.subset_idxs[i].x;
+ data[i * 2 + 1] = attr.subset_idxs[i].y;
+ }
+
+ BufferDescriptor desc;
+ desc.element_type = DataType::INT32;
+ desc.element_size = 2;
+ desc.memory_type = MemoryType::GLOBAL;
+ desc.size = attr.subset_idxs.size() * sizeof(int32_t) * 2;
+ desc.data.resize(desc.size);
+ memcpy(desc.data.data(), data.data(), desc.size);
+
+ GPUOperation result(definition);
+ result.AddSrcTensor("src_tensor", definition.src_tensors[0]);
+ result.AddDstTensor("dst_tensor", definition.dst_tensors[0]);
+
+ result.args_.AddInt("left_rotation_idx", attr.left_rotation_idx);
+ result.args_.AddInt("right_rotation_idx", attr.right_rotation_idx);
+ result.args_.AddFloat("target_rotation_radians",
+ attr.target_rotation_radians);
+ result.args_.AddFloat("output_height", attr.output_height);
+ result.args_.AddFloat("output_width", attr.output_width);
+ result.args_.AddFloat("scale_x", attr.scale_x);
+ result.args_.AddFloat("scale_y", attr.scale_y);
+ result.args_.AddFloat("multiplier", attr.multiplier);
+
+ result.args_.AddInt("subset_idxs_size", attr.subset_idxs.size());
+ result.args_.AddObject("subset_idxs",
+ absl::make_unique<BufferDescriptor>(std::move(desc)));
+ result.code_ = GetLandmarksToTransformMatrixV2KernelCode(definition, attr);
+ result.work_group_size_ = int3(1, 1, 1);
+ result.tensor_to_grid_ = TensorToGrid::kBToX_YIs1_ZIs1;
+ return result;
+}
+
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/landmarks_to_transform_matrix.h b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/landmarks_to_transform_matrix.h
new file mode 100644
index 00000000000..2fd523df7c7
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/landmarks_to_transform_matrix.h
@@ -0,0 +1,26 @@
+#ifndef TENSORFLOW_LITE_DELEGATES_GPU_COMMON_TASKS_MEDIAPIPELANDMARKS_TO_TRANSFORM_MATRIX_H_
+#define TENSORFLOW_LITE_DELEGATES_GPU_COMMON_TASKS_MEDIAPIPELANDMARKS_TO_TRANSFORM_MATRIX_H_
+
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/landmarks_to_transform_matrix.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/task/gpu_operation.h"
+
+namespace tflite {
+namespace gpu {
+
+absl::Status CreateLandmarksToTransformMatrixFromNode(
+ const OperationDef& op_def, const Node& node,
+ std::unique_ptr<GPUOperation>* gpu_op);
+
+GPUOperation CreateLandmarksToTransformMatrixV1(
+ const OperationDef& definition,
+ const LandmarksToTransformMatrixV1Attributes& attr);
+
+GPUOperation CreateLandmarksToTransformMatrixV2(
+ const OperationDef& definition,
+ const LandmarksToTransformMatrixV2Attributes& attr);
+
+} // namespace gpu
+} // namespace tflite
+
+#endif // TENSORFLOW_LITE_DELEGATES_GPU_COMMON_TASKS_MEDIAPIPELANDMARKS_TO_TRANSFORM_MATRIX_H_
diff --git a/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_landmarks.cc b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_landmarks.cc
new file mode 100644
index 00000000000..999917a9251
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_landmarks.cc
@@ -0,0 +1,116 @@
+#include "tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_landmarks.h"
+
+#include <string>
+
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/task/work_group_picking.h"
+
+namespace tflite {
+namespace gpu {
+namespace {
+
+std::string GetTransformLandmarksKernelCode(const OperationDef& op_def,
+ int dimension, float scale) {
+ std::string c;
+ c += "MAIN_FUNCTION($0) {\n";
+ if (op_def.IsBatchSupported()) {
+ c += " int linear_id = GLOBAL_ID_0;\n";
+ c += " int X = linear_id / args.dst_tensor.Batch();\n";
+ c += " int B = linear_id % args.dst_tensor.Batch();\n";
+ c += " args.dst_tensor.SetBatchRef(B);\n";
+ c += " args.matrix_transform.SetBatchRef(B);\n";
+ c += " args.src_tensor.SetBatchRef(B);\n";
+ } else {
+ c += " int X = GLOBAL_ID_0;\n";
+ }
+ c += " int Y = GLOBAL_ID_1;\n";
+ c += " int Z = GLOBAL_ID_2;\n";
+ c += " if (X >= args.dst_tensor.Width() || Y >= args.dst_tensor.Height() || "
+ "Z >= args.dst_tensor.Slices()) "
+ "return;\n";
+ c += " float4 x_transform = args.matrix_transform.Read<float>(0, 0, 0);\n";
+ c += " float4 y_transform = args.matrix_transform.Read<float>(1, 0, 0);\n";
+ if (scale != 1.0) {
+ c += " x_transform.w *= args.scale;\n";
+ c += " y_transform.w *= args.scale;\n";
+ }
+ c += " float4 landmks = args.src_tensor.Read<float>(X, Y, Z);\n";
+ c += " float4 result = INIT_FLOAT4(0.0f);\n";
+ if (dimension == 2) {
+ c += " float4 l_pair1_ = INIT_FLOAT4v4(landmks.x, landmks.y, 0.0f, "
+ "1.0f);\n";
+ c += " float4 l_pair2_ = INIT_FLOAT4v4(landmks.z, landmks.w, 0.0f, "
+ "1.0f);\n";
+ c += " result.x = dot(x_transform, l_pair1_);\n";
+ c += " result.y = dot(y_transform, l_pair1_);\n";
+ c += " result.z = dot(x_transform, l_pair2_);\n";
+ c += " result.w = dot(y_transform, l_pair2_);\n";
+ } else if (dimension == 3) {
+ c += " int reminder = (Z * 4) % 3;\n";
+ c += " if (reminder == 0) { // 0, 3, 6\n";
+ c += " // x y z x\n";
+ c += " float4 landmks_next = args.src_tensor.Read<float>(X, Y, Z+1);\n";
+ c += " float4 l_= landmks;\n";
+ c += " l_.z = 0.0f;\n";
+ c += " l_.w = 1.0f;\n";
+ c += " result.x = dot(x_transform, l_);\n";
+ c += " result.y = dot(y_transform, l_);\n";
+ c += " result.z = landmks.z;\n";
+ c += " result.w = dot(x_transform, INIT_FLOAT4v4(landmks.w, "
+ "landmks_next.x, "
+ "0.0f, 1.0f));\n";
+ c += " } else if (reminder == 1) { // 1, 4, 7\n";
+ c += " // y z x y\n";
+ c += " float4 landmks_prev = args.src_tensor.Read<float>(X, Y, Z-1);\n";
+ c += " float4 l_ = INIT_FLOAT4v4(landmks.z, landmks.w, 0.0f, 1.0f);\n";
+ c += " result.x = dot(y_transform, INIT_FLOAT4v4(landmks_prev.w, "
+ "landmks.x, "
+ "0.0f, 1.0f));\n";
+ c += " result.y = landmks.y;\n";
+ c += " result.z = dot(x_transform, l_);\n";
+ c += " result.w = dot(y_transform, l_);\n";
+ c += " } else { // reminder == 2; // 2, 5, 8\n";
+ c += " // z, x, y, z\n";
+ c += " float4 l_ = INIT_FLOAT4v4(landmks.y, landmks.z, 0.0f, 1.0f);\n";
+ c += " result.x = landmks.x;\n";
+ c += " result.y = dot(x_transform, l_);\n";
+ c += " result.z = dot(y_transform, l_);\n";
+ c += " result.w = landmks.w;\n";
+ c += " }\n";
+ }
+ c += " FLT4 res = TO_FLT4(result);\n";
+ c += " args.dst_tensor.Write(res, X, Y, Z);\n";
+ c += "}\n";
+ return c;
+}
+} // namespace
+
+absl::Status CreateTransformLandmarksFromNode(
+ const OperationDef& op_def, const Node& node,
+ std::unique_ptr<GPUOperation>* gpu_op) {
+ auto attr =
+ absl::any_cast<TransformLandmarksAttributes>(node.operation.attributes);
+ if (attr.version != 1) {
+ return absl::InvalidArgumentError(
+ "Transform Landmarks operation supports only version 1.");
+ }
+ GPUOperation operation = CreateTransformLandmarks(op_def, attr);
+ *gpu_op = absl::make_unique<GPUOperation>(std::move(operation));
+ return absl::OkStatus();
+}
+
+GPUOperation CreateTransformLandmarks(
+ const OperationDef& definition, const TransformLandmarksAttributes& attr) {
+ GPUOperation op(definition);
+ op.AddSrcTensor("src_tensor", definition.src_tensors[0]);
+ op.AddSrcTensor("matrix_transform", definition.src_tensors[1]);
+ op.AddDstTensor("dst_tensor", definition.dst_tensors[0]);
+ op.args_.AddFloat("scale", attr.scale);
+ op.code_ =
+ GetTransformLandmarksKernelCode(definition, attr.dimensions, attr.scale);
+ op.tensor_to_grid_ = TensorToGrid::kWBToX_HDToY_SToZ;
+ return op;
+}
+
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_landmarks.h b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_landmarks.h
new file mode 100644
index 00000000000..5c0be19033a
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_landmarks.h
@@ -0,0 +1,21 @@
+#ifndef TENSORFLOW_LITE_DELEGATES_GPU_COMMON_TASKS_MEDIAPIPETRANSFORM_LANDMARKS_H_
+#define TENSORFLOW_LITE_DELEGATES_GPU_COMMON_TASKS_MEDIAPIPETRANSFORM_LANDMARKS_H_
+
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/transform_landmarks.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/task/gpu_operation.h"
+
+namespace tflite {
+namespace gpu {
+
+absl::Status CreateTransformLandmarksFromNode(
+ const OperationDef& op_def, const Node& node,
+ std::unique_ptr<GPUOperation>* gpu_op);
+
+GPUOperation CreateTransformLandmarks(const OperationDef& definition,
+ const TransformLandmarksAttributes& attr);
+
+} // namespace gpu
+} // namespace tflite
+
+#endif // TENSORFLOW_LITE_DELEGATES_GPU_COMMON_TASKS_MEDIAPIPETRANSFORM_LANDMARKS_H_
diff --git a/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_tensor_bilinear.cc b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_tensor_bilinear.cc
new file mode 100644
index 00000000000..2723216f324
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_tensor_bilinear.cc
@@ -0,0 +1,123 @@
+#include "tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_tensor_bilinear.h"
+
+#include <string>
+
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/task/work_group_picking.h"
+
+namespace tflite {
+namespace gpu {
+namespace {
+
+std::string AlignCornersCorrection(bool align_corners) {
+ // Align corners correction: T -> S * ( T * A ), where T is a
+ // transformation matrix, and subtruction and addition matrices are:
+ // S A
+ // 1 0 0 -0.5 1 0 0 0.5
+ // 0 1 0 -0.5 0 1 0 0.5
+ // 0 0 1 0 0 0 1 0
+ // 0 0 0 1 0 0 0 1
+ // Transformation matrix column 3 and rows 3, 4 are identity, which makes
+ // the final formula pretty simple and easy to get if doing a manual
+ // multiuplication.
+ return align_corners ? R"(
+ first_line.w += first_line.x * 0.5 + first_line.y * 0.5 - 0.5;
+ second_line.w += second_line.x * 0.5 + second_line.y * 0.5 - 0.5;
+ )"
+ : "";
+}
+
+std::string GetTransformTensorBilinearKernelCode(const OperationDef& op_def,
+ bool align_corners) {
+ std::string c;
+ c += "MAIN_FUNCTION($0) {\n";
+ c += " int Y = GLOBAL_ID_1;\n";
+ c += " int Z = GLOBAL_ID_2;\n";
+ if (op_def.IsBatchSupported()) {
+ c += " int linear_id = GLOBAL_ID_0;\n";
+ c += " int X = linear_id / args.dst_tensor.Batch();\n";
+ c += " int B = linear_id % args.dst_tensor.Batch();\n";
+ c += " args.dst_tensor.SetBatchRef(B);\n";
+ c += " args.matrix_transform.SetBatchRef(B);\n";
+ c += " args.src_tensor.SetBatchRef(B);\n";
+ } else {
+ c += " int X = GLOBAL_ID_0;\n";
+ }
+ c += " if (X >= args.dst_tensor.Width() || Y >= args.dst_tensor.Height() || "
+ "Z >= args.dst_tensor.Slices()) "
+ "return;\n";
+ c += " float4 first_line = args.matrix_transform.Read<float>(0, 0, 0);\n";
+ c += " float4 second_line = args.matrix_transform.Read<float>(1, 0, 0);\n";
+ c += AlignCornersCorrection(align_corners);
+ c += " float4 before_transform_coord_2d = INIT_FLOAT4v4(INIT_FLOAT(X), "
+ "INIT_FLOAT(Y), "
+ "0.0f, 1.0f);\n";
+ c += " // Get transformed coordinates\n";
+ c +=
+ " float2 xy = INIT_FLOAT2v2(dot(first_line, before_transform_coord_2d), "
+ "dot(second_line, before_transform_coord_2d));\n";
+ c += " float2 xy_floor = floor(xy);\n";
+ c += " int4 st;\n";
+ c += " st.xy = INIT_INT2v2(xy_floor.x, xy_floor.y);\n";
+ c += " st.zw = INIT_INT2v2(xy_floor.x, xy_floor.y) + INIT_INT2v2(1, 1);\n";
+ c += " // Apply interpolation if coordinate is in bounds.\n";
+ c += " float4 result = INIT_FLOAT4(0.0f);\n";
+ c += " float2 t = xy - xy_floor;\n";
+ c += " if(xy.x >= 0.0 && xy.x <= INIT_FLOAT(args.src_tensor.Width() - 1) && "
+ "xy.y >= 0.0 && "
+ "xy.y <= INIT_FLOAT(args.src_tensor.Height() - 1)) {\n";
+ c += " float4 p0 = INIT_FLOAT4(0.0f);\n";
+ c += " float4 p1 = INIT_FLOAT4(0.0f);\n";
+ c += " float4 p2 = INIT_FLOAT4(0.0f);\n";
+ c += " float4 p3 = INIT_FLOAT4(0.0f);\n";
+ auto read_src = [&](const std::string& result, const std::string& xc,
+ const std::string& yc, const std::string& zc) {
+ c += " if(" + xc + " >= 0 && " + yc + " >= 0 && " + xc +
+ " < args.src_tensor.Width() && " + yc +
+ " < args.src_tensor.Height()) {\n";
+ c += " " + result + " = args.src_tensor.Read<float>(" + xc + ", " +
+ yc + ", " + zc + ");\n";
+ c += " }\n";
+ };
+ read_src("p0", "st.x", "st.y", "Z");
+ read_src("p1", "st.z", "st.y", "Z");
+ read_src("p2", "st.x", "st.w", "Z");
+ read_src("p3", "st.z", "st.w", "Z");
+ c += " result = mix(mix(p0, p1, t.x), mix(p2, p3, t.x), t.y);\n";
+ c += " }\n";
+ c += " FLT4 res = TO_FLT4(result);\n";
+ c += " args.dst_tensor.Write(res, X, Y, Z);\n";
+ c += "}\n";
+ return c;
+}
+} // namespace
+
+absl::Status CreateTransformTensorBilinearFromNode(
+ const OperationDef& op_def, const Node& node,
+ std::unique_ptr<GPUOperation>* gpu_op) {
+ auto attr = absl::any_cast<TransformTensorBilinearAttributes>(
+ node.operation.attributes);
+ if (attr.version != 1) {
+ return absl::InvalidArgumentError(
+ "Transform Tensor Bilinear operation supports only version 1.");
+ }
+ GPUOperation operation = CreateTransformTensorBilinear(op_def, attr);
+ *gpu_op = absl::make_unique<GPUOperation>(std::move(operation));
+ return absl::OkStatus();
+}
+
+GPUOperation CreateTransformTensorBilinear(
+ const OperationDef& definition,
+ const TransformTensorBilinearAttributes& attr) {
+ GPUOperation op(definition);
+ op.AddSrcTensor("src_tensor", definition.src_tensors[0]);
+ op.AddSrcTensor("matrix_transform", definition.src_tensors[1]);
+ op.AddDstTensor("dst_tensor", definition.dst_tensors[0]);
+ op.code_ =
+ GetTransformTensorBilinearKernelCode(definition, attr.align_corners);
+ op.tensor_to_grid_ = TensorToGrid::kWBToX_HDToY_SToZ;
+ return op;
+}
+
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_tensor_bilinear.h b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_tensor_bilinear.h
new file mode 100644
index 00000000000..0251265cdf4
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/common/tasks/mediapipe/transform_tensor_bilinear.h
@@ -0,0 +1,22 @@
+#ifndef TENSORFLOW_LITE_DELEGATES_GPU_COMMON_TASKS_MEDIAPIPETRANSFORM_TENSOR_BILINEAR_H_
+#define TENSORFLOW_LITE_DELEGATES_GPU_COMMON_TASKS_MEDIAPIPETRANSFORM_TENSOR_BILINEAR_H_
+
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/transform_tensor_bilinear.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/task/gpu_operation.h"
+
+namespace tflite {
+namespace gpu {
+
+absl::Status CreateTransformTensorBilinearFromNode(
+ const OperationDef& op_def, const Node& node,
+ std::unique_ptr<GPUOperation>* gpu_op);
+
+GPUOperation CreateTransformTensorBilinear(
+ const OperationDef& definition,
+ const TransformTensorBilinearAttributes& attr);
+
+} // namespace gpu
+} // namespace tflite
+
+#endif // TENSORFLOW_LITE_DELEGATES_GPU_COMMON_TASKS_MEDIAPIPETRANSFORM_TENSOR_BILINEAR_H_
diff --git a/tensorflow/lite/delegates/gpu/common/transformations/BUILD b/tensorflow/lite/delegates/gpu/common/transformations/BUILD
index d26b4f807de..9596dbab7e6 100644
--- a/tensorflow/lite/delegates/gpu/common/transformations/BUILD
+++ b/tensorflow/lite/delegates/gpu/common/transformations/BUILD
@@ -287,7 +287,7 @@ cc_library(
":merge_padding_with",
":remove_noop",
"//tensorflow/lite/delegates/gpu/common:model_transformer",
- ] + tf_platform_alias("custom_transformations", "//tensorflow/lite/delegates/gpu/common/"),
+ ] + ["//tensorflow/lite/delegates/gpu/common/mediapipe:custom_transformations"],
)
cc_library(
diff --git a/tensorflow/lite/delegates/gpu/gl/kernels/BUILD b/tensorflow/lite/delegates/gpu/gl/kernels/BUILD
index b7860b44ede..30cc160d32c 100644
--- a/tensorflow/lite/delegates/gpu/gl/kernels/BUILD
+++ b/tensorflow/lite/delegates/gpu/gl/kernels/BUILD
@@ -153,10 +153,11 @@ cc_test(
cc_library(
name = "custom_registry",
- srcs = ["custom_registry.cc"],
+ srcs = ["//tensorflow/lite/delegates/gpu/gl/kernels/mediapipe:registry.cc"],
hdrs = ["custom_registry.h"],
deps = [
"//tensorflow/lite/delegates/gpu/gl:node_shader",
+ "//tensorflow/lite/delegates/gpu/gl/kernels/mediapipe:all_custom_ops",
"@com_google_absl//absl/container:flat_hash_map",
],
)
diff --git a/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/BUILD b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/BUILD
new file mode 100644
index 00000000000..f5e696d0859
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/BUILD
@@ -0,0 +1,85 @@
+load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite")
+
+package(
+ default_visibility = ["//visibility:public"],
+ licenses = ["notice"],
+)
+
+exports_files([
+ "registry.cc",
+ "landmarks_to_transform_matrix.h",
+ "transform_landmarks.h",
+ "transform_tensor_bilinear.h",
+])
+
+cc_library(
+ name = "all_custom_ops",
+ hdrs = [
+ "landmarks_to_transform_matrix.h",
+ "transform_landmarks.h",
+ "transform_tensor_bilinear.h",
+ ],
+ deps = [
+ ":landmarks_to_transform_matrix",
+ ":transform_landmarks",
+ ":transform_tensor_bilinear",
+ "//tensorflow/lite/delegates/gpu/common:operations",
+ "//tensorflow/lite/delegates/gpu/gl:node_shader",
+ ],
+)
+
+cc_library(
+ name = "landmarks_to_transform_matrix",
+ srcs = ["landmarks_to_transform_matrix.cc"],
+ hdrs = ["landmarks_to_transform_matrix.h"],
+ deps = [
+ "//tensorflow/lite/delegates/gpu/common:operations",
+ "//tensorflow/lite/delegates/gpu/common:shape",
+ "//tensorflow/lite/delegates/gpu/common:status",
+ "//tensorflow/lite/delegates/gpu/common:types",
+ "//tensorflow/lite/delegates/gpu/common:util",
+ "//tensorflow/lite/delegates/gpu/common/mediapipe:landmarks_to_transform_matrix",
+ "//tensorflow/lite/delegates/gpu/gl:node_shader",
+ "@com_google_absl//absl/memory",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/types:any",
+ ],
+)
+
+cc_library(
+ name = "transform_tensor_bilinear",
+ srcs = ["transform_tensor_bilinear.cc"],
+ hdrs = ["transform_tensor_bilinear.h"],
+ deps = [
+ "//tensorflow/lite/delegates/gpu/common:operations",
+ "//tensorflow/lite/delegates/gpu/common:shape",
+ "//tensorflow/lite/delegates/gpu/common:status",
+ "//tensorflow/lite/delegates/gpu/common:types",
+ "//tensorflow/lite/delegates/gpu/common:util",
+ "//tensorflow/lite/delegates/gpu/common/mediapipe:transform_tensor_bilinear",
+ "//tensorflow/lite/delegates/gpu/gl:node_shader",
+ "@com_google_absl//absl/memory",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/types:any",
+ ],
+)
+
+cc_library(
+ name = "transform_landmarks",
+ srcs = ["transform_landmarks.cc"],
+ hdrs = ["transform_landmarks.h"],
+ deps = [
+ "//tensorflow/lite/delegates/gpu/common:operations",
+ "//tensorflow/lite/delegates/gpu/common:shape",
+ "//tensorflow/lite/delegates/gpu/common:status",
+ "//tensorflow/lite/delegates/gpu/common:types",
+ "//tensorflow/lite/delegates/gpu/common:util",
+ "//tensorflow/lite/delegates/gpu/common/mediapipe:transform_landmarks",
+ "//tensorflow/lite/delegates/gpu/gl:node_shader",
+ "@com_google_absl//absl/memory",
+ "@com_google_absl//absl/strings",
+ "@com_google_absl//absl/types:any",
+ ],
+)
+
+tflite_portable_test_suite()
diff --git a/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/landmarks_to_transform_matrix.cc b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/landmarks_to_transform_matrix.cc
new file mode 100644
index 00000000000..de75dd7df2e
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/landmarks_to_transform_matrix.cc
@@ -0,0 +1,356 @@
+#include "tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/landmarks_to_transform_matrix.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "absl/memory/memory.h"
+#include "absl/strings/substitute.h"
+#include "absl/types/any.h"
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/landmarks_to_transform_matrix.h"
+#include "tensorflow/lite/delegates/gpu/common/shape.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/types.h"
+#include "tensorflow/lite/delegates/gpu/common/util.h"
+
+namespace tflite {
+namespace gpu {
+namespace gl {
+namespace {
+
+namespace v1 {
+
+std::string ReadLandmark(const std::string& landmark, const std::string& idx) {
+ std::string source = R"(
+ vec4 )" + landmark +
+ R"(;
+ {
+ int z_coord = )" +
+ idx +
+ R"( * $dimensions$ / 4;
+ vec4 result = $input_data_0[0, 0, z_coord]$;
+ int rest = )" + idx +
+ R"( * $dimensions$ % 4;
+ if (rest != 0) {
+ if (rest == 1) {
+ result.x = result.y;
+ result.y = result.z;
+ }
+ if (rest == 2) {
+ result.x = result.z;
+ result.y = result.w;
+ }
+ if (rest == 3) {
+ vec4 next_after_result = $input_data_0[0, 0, z_coord + 1]$;
+ result.x = result.w;
+ result.y = next_after_result.x;
+ }
+ }
+ )" + landmark + R"( = result;
+ }
+ )";
+ return source;
+}
+
+bool IsSupported(const LandmarksToTransformMatrixV1Attributes& attr) {
+ return attr.dimensions == 3;
+}
+
+absl::Status GenerateCode(const LandmarksToTransformMatrixV1Attributes& attr,
+ const NodeShader::GenerationContext& ctx,
+ GeneratedCode* generated_code) {
+ if (!IsSupported(attr)) {
+ return absl::InvalidArgumentError(
+ "This case is not supported by LandmarksToTransformMatrix v1");
+ }
+
+ std::vector<Variable> params = {
+ {"dimensions", static_cast<int>(attr.dimensions)},
+ {"landmarks_range", static_cast<int>(attr.landmarks_range)},
+ {"left_rotation_idx", static_cast<int>(attr.left_rotation_idx)},
+ {"right_rotation_idx", static_cast<int>(attr.right_rotation_idx)},
+ {"bbox_size_multiplier", static_cast<float>(attr.bbox_size_multiplier)},
+ {"input_h", static_cast<int>(attr.input_hw.h)},
+ {"input_w", static_cast<int>(attr.input_hw.w)},
+ {"output_h", static_cast<int>(attr.output_hw.h)},
+ {"output_w", static_cast<int>(attr.output_hw.w)},
+ {"subset", attr.subset},
+ {"subset_size", static_cast<int>(attr.subset.size())},
+ };
+
+ std::string source = R"(
+ )" + ReadLandmark("left_landmark", "$left_rotation_idx$") +
+ R"(
+
+ )" + ReadLandmark("right_landmark", "$right_rotation_idx$") +
+ R"(
+
+ float alpha = -atan(right_landmark.y - left_landmark.y,
+ right_landmark.x - left_landmark.x);
+
+ vec4 max_value = vec4(-100000, -100000, 0.0, 0.0);
+ vec4 min_value = vec4(100000, 100000, 0.0, 0.0);
+ for (int i = 0; i < $subset_size$; i++) {
+ for (int j = 0; j < 2; j++) {
+ )" + ReadLandmark("landmark_current", "$subset$[i][j]") +
+ R"(
+
+ vec4 rotated = vec4(landmark_current.x * cos(alpha) -
+ landmark_current.y * sin(alpha),
+ landmark_current.x * sin(alpha) +
+ landmark_current.y * cos(alpha),
+ 0.0, 0.0);
+ // both by x and y
+ max_value = vec4(max(max_value.x, rotated.x),
+ max(max_value.y, rotated.y),
+ 0.0, 0.0);
+ min_value = vec4(min(min_value.x, rotated.x),
+ min(min_value.y, rotated.y),
+ 0.0, 0.0);
+ }
+ }
+
+ vec4 bbox_size = max_value - min_value;
+ bbox_size *= $bbox_size_multiplier$;
+
+ mat3 scale_matrix =
+ mat3(bbox_size.x / float($landmarks_range$), 0.0, 0.0, // first column
+ 0.0, bbox_size.y / float($landmarks_range$), 0.0, // second column
+ 0.0, 0.0, 1.0); // third column
+
+ vec4 middle = (max_value + min_value) / 2.0;
+
+ vec4 rotated_middle =
+ vec4(middle.x * cos(-alpha) - middle.y * sin(-alpha),
+ middle.x * sin(-alpha) + middle.y * cos(-alpha), 0.0, 0.0);
+
+ mat3 rotation_matrix =
+ mat3(cos(-alpha), sin(-alpha), 0, // first column
+ -sin(-alpha), cos(-alpha), 0, // second column
+ // third column
+ (rotated_middle.x / float($landmarks_range$)) * 2.0 - 1.0,
+ (rotated_middle.y / float($landmarks_range$)) * 2.0 - 1.0, 1);
+
+ mat3 to_relative =
+ mat3(2.0 / (float($output_w$) - 1.0), 0.0, 0.0, // first column
+ 0.0, 2.0 / (float($output_h$) - 1.0), 0.0, // second column
+ -1.0, -1.0, 1.0); // third column
+
+ mat3 to_absolute =
+ mat3((float($input_w$) - 1.0) / 2.0, 0.0, 0.0, // first column
+ 0.0, (float($input_h$) - 1.0) / 2.0, 0.0, // second column
+ // third column
+ (float($input_w$) - 1.0) / 2.0, (float($input_h$) - 1.0)/2.0, 1.0);
+
+ // Transformstion Matrix
+ mat3 tm = to_absolute * rotation_matrix * scale_matrix * to_relative;
+
+ // Inverse Transformation Matrix
+ $output_data_0[0, 0, 0] = vec4(tm[0][0], tm[1][0], 0.0, tm[2][0])$;
+ $output_data_0[1, 0, 0] = vec4(tm[0][1], tm[1][1], 0.0, tm[2][1])$;
+ $output_data_0[2, 0, 0] = vec4(tm[0][2], tm[1][2], tm[2][2], 0.0)$;
+ $output_data_0[3, 0, 0] = vec4( 0, 0, 0, 1.0)$;
+ )";
+
+ *generated_code = {
+ /*parameters=*/params,
+ /*objects=*/{},
+ /*shared_variables=*/{},
+ /*workload=*/uint3(1, 1, 1),
+ /*workgroup=*/uint3(1, 1, 1),
+ /*source_code=*/std::move(source),
+ /*input=*/IOStructure::ONLY_DEFINITIONS,
+ /*output=*/IOStructure::ONLY_DEFINITIONS,
+ };
+ return absl::OkStatus();
+}
+
+} // namespace v1
+
+namespace v2 {
+
+std::string ReadLandmark(const std::string& landmark, const std::string& idx) {
+ std::string source = R"(
+ vec4 )" + landmark +
+ R"(;
+ {
+ int z_coord = )" +
+ idx +
+ R"( * $dimensions$ / 4;
+ vec4 result = $input_data_0[0, 0, z_coord]$;
+ int rest = )" + idx +
+ R"( * $dimensions$ % 4;
+ if (rest != 0) {
+ if (rest == 1) {
+ result.x = result.y;
+ result.y = result.z;
+ }
+ if (rest == 2) {
+ result.x = result.z;
+ result.y = result.w;
+ }
+ if (rest == 3) {
+ vec4 next_after_result = $input_data_0[0, 0, z_coord + 1]$;
+ result.x = result.w;
+ result.y = next_after_result.x;
+ }
+ }
+ result *= $multiplier$;
+ )" + landmark + R"( = result;
+ } )";
+ return source;
+}
+
+static bool IsSupported(const NodeShader::GenerationContext& ctx) {
+ return ctx.input_shapes.size() == 1 && ctx.input_shapes[0][1] == 1 &&
+ ctx.input_shapes[0][2] == 1 && ctx.input_shapes[0][3] % 3 == 0;
+}
+
+absl::Status GenerateCode(const LandmarksToTransformMatrixV2Attributes& attr,
+ const NodeShader::GenerationContext& ctx,
+ GeneratedCode* generated_code) {
+ if (!IsSupported(ctx)) {
+ return absl::InvalidArgumentError(
+ "This case is not supported by LandmarksToTransformMatrixV2");
+ }
+
+ std::vector<Variable> params = {
+ {"dimensions", static_cast<int>(3)},
+ {"scale_x", static_cast<float>(attr.scale_x)},
+ {"scale_y", static_cast<float>(attr.scale_y)},
+ {"left_rotation_idx", static_cast<int>(attr.left_rotation_idx)},
+ {"right_rotation_idx", static_cast<int>(attr.right_rotation_idx)},
+ {"target_rotation_radians",
+ static_cast<float>(attr.target_rotation_radians)},
+ {"output_width", static_cast<float>(attr.output_width)},
+ {"output_height", static_cast<float>(attr.output_height)},
+ {"subset_idxs", attr.subset_idxs},
+ {"subset_idxs_size", static_cast<int>(attr.subset_idxs.size())},
+ {"multiplier", static_cast<float>(attr.multiplier)},
+ };
+
+ std::string source = R"(
+ )" + ReadLandmark("left_landmark", "$left_rotation_idx$") +
+ R"(
+ )" + ReadLandmark("right_landmark", "$right_rotation_idx$") +
+ R"(
+
+ float diff_y = right_landmark.y - left_landmark.y;
+ float diff_x = right_landmark.x - left_landmark.x;
+ float rotation = 0.0;
+ if (diff_y != 0.0 && diff_x != 0.0) rotation = atan(diff_y, diff_x);
+ float r = $target_rotation_radians$ - rotation;
+
+ vec4 max_value = vec4(-100000, -100000, 0.0, 0.0);
+ vec4 min_value = vec4(100000, 100000, 0.0, 0.0);
+ for (int i = 0; i < $subset_idxs_size$; i++) {
+ for (int j = 0; j < 2; j++) {
+ )" + ReadLandmark("landmark_current", "$subset_idxs$[i][j]") +
+ R"(
+ vec4 rotated = vec4(landmark_current.x * cos(r) -
+ landmark_current.y * sin(r),
+ landmark_current.x * sin(r) +
+ landmark_current.y * cos(r),
+ 0.0, 0.0);
+ // both by x and y
+ max_value = vec4(max(max_value.x, rotated.x),
+ max(max_value.y, rotated.y),
+ 0.0, 0.0);
+ min_value = vec4(min(min_value.x, rotated.x),
+ min(min_value.y, rotated.y),
+ 0.0, 0.0);
+ }
+ }
+
+ float crop_width = max_value.x - min_value.x;
+ float crop_height = max_value.y - min_value.y;
+
+ vec4 crop_xy1 = (max_value + min_value) / vec4(2.0);
+
+ float crop_x = cos(-r) * crop_xy1.x - sin(-r) * crop_xy1.y;
+ float crop_y = sin(-r) * crop_xy1.x + cos(-r) * crop_xy1.y;
+
+
+ mat4 t = mat4(1.0, 0.0, 0.0, 0.0, // first column
+ 0.0, 1.0, 0.0, 0.0, // second column
+ 0.0, 0.0, 1.0, 0.0, // third column
+ 0.0, 0.0, 0.0, 1.0); // forth column
+
+ mat4 t_shift = mat4(1.0, 0.0, 0.0, 0.0, // first column
+ 0.0, 1.0, 0.0, 0.0, // second column
+ 0.0, 0.0, 1.0, 0.0, // third column
+ crop_x, crop_y, 0.0, 1.0); // forth column
+ t *= t_shift;
+
+ r = -r;
+
+ mat4 t_rotation = mat4(cos(r), sin(r), 0.0, 0.0, // first column
+ -sin(r), cos(r), 0.0, 0.0, // second column
+ 0.0, 0.0, 1.0, 0.0, // third column
+ 0.0, 0.0, 0.0, 1.0); // forth column
+
+ t *= t_rotation;
+ // cropped scale for x and y
+ float cs_x = $scale_x$ * crop_width / $output_width$;
+ float cs_y = $scale_y$ * crop_height / $output_height$;
+ mat4 t_scale = mat4(cs_x, 0.0, 0.0, 0.0, // first column
+ 0.0, cs_y, 0.0, 0.0, // second column
+ 0.0, 0.0, 1.0, 0.0, // third column
+ 0.0, 0.0, 0.0, 1.0); // forth column
+ t *= t_scale;
+ float shift_x = -1.0 * ($output_width$ / 2.0);
+ float shift_y = -1.0 * ($output_height$ / 2.0);
+ mat4 t_shift2 = mat4(1.0, 0.0, 0.0, 0.0, // first column
+ 0.0, 1.0, 0.0, 0.0, // second column
+ 0.0, 0.0, 1.0, 0.0, // third column
+ shift_x, shift_y, 0.0, 1.0); // forth column
+ t *= t_shift2;
+ // Inverse Transformation Matrix
+ $output_data_0[0, 0, 0] = vec4(t[0][0], t[1][0], t[2][0], t[3][0])$;
+ $output_data_0[1, 0, 0] = vec4(t[0][1], t[1][1], t[2][1], t[3][1])$;
+ $output_data_0[2, 0, 0] = vec4(t[0][2], t[1][2], t[2][2], t[3][2])$;
+ $output_data_0[3, 0, 0] = vec4(t[0][3], t[1][3], t[2][3], t[3][3])$;
+ )";
+
+ *generated_code = {
+ /*parameters=*/params,
+ /*objects=*/{},
+ /*shared_variables=*/{},
+ /*workload=*/uint3(1, 1, 1),
+ /*workgroup=*/uint3(1, 1, 1),
+ /*source_code=*/std::move(source),
+ /*input=*/IOStructure::ONLY_DEFINITIONS,
+ /*output=*/IOStructure::ONLY_DEFINITIONS,
+ };
+ return absl::OkStatus();
+}
+
+} // namespace v2
+
+class LandmarksToTransformMatrix : public NodeShader {
+ public:
+ absl::Status GenerateCode(const GenerationContext& ctx,
+ GeneratedCode* generated_code) const final {
+ auto* attr_v1 =
+ absl::any_cast<LandmarksToTransformMatrixV1Attributes>(&ctx.op_attr);
+ if (attr_v1) return v1::GenerateCode(*attr_v1, ctx, generated_code);
+
+ auto* attr_v2 =
+ absl::any_cast<LandmarksToTransformMatrixV2Attributes>(&ctx.op_attr);
+ if (attr_v2) return v2::GenerateCode(*attr_v2, ctx, generated_code);
+
+ return absl::InvalidArgumentError("Incorrect attributes' type.");
+ }
+};
+
+} // namespace
+
+std::unique_ptr<NodeShader> NewLandmarksToTransformMatrixNodeShader() {
+ return absl::make_unique<LandmarksToTransformMatrix>();
+}
+
+} // namespace gl
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/landmarks_to_transform_matrix.cc.orig b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/landmarks_to_transform_matrix.cc.orig
new file mode 100644
index 00000000000..3e884b643a5
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/landmarks_to_transform_matrix.cc.orig
@@ -0,0 +1,356 @@
+#include "mediapipe/util/tflite/gpu/gl/kernels/mediapipe/landmarks_to_transform_matrix.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "third_party/absl/memory/memory.h"
+#include "third_party/absl/strings/substitute.h"
+#include "third_party/absl/types/any.h"
+#include "mediapipe/util/tflite/gpu/common/mediapipe/landmarks_to_transform_matrix.h"
+#include "third_party/tensorflow/lite/delegates/gpu/common/shape.h"
+#include "third_party/tensorflow/lite/delegates/gpu/common/status.h"
+#include "third_party/tensorflow/lite/delegates/gpu/common/types.h"
+#include "third_party/tensorflow/lite/delegates/gpu/common/util.h"
+
+namespace tflite {
+namespace gpu {
+namespace gl {
+namespace {
+
+namespace v1 {
+
+std::string ReadLandmark(const std::string& landmark, const std::string& idx) {
+ std::string source = R"(
+ vec4 )" + landmark +
+ R"(;
+ {
+ int z_coord = )" +
+ idx +
+ R"( * $dimensions$ / 4;
+ vec4 result = $input_data_0[0, 0, z_coord]$;
+ int rest = )" + idx +
+ R"( * $dimensions$ % 4;
+ if (rest != 0) {
+ if (rest == 1) {
+ result.x = result.y;
+ result.y = result.z;
+ }
+ if (rest == 2) {
+ result.x = result.z;
+ result.y = result.w;
+ }
+ if (rest == 3) {
+ vec4 next_after_result = $input_data_0[0, 0, z_coord + 1]$;
+ result.x = result.w;
+ result.y = next_after_result.x;
+ }
+ }
+ )" + landmark + R"( = result;
+ }
+ )";
+ return source;
+}
+
+bool IsSupported(const LandmarksToTransformMatrixV1Attributes& attr) {
+ return attr.dimensions == 3;
+}
+
+absl::Status GenerateCode(const LandmarksToTransformMatrixV1Attributes& attr,
+ const NodeShader::GenerationContext& ctx,
+ GeneratedCode* generated_code) {
+ if (!IsSupported(attr)) {
+ return absl::InvalidArgumentError(
+ "This case is not supported by LandmarksToTransformMatrix v1");
+ }
+
+ std::vector<Variable> params = {
+ {"dimensions", static_cast<int>(attr.dimensions)},
+ {"landmarks_range", static_cast<int>(attr.landmarks_range)},
+ {"left_rotation_idx", static_cast<int>(attr.left_rotation_idx)},
+ {"right_rotation_idx", static_cast<int>(attr.right_rotation_idx)},
+ {"bbox_size_multiplier", static_cast<float>(attr.bbox_size_multiplier)},
+ {"input_h", static_cast<int>(attr.input_hw.h)},
+ {"input_w", static_cast<int>(attr.input_hw.w)},
+ {"output_h", static_cast<int>(attr.output_hw.h)},
+ {"output_w", static_cast<int>(attr.output_hw.w)},
+ {"subset", attr.subset},
+ {"subset_size", static_cast<int>(attr.subset.size())},
+ };
+
+ std::string source = R"(
+ )" + ReadLandmark("left_landmark", "$left_rotation_idx$") +
+ R"(
+
+ )" + ReadLandmark("right_landmark", "$right_rotation_idx$") +
+ R"(
+
+ float alpha = -atan(right_landmark.y - left_landmark.y,
+ right_landmark.x - left_landmark.x);
+
+ vec4 max_value = vec4(-100000, -100000, 0.0, 0.0);
+ vec4 min_value = vec4(100000, 100000, 0.0, 0.0);
+ for (int i = 0; i < $subset_size$; i++) {
+ for (int j = 0; j < 2; j++) {
+ )" + ReadLandmark("landmark_current", "$subset$[i][j]") +
+ R"(
+
+ vec4 rotated = vec4(landmark_current.x * cos(alpha) -
+ landmark_current.y * sin(alpha),
+ landmark_current.x * sin(alpha) +
+ landmark_current.y * cos(alpha),
+ 0.0, 0.0);
+ // both by x and y
+ max_value = vec4(max(max_value.x, rotated.x),
+ max(max_value.y, rotated.y),
+ 0.0, 0.0);
+ min_value = vec4(min(min_value.x, rotated.x),
+ min(min_value.y, rotated.y),
+ 0.0, 0.0);
+ }
+ }
+
+ vec4 bbox_size = max_value - min_value;
+ bbox_size *= $bbox_size_multiplier$;
+
+ mat3 scale_matrix =
+ mat3(bbox_size.x / float($landmarks_range$), 0.0, 0.0, // first column
+ 0.0, bbox_size.y / float($landmarks_range$), 0.0, // second column
+ 0.0, 0.0, 1.0); // third column
+
+ vec4 middle = (max_value + min_value) / 2.0;
+
+ vec4 rotated_middle =
+ vec4(middle.x * cos(-alpha) - middle.y * sin(-alpha),
+ middle.x * sin(-alpha) + middle.y * cos(-alpha), 0.0, 0.0);
+
+ mat3 rotation_matrix =
+ mat3(cos(-alpha), sin(-alpha), 0, // first column
+ -sin(-alpha), cos(-alpha), 0, // second column
+ // third column
+ (rotated_middle.x / float($landmarks_range$)) * 2.0 - 1.0,
+ (rotated_middle.y / float($landmarks_range$)) * 2.0 - 1.0, 1);
+
+ mat3 to_relative =
+ mat3(2.0 / (float($output_w$) - 1.0), 0.0, 0.0, // first column
+ 0.0, 2.0 / (float($output_h$) - 1.0), 0.0, // second column
+ -1.0, -1.0, 1.0); // third column
+
+ mat3 to_absolute =
+ mat3((float($input_w$) - 1.0) / 2.0, 0.0, 0.0, // first column
+ 0.0, (float($input_h$) - 1.0) / 2.0, 0.0, // second column
+ // third column
+ (float($input_w$) - 1.0) / 2.0, (float($input_h$) - 1.0)/2.0, 1.0);
+
+ // Transformstion Matrix
+ mat3 tm = to_absolute * rotation_matrix * scale_matrix * to_relative;
+
+ // Inverse Transformation Matrix
+ $output_data_0[0, 0, 0] = vec4(tm[0][0], tm[1][0], 0.0, tm[2][0])$;
+ $output_data_0[1, 0, 0] = vec4(tm[0][1], tm[1][1], 0.0, tm[2][1])$;
+ $output_data_0[2, 0, 0] = vec4(tm[0][2], tm[1][2], tm[2][2], 0.0)$;
+ $output_data_0[3, 0, 0] = vec4( 0, 0, 0, 1.0)$;
+ )";
+
+ *generated_code = {
+ /*parameters=*/params,
+ /*objects=*/{},
+ /*shared_variables=*/{},
+ /*workload=*/uint3(1, 1, 1),
+ /*workgroup=*/uint3(1, 1, 1),
+ /*source_code=*/std::move(source),
+ /*input=*/IOStructure::ONLY_DEFINITIONS,
+ /*output=*/IOStructure::ONLY_DEFINITIONS,
+ };
+ return absl::OkStatus();
+}
+
+} // namespace v1
+
+namespace v2 {
+
+std::string ReadLandmark(const std::string& landmark, const std::string& idx) {
+ std::string source = R"(
+ vec4 )" + landmark +
+ R"(;
+ {
+ int z_coord = )" +
+ idx +
+ R"( * $dimensions$ / 4;
+ vec4 result = $input_data_0[0, 0, z_coord]$;
+ int rest = )" + idx +
+ R"( * $dimensions$ % 4;
+ if (rest != 0) {
+ if (rest == 1) {
+ result.x = result.y;
+ result.y = result.z;
+ }
+ if (rest == 2) {
+ result.x = result.z;
+ result.y = result.w;
+ }
+ if (rest == 3) {
+ vec4 next_after_result = $input_data_0[0, 0, z_coord + 1]$;
+ result.x = result.w;
+ result.y = next_after_result.x;
+ }
+ }
+ result *= $multiplier$;
+ )" + landmark + R"( = result;
+ } )";
+ return source;
+}
+
+static bool IsSupported(const NodeShader::GenerationContext& ctx) {
+ return ctx.input_shapes.size() == 1 && ctx.input_shapes[0][1] == 1 &&
+ ctx.input_shapes[0][2] == 1 && ctx.input_shapes[0][3] % 3 == 0;
+}
+
+absl::Status GenerateCode(const LandmarksToTransformMatrixV2Attributes& attr,
+ const NodeShader::GenerationContext& ctx,
+ GeneratedCode* generated_code) {
+ if (!IsSupported(ctx)) {
+ return absl::InvalidArgumentError(
+ "This case is not supported by LandmarksToTransformMatrixV2");
+ }
+
+ std::vector<Variable> params = {
+ {"dimensions", static_cast<int>(3)},
+ {"scale_x", static_cast<float>(attr.scale_x)},
+ {"scale_y", static_cast<float>(attr.scale_y)},
+ {"left_rotation_idx", static_cast<int>(attr.left_rotation_idx)},
+ {"right_rotation_idx", static_cast<int>(attr.right_rotation_idx)},
+ {"target_rotation_radians",
+ static_cast<float>(attr.target_rotation_radians)},
+ {"output_width", static_cast<float>(attr.output_width)},
+ {"output_height", static_cast<float>(attr.output_height)},
+ {"subset_idxs", attr.subset_idxs},
+ {"subset_idxs_size", static_cast<int>(attr.subset_idxs.size())},
+ {"multiplier", static_cast<float>(attr.multiplier)},
+ };
+
+ std::string source = R"(
+ )" + ReadLandmark("left_landmark", "$left_rotation_idx$") +
+ R"(
+ )" + ReadLandmark("right_landmark", "$right_rotation_idx$") +
+ R"(
+
+ float diff_y = right_landmark.y - left_landmark.y;
+ float diff_x = right_landmark.x - left_landmark.x;
+ float rotation = 0.0;
+ if (diff_y != 0.0 && diff_x != 0.0) rotation = atan(diff_y, diff_x);
+ float r = $target_rotation_radians$ - rotation;
+
+ vec4 max_value = vec4(-100000, -100000, 0.0, 0.0);
+ vec4 min_value = vec4(100000, 100000, 0.0, 0.0);
+ for (int i = 0; i < $subset_idxs_size$; i++) {
+ for (int j = 0; j < 2; j++) {
+ )" + ReadLandmark("landmark_current", "$subset_idxs$[i][j]") +
+ R"(
+ vec4 rotated = vec4(landmark_current.x * cos(r) -
+ landmark_current.y * sin(r),
+ landmark_current.x * sin(r) +
+ landmark_current.y * cos(r),
+ 0.0, 0.0);
+ // both by x and y
+ max_value = vec4(max(max_value.x, rotated.x),
+ max(max_value.y, rotated.y),
+ 0.0, 0.0);
+ min_value = vec4(min(min_value.x, rotated.x),
+ min(min_value.y, rotated.y),
+ 0.0, 0.0);
+ }
+ }
+
+ float crop_width = max_value.x - min_value.x;
+ float crop_height = max_value.y - min_value.y;
+
+ vec4 crop_xy1 = (max_value + min_value) / vec4(2.0);
+
+ float crop_x = cos(-r) * crop_xy1.x - sin(-r) * crop_xy1.y;
+ float crop_y = sin(-r) * crop_xy1.x + cos(-r) * crop_xy1.y;
+
+
+ mat4 t = mat4(1.0, 0.0, 0.0, 0.0, // first column
+ 0.0, 1.0, 0.0, 0.0, // second column
+ 0.0, 0.0, 1.0, 0.0, // third column
+ 0.0, 0.0, 0.0, 1.0); // forth column
+
+ mat4 t_shift = mat4(1.0, 0.0, 0.0, 0.0, // first column
+ 0.0, 1.0, 0.0, 0.0, // second column
+ 0.0, 0.0, 1.0, 0.0, // third column
+ crop_x, crop_y, 0.0, 1.0); // forth column
+ t *= t_shift;
+
+ r = -r;
+
+ mat4 t_rotation = mat4(cos(r), sin(r), 0.0, 0.0, // first column
+ -sin(r), cos(r), 0.0, 0.0, // second column
+ 0.0, 0.0, 1.0, 0.0, // third column
+ 0.0, 0.0, 0.0, 1.0); // forth column
+
+ t *= t_rotation;
+ // cropped scale for x and y
+ float cs_x = $scale_x$ * crop_width / $output_width$;
+ float cs_y = $scale_y$ * crop_height / $output_height$;
+ mat4 t_scale = mat4(cs_x, 0.0, 0.0, 0.0, // first column
+ 0.0, cs_y, 0.0, 0.0, // second column
+ 0.0, 0.0, 1.0, 0.0, // third column
+ 0.0, 0.0, 0.0, 1.0); // forth column
+ t *= t_scale;
+ float shift_x = -1.0 * ($output_width$ / 2.0);
+ float shift_y = -1.0 * ($output_height$ / 2.0);
+ mat4 t_shift2 = mat4(1.0, 0.0, 0.0, 0.0, // first column
+ 0.0, 1.0, 0.0, 0.0, // second column
+ 0.0, 0.0, 1.0, 0.0, // third column
+ shift_x, shift_y, 0.0, 1.0); // forth column
+ t *= t_shift2;
+ // Inverse Transformation Matrix
+ $output_data_0[0, 0, 0] = vec4(t[0][0], t[1][0], t[2][0], t[3][0])$;
+ $output_data_0[1, 0, 0] = vec4(t[0][1], t[1][1], t[2][1], t[3][1])$;
+ $output_data_0[2, 0, 0] = vec4(t[0][2], t[1][2], t[2][2], t[3][2])$;
+ $output_data_0[3, 0, 0] = vec4(t[0][3], t[1][3], t[2][3], t[3][3])$;
+ )";
+
+ *generated_code = {
+ /*parameters=*/params,
+ /*objects=*/{},
+ /*shared_variables=*/{},
+ /*workload=*/uint3(1, 1, 1),
+ /*workgroup=*/uint3(1, 1, 1),
+ /*source_code=*/std::move(source),
+ /*input=*/IOStructure::ONLY_DEFINITIONS,
+ /*output=*/IOStructure::ONLY_DEFINITIONS,
+ };
+ return absl::OkStatus();
+}
+
+} // namespace v2
+
+class LandmarksToTransformMatrix : public NodeShader {
+ public:
+ absl::Status GenerateCode(const GenerationContext& ctx,
+ GeneratedCode* generated_code) const final {
+ auto* attr_v1 =
+ absl::any_cast<LandmarksToTransformMatrixV1Attributes>(&ctx.op_attr);
+ if (attr_v1) return v1::GenerateCode(*attr_v1, ctx, generated_code);
+
+ auto* attr_v2 =
+ absl::any_cast<LandmarksToTransformMatrixV2Attributes>(&ctx.op_attr);
+ if (attr_v2) return v2::GenerateCode(*attr_v2, ctx, generated_code);
+
+ return absl::InvalidArgumentError("Incorrect attributes' type.");
+ }
+};
+
+} // namespace
+
+std::unique_ptr<NodeShader> NewLandmarksToTransformMatrixNodeShader() {
+ return absl::make_unique<LandmarksToTransformMatrix>();
+}
+
+} // namespace gl
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/landmarks_to_transform_matrix.h b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/landmarks_to_transform_matrix.h
new file mode 100644
index 00000000000..d3949050578
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/landmarks_to_transform_matrix.h
@@ -0,0 +1,19 @@
+#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MEDIAPIPE_LANDMARKS_TO_TRANSFORM_MATRIX_H_
+#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MEDIAPIPE_LANDMARKS_TO_TRANSFORM_MATRIX_H_
+
+#include <memory>
+
+#include "tensorflow/lite/delegates/gpu/common/operations.h"
+#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
+
+namespace tflite {
+namespace gpu {
+namespace gl {
+
+std::unique_ptr<NodeShader> NewLandmarksToTransformMatrixNodeShader();
+
+} // namespace gl
+} // namespace gpu
+} // namespace tflite
+
+#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MEDIAPIPE_LANDMARKS_TO_TRANSFORM_MATRIX_H_
diff --git a/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/registry.cc b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/registry.cc
new file mode 100644
index 00000000000..3ef02a248c3
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/registry.cc
@@ -0,0 +1,28 @@
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "tensorflow/lite/delegates/gpu/gl/kernels/custom_registry.h"
+#include "tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/landmarks_to_transform_matrix.h"
+#include "tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_landmarks.h"
+#include "tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_tensor_bilinear.h"
+
+namespace tflite {
+namespace gpu {
+namespace gl {
+
+void RegisterCustomOps(
+ absl::flat_hash_map<std::string, std::vector<std::unique_ptr<NodeShader>>>*
+ shaders) {
+ (*shaders)["landmarks_to_transform_matrix"].push_back(
+ NewLandmarksToTransformMatrixNodeShader());
+ (*shaders)["transform_landmarks"].push_back(
+ NewTransformLandmarksNodeShader());
+ (*shaders)["transform_tensor_bilinear"].push_back(
+ NewTransformTensorBilinearNodeShader());
+}
+
+} // namespace gl
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_landmarks.cc b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_landmarks.cc
new file mode 100644
index 00000000000..980e2aa99e6
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_landmarks.cc
@@ -0,0 +1,123 @@
+#include "tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_landmarks.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "absl/memory/memory.h"
+#include "absl/strings/substitute.h"
+#include "absl/types/any.h"
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/transform_landmarks.h"
+#include "tensorflow/lite/delegates/gpu/common/shape.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/types.h"
+#include "tensorflow/lite/delegates/gpu/common/util.h"
+
+namespace tflite {
+namespace gpu {
+namespace gl {
+namespace {
+
+class TransformLandmarks : public NodeShader {
+ public:
+ absl::Status GenerateCode(const GenerationContext& ctx,
+ GeneratedCode* generated_code) const final {
+ if (!IsSupported(ctx)) {
+ return absl::InvalidArgumentError(
+ "This case is not supported by TransformLandmarks");
+ }
+
+ const auto& attr =
+ absl::any_cast<const TransformLandmarksAttributes&>(ctx.op_attr);
+
+ // For transformlandmarks v2 scale parameter is set to 1 when operation is
+ // parsed.
+ std::vector<Variable> params;
+ if (attr.scale != 1) {
+ params.push_back({"scale", static_cast<float>(attr.scale)});
+ }
+ std::string source = R"(
+ vec4 x_transform = $input_data_1[0, 0, 0]$;
+ vec4 y_transform = $input_data_1[1, 0, 0]$; )";
+ if (attr.scale != 1) {
+ source += R"(
+ x_transform.w *= $scale$;
+ y_transform.w *= $scale$;
+ )";
+ }
+ source += R"(
+ vec4 landmks = $input_data_0[gid.x, gid.y, gid.z]$;
+ vec4 transformed = vec4(0.0);
+ )";
+ switch (attr.dimensions) {
+ case 2:
+ source += R"(
+ // x y x y
+ vec4 l_pair1_ = vec4(landmks.x, landmks.y, 0.0, 1.0);
+ vec4 l_pair2_ = vec4(landmks.z, landmks.w, 0.0, 1.0);
+ transformed = vec4(dot(x_transform, l_pair1_), dot(y_transform, l_pair1_),
+ dot(x_transform, l_pair2_), dot(y_transform, l_pair2_));
+
+ value_0 = transformed;
+ )";
+ break;
+ case 3:
+ source += R"(
+ if ((gid.z * 4) % 3 == 0) { // 0, 3, 6
+ // x y z x
+ vec4 landmks_next = $input_data_0[gid.x, gid.y, gid.z + 1]$;
+ vec4 l_= landmks;
+ l_.z = 0.0;
+ l_.w = 1.0;
+ transformed = vec4(dot(x_transform, l_),
+ dot(y_transform, l_),
+ landmks.z, dot(x_transform, vec4(landmks.w, landmks_next.x, 0.0, 1.0)));
+ } else if ((gid.z * 4) % 3 == 1) { // 1, 4, 7
+ // y z x y
+ vec4 landmks_prev = $input_data_0[gid.x, gid.y, gid.z - 1]$;
+ vec4 l_ = vec4(landmks.z, landmks.w, 0.0, 1.0);
+ transformed = vec4(dot(y_transform, vec4(landmks_prev.w, landmks.x, 0.0, 1.0)), landmks.y,
+ dot(x_transform, l_), dot(y_transform, l_));
+ } else if ((gid.z * 4) % 3 == 2) { // 2, 5, 8
+ // z, x, y, z
+ vec4 l_ = vec4(landmks.y, landmks.z, 0.0, 1.0);
+ transformed = vec4(landmks.x, dot(x_transform, l_),
+ dot(y_transform, l_), landmks.w);
+ }
+ value_0 = transformed;
+ )";
+ break;
+ }
+
+ *generated_code = {
+ /*parameters=*/params,
+ /*objects=*/{},
+ /*shared_variables=*/{},
+ /*workload=*/uint3(),
+ /*workgroup=*/uint3(),
+ /*source_code=*/std::move(source),
+ /*input=*/IOStructure::ONLY_DEFINITIONS,
+ /*output=*/IOStructure::AUTO,
+ };
+ return absl::OkStatus();
+ }
+
+ private:
+ static bool IsSupported(const GenerationContext& ctx) {
+ const auto& attr =
+ absl::any_cast<const TransformLandmarksAttributes&>(ctx.op_attr);
+ return (attr.dimensions == 2 || attr.dimensions == 3) && attr.version == 1;
+ }
+};
+
+} // namespace
+
+std::unique_ptr<NodeShader> NewTransformLandmarksNodeShader() {
+ return absl::make_unique<TransformLandmarks>();
+}
+
+} // namespace gl
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_landmarks.h b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_landmarks.h
new file mode 100644
index 00000000000..cfb656675e4
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_landmarks.h
@@ -0,0 +1,19 @@
+#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MEDIAPIPE_TRANSFORM_LANDMARKS_H_
+#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MEDIAPIPE_TRANSFORM_LANDMARKS_H_
+
+#include <memory>
+
+#include "tensorflow/lite/delegates/gpu/common/operations.h"
+#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
+
+namespace tflite {
+namespace gpu {
+namespace gl {
+
+std::unique_ptr<NodeShader> NewTransformLandmarksNodeShader();
+
+} // namespace gl
+} // namespace gpu
+} // namespace tflite
+
+#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MEDIAPIPE_TRANSFORM_LANDMARKS_H_
diff --git a/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_tensor_bilinear.cc b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_tensor_bilinear.cc
new file mode 100644
index 00000000000..8013b9b3505
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_tensor_bilinear.cc
@@ -0,0 +1,169 @@
+#include "tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_tensor_bilinear.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "absl/memory/memory.h"
+#include "absl/strings/substitute.h"
+#include "absl/types/any.h"
+#include "tensorflow/lite/delegates/gpu/common/mediapipe/transform_tensor_bilinear.h"
+#include "tensorflow/lite/delegates/gpu/common/shape.h"
+#include "tensorflow/lite/delegates/gpu/common/status.h"
+#include "tensorflow/lite/delegates/gpu/common/types.h"
+#include "tensorflow/lite/delegates/gpu/common/util.h"
+
+namespace tflite {
+namespace gpu {
+namespace gl {
+namespace {
+
+class TransformTensorBilinear : public NodeShader {
+ public:
+ absl::Status GenerateCode(const GenerationContext& ctx,
+ GeneratedCode* generated_code) const final {
+ if (!IsSupported(ctx)) {
+ return absl::InvalidArgumentError(
+ "This case is not supported by TransformTensorBilinear.");
+ }
+
+ std::vector<Variable> params = {
+ {"input_data_0_h", static_cast<int>(ctx.input_shapes[0][1])},
+ {"input_data_0_w", static_cast<int>(ctx.input_shapes[0][2])}};
+
+ // Only bilinear transformation is supported right now.
+ std::string source = R"(
+ vec4 first_line = $input_data_1[0, 0, 0]$;
+ vec4 second_line = $input_data_1[1, 0, 0]$;
+ )" + AlignCornersCorrection(ctx) +
+ R"(
+ vec4 before_transform_coord_2d = vec4(gid.x, gid.y, 0.0, 1.0);
+
+ // Get transformed coordinates
+ vec2 xy = vec2(dot(first_line, before_transform_coord_2d),
+ dot(second_line, before_transform_coord_2d));
+
+ // Get coordinates of corners to interpolate from.
+ int x1 = int(floor(xy.x)); // x2 is x1 + 1
+ int y1 = int(floor(xy.y)); // y2 is y1 + 1
+
+ // Apply interpolation if coordinate is in bounds.
+ vec4 result = vec4(0.0);
+
+ if(xy.x >= 0.0 && xy.x <= float($input_data_0_w$ -1) &&
+ xy.y >= 0.0 && xy.y <= float($input_data_0_h$ -1)) {
+
+ // Corners position:
+ // q_11 --- q_21
+ // ---- ----
+ // q_12 --- q_22
+)";
+ source += SampleFromInput0("q_11", "x1", "y1") +
+ SampleFromInput0("q_12", "x1", "y1 + 1") +
+ SampleFromInput0("q_21", "x1 + 1", "y1") +
+ SampleFromInput0("q_22", "x1 + 1", "y1 + 1") + R"(
+
+ float right_contrib = xy.x - float(x1);
+ float lower_contrib = xy.y - float(y1);
+
+ vec4 upper = (1.0 - right_contrib) * q_11 + right_contrib * q_21;
+ vec4 lower = (1.0 - right_contrib) * q_12 + right_contrib * q_22;
+
+ result = lower_contrib * lower + (1.0 - lower_contrib) * upper;
+
+ }
+ value_0 = result;
+ )";
+
+ *generated_code = {
+ /*parameters=*/params,
+ /*objects=*/{},
+ /*shared_variables=*/{},
+ /*workload=*/uint3(),
+ /*workgroup=*/uint3(),
+ /*source_code=*/std::move(source),
+ /*input=*/IOStructure::ONLY_DEFINITIONS,
+ /*output=*/IOStructure::AUTO,
+ };
+ return absl::OkStatus();
+ }
+
+ private:
+ std::string SampleFromInput0(absl::string_view variable,
+ absl::string_view x_coord,
+ absl::string_view y_coord) const {
+ // This function generates code, which samples data from the first input
+ // tensor and checks the coordinates' bounds:
+ //
+ // vec4 q = vec4(0.0);
+ // [0, H)
+ // if (x >= 0 && x < $input_data_0_w$ && y >= 0 && y < $input_data_0_h$) {
+ // q = $input_data_0[x, y, gid.z]$;
+ // }
+
+ // Create zero initialized variable on stack
+ std::string result =
+ absl::Substitute(" vec4 $0 = vec4(0.0);\n", variable);
+ // If coordinates are not out of scope, load value from input_data_0
+ absl::SubstituteAndAppend(
+ &result,
+ " if ($0 >= 0 && $1 < $$input_data_0_w$$ && "
+ "$2 >= 0 && $3 < $$input_data_0_h$$) {\n",
+ x_coord, x_coord, y_coord, y_coord);
+ absl::SubstituteAndAppend(
+ &result,
+ " $0 = $$input_data_0[$1, $2, gid.z]$$;\n }\n\n",
+ variable, x_coord, y_coord);
+ return result;
+ }
+
+ std::string AlignCornersCorrection(const GenerationContext& ctx) const {
+ const auto& attr =
+ absl::any_cast<const TransformTensorBilinearAttributes&>(ctx.op_attr);
+ // Align corners correction: T -> S * ( T * A ), where T is a
+ // transformation matrix, and subtruction and addition matrices are:
+ // S A
+ // 1 0 0 -0.5 1 0 0 0.5
+ // 0 1 0 -0.5 0 1 0 0.5
+ // 0 0 1 0 0 0 1 0
+ // 0 0 0 1 0 0 0 1
+ // Transformation matrix column 3 and rows 3, 4 are identity, which makes
+ // the final formula pretty simple and easy to get if doing a manual
+ // multiuplication.
+ if (attr.align_corners) {
+ return R"(
+ first_line.w += first_line.x * 0.5 + first_line.y * 0.5 - 0.5;
+ second_line.w += second_line.x * 0.5 + second_line.y * 0.5 - 0.5;
+ )";
+ } else {
+ return "";
+ }
+ }
+
+ static bool IsSupported(const GenerationContext& ctx) {
+ // if version 2 - align corners is turned on.
+ // both versions expect transformation matrix as 1x1x1x16
+ if (ctx.input_shapes.size() != 2) return false;
+
+ if (ctx.input_shapes[1][0] != 1 || ctx.input_shapes[1][1] != 1 ||
+ ctx.input_shapes[1][2] != 4 || ctx.input_shapes[1][3] != 4)
+ return false;
+
+ const auto& attr =
+ absl::any_cast<const TransformTensorBilinearAttributes&>(ctx.op_attr);
+ return attr.output_size.h > 0 && attr.output_size.w > 0 &&
+ attr.version == 1;
+ }
+};
+
+} // namespace
+
+std::unique_ptr<NodeShader> NewTransformTensorBilinearNodeShader() {
+ return absl::make_unique<TransformTensorBilinear>();
+}
+
+} // namespace gl
+} // namespace gpu
+} // namespace tflite
diff --git a/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_tensor_bilinear.h b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_tensor_bilinear.h
new file mode 100644
index 00000000000..c62387a4b96
--- /dev/null
+++ b/tensorflow/lite/delegates/gpu/gl/kernels/mediapipe/transform_tensor_bilinear.h
@@ -0,0 +1,19 @@
+#ifndef TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MEDIAPIPE_TRANSFORM_TENSOR_BILINEAR_H_
+#define TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MEDIAPIPE_TRANSFORM_TENSOR_BILINEAR_H_
+
+#include <memory>
+
+#include "tensorflow/lite/delegates/gpu/common/operations.h"
+#include "tensorflow/lite/delegates/gpu/gl/node_shader.h"
+
+namespace tflite {
+namespace gpu {
+namespace gl {
+
+std::unique_ptr<NodeShader> NewTransformTensorBilinearNodeShader();
+
+} // namespace gl
+} // namespace gpu
+} // namespace tflite
+
+#endif // TENSORFLOW_LITE_DELEGATES_GPU_GL_KERNELS_MEDIAPIPE_TRANSFORM_TENSOR_BILINEAR_H_