Project import generated by Copybara.
PiperOrigin-RevId: 253489161
This commit is contained in:
@@ -0,0 +1,485 @@
|
||||
# Copyright 2019 The MediaPipe Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
package(default_visibility = ["//visibility:private"])
|
||||
|
||||
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library")
|
||||
|
||||
proto_library(
|
||||
name = "calculator_graph_template_proto",
|
||||
srcs = ["calculator_graph_template.proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_proto",
|
||||
"//mediapipe/framework/deps:proto_descriptor_proto",
|
||||
],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "source_proto",
|
||||
srcs = ["source.proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//mediapipe/framework:calculator_proto"],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "calculator_graph_template_cc_proto",
|
||||
srcs = ["calculator_graph_template.proto"],
|
||||
cc_deps = [
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/framework/deps:proto_descriptor_cc_proto",
|
||||
],
|
||||
visibility = ["//mediapipe/framework:__subpackages__"],
|
||||
deps = [":calculator_graph_template_proto"],
|
||||
)
|
||||
|
||||
mediapipe_cc_proto_library(
|
||||
name = "source_cc_proto",
|
||||
srcs = ["source.proto"],
|
||||
cc_deps = ["//mediapipe/framework:calculator_cc_proto"],
|
||||
visibility = ["//mediapipe/framework:__subpackages__"],
|
||||
deps = [":source_proto"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "fill_packet_set",
|
||||
srcs = ["fill_packet_set.cc"],
|
||||
hdrs = ["fill_packet_set.h"],
|
||||
visibility = ["//mediapipe/framework:mediapipe_internal"],
|
||||
deps = [
|
||||
"//mediapipe/framework:packet_set",
|
||||
"//mediapipe/framework:packet_type",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework/port:statusor",
|
||||
"//mediapipe/framework/tool:status_util",
|
||||
"@com_google_absl//absl/memory",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "name_util",
|
||||
srcs = ["name_util.cc"],
|
||||
hdrs = ["name_util.h"],
|
||||
visibility = ["//mediapipe/framework:mediapipe_internal"],
|
||||
deps = [
|
||||
":validate_name",
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/framework/port:map_util",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "executor_util",
|
||||
srcs = ["executor_util.cc"],
|
||||
hdrs = ["executor_util.h"],
|
||||
visibility = ["//mediapipe/framework:mediapipe_internal"],
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/framework:mediapipe_options_cc_proto",
|
||||
"//mediapipe/framework:thread_pool_executor_cc_proto",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "executor_util_test",
|
||||
srcs = ["executor_util_test.cc"],
|
||||
visibility = ["//mediapipe/framework:mediapipe_internal"],
|
||||
deps = [
|
||||
":executor_util",
|
||||
"//mediapipe/framework/deps:message_matchers",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:parse_text_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "options_util",
|
||||
hdrs = ["options_util.h"],
|
||||
visibility = ["//mediapipe/framework:mediapipe_internal"],
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/framework:packet_generator_cc_proto",
|
||||
"//mediapipe/framework:packet_set",
|
||||
"//mediapipe/framework/port:any_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "packet_util",
|
||||
hdrs = ["packet_util.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/framework/port:statusor",
|
||||
"@org_tensorflow//tensorflow/core:protos_all_cc",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "proto_util_lite",
|
||||
srcs = ["proto_util_lite.cc"],
|
||||
hdrs = ["proto_util_lite.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework:type_map",
|
||||
"//mediapipe/framework/port:advanced_proto_lite",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:logging",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "simulation_clock",
|
||||
srcs = ["simulation_clock.cc"],
|
||||
hdrs = ["simulation_clock.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework/deps:clock",
|
||||
"//mediapipe/framework/port:logging",
|
||||
"//mediapipe/framework/port:status",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
"@com_google_absl//absl/time",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "sink",
|
||||
srcs = ["sink.cc"],
|
||||
hdrs = ["sink.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":name_util",
|
||||
"//mediapipe/calculators/internal:callback_packet_calculator",
|
||||
"//mediapipe/calculators/internal:callback_packet_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_base",
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_graph",
|
||||
"//mediapipe/framework:calculator_registry",
|
||||
"//mediapipe/framework:input_stream",
|
||||
"//mediapipe/framework:packet",
|
||||
"//mediapipe/framework:packet_type",
|
||||
"//mediapipe/framework/port:logging",
|
||||
"//mediapipe/framework/port:source_location",
|
||||
"//mediapipe/framework/port:status",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "subgraph_expansion",
|
||||
srcs = ["subgraph_expansion.cc"],
|
||||
hdrs = ["subgraph_expansion.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":tag_map",
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/framework:packet_generator",
|
||||
"//mediapipe/framework:packet_generator_cc_proto",
|
||||
"//mediapipe/framework:port",
|
||||
"//mediapipe/framework:status_handler_cc_proto",
|
||||
"//mediapipe/framework:subgraph",
|
||||
"//mediapipe/framework/port:core_proto",
|
||||
"//mediapipe/framework/port:logging",
|
||||
"//mediapipe/framework/port:map_util",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "source",
|
||||
srcs = ["source.cc"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework/tool:source_cc_proto",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "status_util",
|
||||
srcs = ["status_util.cc"],
|
||||
hdrs = ["status_util.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework/port:status",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "tag_map",
|
||||
srcs = ["tag_map.cc"],
|
||||
hdrs = ["tag_map.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":validate_name",
|
||||
"//mediapipe/framework:collection_item_id",
|
||||
"//mediapipe/framework/port:core_proto",
|
||||
"//mediapipe/framework/port:logging",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework/port:statusor",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "tag_map_helper",
|
||||
testonly = 1,
|
||||
srcs = ["tag_map_helper.cc"],
|
||||
hdrs = ["tag_map_helper.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":tag_map",
|
||||
"//mediapipe/framework/port:core_proto",
|
||||
"//mediapipe/framework/port:logging",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework/port:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "template_expander",
|
||||
srcs = ["template_expander.cc"],
|
||||
hdrs = ["template_expander.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":proto_util_lite",
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/framework/deps:proto_descriptor_cc_proto",
|
||||
"//mediapipe/framework/port:logging",
|
||||
"//mediapipe/framework/port:numbers",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework/tool:calculator_graph_template_cc_proto",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "template_parser",
|
||||
srcs = [
|
||||
"template_parser.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"template_parser.h",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":proto_util_lite",
|
||||
"//mediapipe/framework/deps:proto_descriptor_cc_proto",
|
||||
"//mediapipe/framework/port:advanced_proto",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:logging",
|
||||
"//mediapipe/framework/port:map_util",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework/tool:calculator_graph_template_cc_proto",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "type_util",
|
||||
hdrs = ["type_util.h"],
|
||||
visibility = ["//mediapipe/framework:mediapipe_internal"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "validate",
|
||||
srcs = ["validate.cc"],
|
||||
hdrs = ["validate.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":validate_name",
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/framework/port:core_proto",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "validate_name",
|
||||
srcs = ["validate_name.cc"],
|
||||
hdrs = ["validate_name.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework/port:core_proto",
|
||||
"//mediapipe/framework/port:integral_types",
|
||||
"//mediapipe/framework/port:logging",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:source_location",
|
||||
"//mediapipe/framework/port:status",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "fill_packet_set_test",
|
||||
size = "small",
|
||||
srcs = ["fill_packet_set_test.cc"],
|
||||
deps = [
|
||||
":fill_packet_set",
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "sink_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"sink_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":sink",
|
||||
":validate_type",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:calculator_runner",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:parse_text_proto",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "status_util_test",
|
||||
size = "small",
|
||||
srcs = ["status_util_test.cc"],
|
||||
deps = [
|
||||
":status_util",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:status",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "tag_map_test",
|
||||
size = "small",
|
||||
srcs = ["tag_map_test.cc"],
|
||||
deps = [
|
||||
":tag_map",
|
||||
":tag_map_helper",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:map_util",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "validate_name_test",
|
||||
size = "small",
|
||||
srcs = ["validate_name_test.cc"],
|
||||
linkstatic = 1,
|
||||
deps = [
|
||||
":validate_name",
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/framework/deps:message_matchers",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "validate_type",
|
||||
# TODO make this testonly.
|
||||
srcs = ["validate_type.cc"],
|
||||
hdrs = ["validate_type.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":status_util",
|
||||
"//mediapipe/framework:calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_contract",
|
||||
"//mediapipe/framework:legacy_calculator_support",
|
||||
"//mediapipe/framework:packet_generator",
|
||||
"//mediapipe/framework:packet_generator_cc_proto",
|
||||
"//mediapipe/framework:packet_set",
|
||||
"//mediapipe/framework:packet_type",
|
||||
"//mediapipe/framework/port:core_proto",
|
||||
"//mediapipe/framework/port:map_util",
|
||||
"//mediapipe/framework/port:ret_check",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework/port:threadpool",
|
||||
"//mediapipe/util:cpu_util",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "simulation_clock_executor",
|
||||
srcs = ["simulation_clock_executor.cc"],
|
||||
hdrs = ["simulation_clock_executor.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//mediapipe/framework:thread_pool_executor",
|
||||
"//mediapipe/framework/tool:simulation_clock",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "simulation_clock_test",
|
||||
srcs = ["simulation_clock_test.cc"],
|
||||
deps = [
|
||||
":simulation_clock",
|
||||
":simulation_clock_executor",
|
||||
"//mediapipe/calculators/core:immediate_mux_calculator",
|
||||
"//mediapipe/calculators/core:real_time_flow_limiter_calculator",
|
||||
"//mediapipe/calculators/core:round_robin_demux_calculator",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:executor",
|
||||
"//mediapipe/framework:input_stream",
|
||||
"//mediapipe/framework:output_stream",
|
||||
"//mediapipe/framework:test_calculators",
|
||||
"//mediapipe/framework/port:core_proto",
|
||||
"//mediapipe/framework/port:gtest_main",
|
||||
"//mediapipe/framework/port:logging",
|
||||
"//mediapipe/framework/port:parse_text_proto",
|
||||
"//mediapipe/framework/port:status",
|
||||
"//mediapipe/framework/stream_handler:immediate_input_stream_handler",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,73 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package mediapipe;
|
||||
|
||||
import "mediapipe/framework/calculator.proto";
|
||||
import "mediapipe/framework/deps/proto_descriptor.proto";
|
||||
|
||||
// A template rule or a template rule argument expression.
|
||||
message TemplateExpression {
|
||||
// A template parameter name or a literal value.
|
||||
optional string param = 1;
|
||||
|
||||
// A template rule operation or a template expression operation.
|
||||
optional string op = 2;
|
||||
|
||||
// Nested template expressions, which define the operation args.
|
||||
// TODO: Rename this field to avoid collision with TemplateDict::arg.
|
||||
repeated TemplateExpression arg = 3;
|
||||
|
||||
// The path within the protobuf to the modified field values.
|
||||
optional string path = 4;
|
||||
|
||||
// The FieldDescriptor::Type of the modified field.
|
||||
optional mediapipe.FieldDescriptorProto.Type field_type = 5;
|
||||
|
||||
// Alternative value for the modified field, in protobuf binary format.
|
||||
optional string field_value = 7;
|
||||
}
|
||||
|
||||
// A protobuf extension defining a list of template rules.
|
||||
message CalculatorGraphTemplate {
|
||||
// The base configuration.
|
||||
optional CalculatorGraphConfig config = 1;
|
||||
|
||||
// The list of template rules.
|
||||
repeated TemplateExpression rule = 2;
|
||||
}
|
||||
|
||||
// The value for a template parameter.
|
||||
// The value can be either a simple value, a dictionary, or a list.
|
||||
message TemplateArgument {
|
||||
oneof param_value {
|
||||
// A string value for the parameter.
|
||||
string str = 1;
|
||||
// A numeric value for the parameter.
|
||||
double num = 2;
|
||||
// A dictionary of values for the parameter.
|
||||
TemplateDict dict = 3;
|
||||
}
|
||||
// An ordered list of values for the parameter.
|
||||
repeated TemplateArgument element = 4;
|
||||
}
|
||||
|
||||
// A dictionary of parameter values.
|
||||
message TemplateDict {
|
||||
message Parameter {
|
||||
optional string key = 1;
|
||||
optional TemplateArgument value = 2;
|
||||
}
|
||||
// A map from parameter name to parameter value.
|
||||
repeated Parameter arg = 1;
|
||||
}
|
||||
|
||||
// Options for a mediapipe template subgraph consisting of
|
||||
// mediapipe template arguments.
|
||||
message TemplateSubgraphOptions {
|
||||
extend mediapipe.CalculatorOptions {
|
||||
optional TemplateSubgraphOptions ext = 172998261;
|
||||
}
|
||||
|
||||
// The template arguments used to expand the template for the subgraph.
|
||||
optional TemplateDict dict = 1;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/executor_util.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "mediapipe/framework/mediapipe_options.pb.h"
|
||||
#include "mediapipe/framework/thread_pool_executor.pb.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
void EnsureMinimumDefaultExecutorStackSize(const int32 min_stack_size,
|
||||
CalculatorGraphConfig* config) {
|
||||
mediapipe::ExecutorConfig* default_executor_config = nullptr;
|
||||
for (mediapipe::ExecutorConfig& executor_config :
|
||||
*config->mutable_executor()) {
|
||||
if (executor_config.name().empty()) {
|
||||
default_executor_config = &executor_config;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!default_executor_config) {
|
||||
default_executor_config = config->add_executor();
|
||||
if (config->num_threads()) {
|
||||
default_executor_config->mutable_options()
|
||||
->MutableExtension(mediapipe::ThreadPoolExecutorOptions::ext)
|
||||
->set_num_threads(config->num_threads());
|
||||
config->clear_num_threads();
|
||||
}
|
||||
}
|
||||
if (default_executor_config->type().empty() ||
|
||||
default_executor_config->type() == "ThreadPoolExecutor") {
|
||||
mediapipe::ThreadPoolExecutorOptions* extension =
|
||||
default_executor_config->mutable_options()->MutableExtension(
|
||||
mediapipe::ThreadPoolExecutorOptions::ext);
|
||||
if (extension->stack_size() < min_stack_size) {
|
||||
extension->set_stack_size(min_stack_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_EXECUTOR_UTIL_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_EXECUTOR_UTIL_H_
|
||||
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
#include "mediapipe/framework/port/integral_types.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
// Ensures the default executor's stack size is at least min_stack_size.
|
||||
void EnsureMinimumDefaultExecutorStackSize(int32 min_stack_size,
|
||||
CalculatorGraphConfig* config);
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_EXECUTOR_UTIL_H_
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/executor_util.h"
|
||||
|
||||
#include "mediapipe/framework/deps/message_matchers.h"
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/parse_text_proto.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
TEST(GraphTest, MinimumDefaultExecutorStackSizeExistingConfigSizeUnspecified) {
|
||||
CalculatorGraphConfig config = ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
|
||||
executor {
|
||||
options {
|
||||
[mediapipe.ThreadPoolExecutorOptions.ext] { num_threads: 2 }
|
||||
}
|
||||
}
|
||||
)");
|
||||
CalculatorGraphConfig expected_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
|
||||
executor {
|
||||
options {
|
||||
[mediapipe.ThreadPoolExecutorOptions.ext] {
|
||||
num_threads: 2
|
||||
stack_size: 131072
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
tool::EnsureMinimumDefaultExecutorStackSize(131072, &config);
|
||||
EXPECT_THAT(config, EqualsProto(expected_config));
|
||||
}
|
||||
|
||||
TEST(GraphTest, MinimumDefaultExecutorStackSizeExistingConfigSizeTooSmall) {
|
||||
CalculatorGraphConfig config = ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
|
||||
executor {
|
||||
options {
|
||||
[mediapipe.ThreadPoolExecutorOptions.ext] {
|
||||
num_threads: 2
|
||||
stack_size: 65536
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
CalculatorGraphConfig expected_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
|
||||
executor {
|
||||
options {
|
||||
[mediapipe.ThreadPoolExecutorOptions.ext] {
|
||||
num_threads: 2
|
||||
stack_size: 131072
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
tool::EnsureMinimumDefaultExecutorStackSize(131072, &config);
|
||||
EXPECT_THAT(config, EqualsProto(expected_config));
|
||||
}
|
||||
|
||||
TEST(GraphTest, MinimumDefaultExecutorStackSizeExistingConfigSizeLargeEnough) {
|
||||
CalculatorGraphConfig config = ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
|
||||
executor {
|
||||
options {
|
||||
[mediapipe.ThreadPoolExecutorOptions.ext] {
|
||||
num_threads: 2
|
||||
stack_size: 262144
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
CalculatorGraphConfig expected_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
|
||||
executor {
|
||||
options {
|
||||
[mediapipe.ThreadPoolExecutorOptions.ext] {
|
||||
num_threads: 2
|
||||
stack_size: 262144
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
tool::EnsureMinimumDefaultExecutorStackSize(131072, &config);
|
||||
EXPECT_THAT(config, EqualsProto(expected_config));
|
||||
}
|
||||
|
||||
TEST(GraphTest, MinimumDefaultExecutorStackSizeNumThreads) {
|
||||
CalculatorGraphConfig config = ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
|
||||
num_threads: 1
|
||||
)");
|
||||
CalculatorGraphConfig expected_config =
|
||||
ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
|
||||
executor {
|
||||
options {
|
||||
[mediapipe.ThreadPoolExecutorOptions.ext] {
|
||||
num_threads: 1
|
||||
stack_size: 131072
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
tool::EnsureMinimumDefaultExecutorStackSize(131072, &config);
|
||||
EXPECT_THAT(config, EqualsProto(expected_config));
|
||||
}
|
||||
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/fill_packet_set.h"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "mediapipe/framework/port/status_builder.h"
|
||||
#include "mediapipe/framework/port/statusor.h"
|
||||
#include "mediapipe/framework/tool/status_util.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
::mediapipe::StatusOr<std::unique_ptr<PacketSet>> FillPacketSet(
|
||||
const PacketTypeSet& input_side_packet_types,
|
||||
const std::map<std::string, Packet>& input_side_packets,
|
||||
int* missing_packet_count_ptr) {
|
||||
if (missing_packet_count_ptr != nullptr) {
|
||||
*missing_packet_count_ptr = 0;
|
||||
}
|
||||
std::vector<::mediapipe::Status> errors;
|
||||
auto packet_set =
|
||||
absl::make_unique<PacketSet>(input_side_packet_types.TagMap());
|
||||
const auto& names = input_side_packet_types.TagMap()->Names();
|
||||
for (CollectionItemId id = input_side_packet_types.BeginId();
|
||||
id < input_side_packet_types.EndId(); ++id) {
|
||||
const std::string& name = names[id.value()];
|
||||
const auto iter = input_side_packets.find(name);
|
||||
if (iter == input_side_packets.end()) {
|
||||
if (missing_packet_count_ptr != nullptr) {
|
||||
++(*missing_packet_count_ptr);
|
||||
} else {
|
||||
errors.push_back(::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "Missing input side packet: " << name);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
packet_set->Get(id) = iter->second;
|
||||
// Check the type.
|
||||
::mediapipe::Status status =
|
||||
input_side_packet_types.Get(id).Validate(iter->second);
|
||||
if (!status.ok()) {
|
||||
std::pair<std::string, int> tag_index =
|
||||
input_side_packet_types.TagAndIndexFromId(id);
|
||||
errors.push_back(
|
||||
::mediapipe::StatusBuilder(status, MEDIAPIPE_LOC).SetPrepend()
|
||||
<< "Packet \""
|
||||
<< input_side_packet_types.TagMap()->Names()[id.value()]
|
||||
<< "\" with tag \"" << tag_index.first << "\" and index "
|
||||
<< tag_index.second << " failed validation. ");
|
||||
}
|
||||
}
|
||||
if (!errors.empty()) {
|
||||
return tool::CombinedStatus("FillPacketSet failed:", errors);
|
||||
}
|
||||
return std::move(packet_set);
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_FILL_PACKET_SET_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_FILL_PACKET_SET_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "mediapipe/framework/packet_set.h"
|
||||
#include "mediapipe/framework/packet_type.h"
|
||||
#include "mediapipe/framework/port/statusor.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
// Finds the packet names from input_side_packet_types's TagMap, looks
|
||||
// them up in |input_side_packets| and creates a PacketSet. An error
|
||||
// is returned if any packets fail the type check. If
|
||||
// missing_packet_count_ptr is not null, the number of missing packets
|
||||
// is returned in *missing_packet_count_ptr. Otherwise, an error is
|
||||
// returned if any packets are missing.
|
||||
::mediapipe::StatusOr<std::unique_ptr<PacketSet>> FillPacketSet(
|
||||
const PacketTypeSet& input_side_packet_types,
|
||||
const std::map<std::string, Packet>& input_side_packets,
|
||||
int* missing_packet_count_ptr);
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_FILL_PACKET_SET_H_
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright 2018 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/fill_packet_set.h"
|
||||
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(FillPacketSetTest, Success) {
|
||||
CalculatorGraphConfig::Node node;
|
||||
node.add_input_side_packet("side_packet1");
|
||||
node.add_input_side_packet("side_packet2");
|
||||
node.add_input_side_packet("DOUBLE:side_packet3");
|
||||
node.add_input_side_packet("DOUBLE:1:side_packet4");
|
||||
|
||||
PacketTypeSet input_side_packet_types(
|
||||
tool::TagMap::Create(node.input_side_packet()).ValueOrDie());
|
||||
input_side_packet_types.Index(0).Set<int>(
|
||||
// An age
|
||||
);
|
||||
input_side_packet_types.Index(1).Set<std::string>(
|
||||
// A name
|
||||
);
|
||||
input_side_packet_types.Tag("DOUBLE").Set<double>(
|
||||
// double1
|
||||
);
|
||||
input_side_packet_types.Get("DOUBLE", 1)
|
||||
.Set<double>(
|
||||
// double2
|
||||
);
|
||||
MEDIAPIPE_EXPECT_OK(ValidatePacketTypeSet(input_side_packet_types));
|
||||
|
||||
std::map<std::string, Packet> all_side_packets;
|
||||
all_side_packets["side_packet1"] = MakePacket<int>(70);
|
||||
all_side_packets["side_packet2"] = MakePacket<std::string>("Dennis Ritchie");
|
||||
all_side_packets["side_packet3"] = MakePacket<double>(42.0);
|
||||
all_side_packets["side_packet4"] = MakePacket<double>(-43.0);
|
||||
all_side_packets["foo_side_packet"] = MakePacket<std::string>("asdfgh");
|
||||
all_side_packets["bar_side_packet"] = MakePacket<double>(-1.0);
|
||||
|
||||
std::unique_ptr<PacketSet> input_side_packets =
|
||||
tool::FillPacketSet(input_side_packet_types, all_side_packets, nullptr)
|
||||
.ValueOrDie();
|
||||
ASSERT_EQ(4, input_side_packets->NumEntries());
|
||||
EXPECT_EQ(input_side_packets->Index(0).Get<int>(), 70);
|
||||
EXPECT_EQ(input_side_packets->Index(1).Get<std::string>(), "Dennis Ritchie");
|
||||
EXPECT_EQ(input_side_packets->Tag("DOUBLE").Get<double>(), 42.0);
|
||||
EXPECT_EQ(input_side_packets->Get("DOUBLE", 1).Get<double>(), -43.0);
|
||||
}
|
||||
|
||||
TEST(FillPacketSetTest, MissingSidePacketError) {
|
||||
CalculatorGraphConfig::Node node;
|
||||
node.add_input_side_packet("side_packet1");
|
||||
node.add_input_side_packet("side_packet2");
|
||||
node.add_input_side_packet("DOUBLE:side_packet3");
|
||||
node.add_input_side_packet("DOUBLE:1:side_packet4");
|
||||
|
||||
PacketTypeSet input_side_packet_types(
|
||||
tool::TagMap::Create(node.input_side_packet()).ValueOrDie());
|
||||
input_side_packet_types.Index(0).Set<int>(
|
||||
// An age
|
||||
);
|
||||
input_side_packet_types.Index(1).Set<std::string>(
|
||||
// A name
|
||||
);
|
||||
input_side_packet_types.Tag("DOUBLE").Set<double>(
|
||||
// double1
|
||||
);
|
||||
input_side_packet_types.Get("DOUBLE", 1)
|
||||
.Set<double>(
|
||||
// double2
|
||||
);
|
||||
MEDIAPIPE_EXPECT_OK(ValidatePacketTypeSet(input_side_packet_types));
|
||||
|
||||
std::map<std::string, Packet> all_side_packets;
|
||||
all_side_packets["side_packet1"] = MakePacket<int>(70);
|
||||
all_side_packets["side_packet2"] = MakePacket<std::string>("Dennis Ritchie");
|
||||
all_side_packets["side_packet4"] = MakePacket<double>(-43.0);
|
||||
all_side_packets["foo_side_packet"] = MakePacket<std::string>("asdfgh");
|
||||
all_side_packets["bar_side_packet"] = MakePacket<double>(-1.0);
|
||||
|
||||
EXPECT_THAT(
|
||||
tool::FillPacketSet(input_side_packet_types, all_side_packets, nullptr)
|
||||
.status()
|
||||
.message(),
|
||||
testing::HasSubstr("side_packet3"));
|
||||
}
|
||||
|
||||
TEST(FillPacketSetTest, MissingSidePacketOk) {
|
||||
CalculatorGraphConfig::Node node;
|
||||
node.add_input_side_packet("side_packet1");
|
||||
node.add_input_side_packet("side_packet2");
|
||||
node.add_input_side_packet("DOUBLE:side_packet3");
|
||||
node.add_input_side_packet("DOUBLE:1:side_packet4");
|
||||
|
||||
PacketTypeSet input_side_packet_types(
|
||||
tool::TagMap::Create(node.input_side_packet()).ValueOrDie());
|
||||
input_side_packet_types.Index(0).Set<int>(
|
||||
// An age
|
||||
);
|
||||
input_side_packet_types.Index(1).Set<std::string>(
|
||||
// A name
|
||||
);
|
||||
input_side_packet_types.Tag("DOUBLE").Set<double>(
|
||||
// double1
|
||||
);
|
||||
input_side_packet_types.Get("DOUBLE", 1)
|
||||
.Set<double>(
|
||||
// double2
|
||||
);
|
||||
MEDIAPIPE_EXPECT_OK(ValidatePacketTypeSet(input_side_packet_types));
|
||||
|
||||
std::map<std::string, Packet> all_side_packets;
|
||||
all_side_packets["side_packet1"] = MakePacket<int>(70);
|
||||
all_side_packets["side_packet2"] = MakePacket<std::string>("Dennis Ritchie");
|
||||
all_side_packets["side_packet4"] = MakePacket<double>(-43.0);
|
||||
all_side_packets["foo_side_packet"] = MakePacket<std::string>("asdfgh");
|
||||
all_side_packets["bar_side_packet"] = MakePacket<double>(-1.0);
|
||||
|
||||
int missing_packet_count;
|
||||
std::unique_ptr<PacketSet> input_side_packets =
|
||||
tool::FillPacketSet(input_side_packet_types, all_side_packets,
|
||||
&missing_packet_count)
|
||||
.ValueOrDie();
|
||||
ASSERT_EQ(4, input_side_packets->NumEntries());
|
||||
EXPECT_EQ(1, missing_packet_count);
|
||||
EXPECT_EQ(input_side_packets->Index(0).Get<int>(), 70);
|
||||
EXPECT_EQ(input_side_packets->Index(1).Get<std::string>(), "Dennis Ritchie");
|
||||
EXPECT_TRUE(input_side_packets->Tag("DOUBLE").IsEmpty());
|
||||
EXPECT_EQ(input_side_packets->Get("DOUBLE", 1).Get<double>(), -43.0);
|
||||
}
|
||||
|
||||
TEST(FillPacketSetTest, WrongSidePacketType) {
|
||||
CalculatorGraphConfig::Node node;
|
||||
node.add_input_side_packet("side_packet1");
|
||||
node.add_input_side_packet("side_packet2");
|
||||
node.add_input_side_packet("DOUBLE:side_packet3");
|
||||
node.add_input_side_packet("DOUBLE:1:side_packet4");
|
||||
|
||||
PacketTypeSet input_side_packet_types(
|
||||
tool::TagMap::Create(node.input_side_packet()).ValueOrDie());
|
||||
input_side_packet_types.Index(0).Set<int>(
|
||||
// An age
|
||||
);
|
||||
input_side_packet_types.Index(1).Set<std::string>(
|
||||
// A name
|
||||
);
|
||||
input_side_packet_types.Tag("DOUBLE").Set<double>(
|
||||
// double1
|
||||
);
|
||||
input_side_packet_types.Get("DOUBLE", 1)
|
||||
.Set<double>(
|
||||
// double2
|
||||
);
|
||||
MEDIAPIPE_EXPECT_OK(ValidatePacketTypeSet(input_side_packet_types));
|
||||
|
||||
std::map<std::string, Packet> all_side_packets;
|
||||
all_side_packets["side_packet1"] = MakePacket<float>(3.0f); // Wrong Type.
|
||||
all_side_packets["side_packet2"] = MakePacket<std::string>("Dennis Ritchie");
|
||||
all_side_packets["side_packet3"] = MakePacket<double>(42.0);
|
||||
all_side_packets["side_packet4"] = MakePacket<double>(-43.0);
|
||||
all_side_packets["foo_side_packet"] = MakePacket<std::string>("asdfgh");
|
||||
all_side_packets["bar_side_packet"] = MakePacket<double>(-1.0);
|
||||
|
||||
EXPECT_THAT(
|
||||
tool::FillPacketSet(input_side_packet_types, all_side_packets, nullptr)
|
||||
.status()
|
||||
.message(),
|
||||
testing::AllOf(
|
||||
// Problematic side packet.
|
||||
testing::HasSubstr("side_packet1"),
|
||||
// Actual type.
|
||||
testing::HasSubstr("float"),
|
||||
// Expected type.
|
||||
testing::HasSubstr("int")));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,91 @@
|
||||
"""Provides BUILD macros for MediaPipe graphs.
|
||||
|
||||
mediapipe_binary_graph() converts a graph from text format to serialized binary
|
||||
format.
|
||||
|
||||
Example:
|
||||
mediapipe_binary_graph(
|
||||
name = "make_graph_binarypb",
|
||||
graph = "//mediapipe/framework/tool/testdata:test_graph",
|
||||
output_name = "test.binarypb",
|
||||
deps = [
|
||||
"//video/annotation:graph_calculators_lib",
|
||||
]
|
||||
)
|
||||
|
||||
"""
|
||||
|
||||
load("//mediapipe/framework:encode_binary_proto.bzl", "encode_binary_proto", "generate_proto_descriptor_set")
|
||||
load("//mediapipe/framework:transitive_protos.bzl", "transitive_proto_cc_libs", "transitive_proto_descriptor_sets", "transitive_protos")
|
||||
|
||||
def mediapipe_binary_graph(name, graph = None, output_name = None, deps = [], testonly = None, **kwargs):
|
||||
"""Converts a graph from text format to binary format.
|
||||
|
||||
Args:
|
||||
name: the name of the encode_binary_proto rule generated by this macro.
|
||||
graph: the BUILD label of a text-format MediaPipe graph.
|
||||
output_name: the name of the file to which the binary serialization is
|
||||
written.
|
||||
deps: the BUILD labels of dependencies that provide any additional message
|
||||
types used by the graph. The basic messages defined in calculator.proto
|
||||
are always available, but any custom types (e.g. specific calculator
|
||||
options) should be provided here. It is sufficient to provide targets
|
||||
that depend on the required protos indirectly: this macro examines the
|
||||
entire dependency tree, and does not build any dependencies except for
|
||||
the protos it finds.
|
||||
testonly: pass 1 if the graph is to be used only for tests.
|
||||
**kwargs: any other arguments valid for encode_binary_proto.
|
||||
"""
|
||||
|
||||
if not graph:
|
||||
fail("No input graph file specified.")
|
||||
|
||||
if not output_name:
|
||||
fail("Must specify the output_name.")
|
||||
|
||||
transitive_protos(
|
||||
name = name + "_gather_protos",
|
||||
deps = deps,
|
||||
testonly = testonly,
|
||||
)
|
||||
|
||||
# This collects descriptor sets for tools that need them.
|
||||
transitive_proto_descriptor_sets(
|
||||
name = name + "_gather_proto_descriptor_sets",
|
||||
deps = deps,
|
||||
testonly = testonly,
|
||||
)
|
||||
|
||||
# This collects the generated .a libraries for tools that need them.
|
||||
transitive_proto_cc_libs(
|
||||
name = name + "_gather_proto_libs",
|
||||
deps = deps,
|
||||
testonly = testonly,
|
||||
)
|
||||
|
||||
# This generates a single descriptor set with a single invocation of the proto compiler.
|
||||
# May be faster than using the descriptor sets from proto_library.
|
||||
# We always pass at least the calculator proto since the proto compiler would fail
|
||||
# if it were passed no protos.
|
||||
generate_proto_descriptor_set(
|
||||
name = name + "_proto_descriptor_set",
|
||||
deps = [
|
||||
name + "_gather_protos",
|
||||
"//mediapipe/framework:calculator_proto",
|
||||
],
|
||||
testonly = testonly,
|
||||
)
|
||||
|
||||
return encode_binary_proto(
|
||||
name = name,
|
||||
deps = [
|
||||
name + "_gather_protos",
|
||||
"//mediapipe/framework:calculator_proto",
|
||||
"//mediapipe/framework/stream_handler:fixed_size_input_stream_handler_proto",
|
||||
],
|
||||
message_type = "mediapipe.CalculatorGraphConfig",
|
||||
input = graph,
|
||||
output = output_name,
|
||||
testonly = testonly,
|
||||
**kwargs
|
||||
)
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/name_util.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "mediapipe/framework/port/map_util.h"
|
||||
#include "mediapipe/framework/tool/validate_name.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
std::string GetUnusedNodeName(const CalculatorGraphConfig& config,
|
||||
const std::string& node_name_base) {
|
||||
std::set<std::string> node_names;
|
||||
for (const ::mediapipe::CalculatorGraphConfig::Node& node : config.node()) {
|
||||
if (!node.name().empty()) {
|
||||
node_names.insert(node.name());
|
||||
}
|
||||
}
|
||||
|
||||
std::string candidate = node_name_base;
|
||||
int iter = 2;
|
||||
while (ContainsKey(node_names, candidate)) {
|
||||
candidate =
|
||||
absl::StrCat(node_name_base, "_", absl::StrFormat("%02d", iter));
|
||||
++iter;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
std::string GetUnusedSidePacketName(
|
||||
const CalculatorGraphConfig& config,
|
||||
const std::string& input_side_packet_name_base) {
|
||||
std::unordered_map<std::string,
|
||||
std::vector<::mediapipe::CalculatorGraphConfig::Node>>
|
||||
input_side_packets;
|
||||
for (const ::mediapipe::CalculatorGraphConfig::Node& node : config.node()) {
|
||||
for (const auto& tag_and_name : node.input_side_packet()) {
|
||||
std::string tag;
|
||||
std::string name;
|
||||
int index;
|
||||
MEDIAPIPE_CHECK_OK(ParseTagIndexName(tag_and_name, &tag, &index, &name));
|
||||
input_side_packets[name].push_back(node);
|
||||
}
|
||||
}
|
||||
std::string candidate = input_side_packet_name_base;
|
||||
int iter = 2;
|
||||
while (::mediapipe::ContainsKey(input_side_packets, candidate)) {
|
||||
candidate = absl::StrCat(input_side_packet_name_base, "_",
|
||||
absl::StrFormat("%02d", iter));
|
||||
++iter;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_NAME_UTIL_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_NAME_UTIL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
// Get an unused InputSidePacket name which is (or starts with)
|
||||
// input_side_packet_name_base.
|
||||
std::string GetUnusedSidePacketName(const CalculatorGraphConfig& /*config*/,
|
||||
const std::string& side_packet_name_base);
|
||||
|
||||
// Get an usused node name which is (or starts with) node_name_base.
|
||||
std::string GetUnusedNodeName(const CalculatorGraphConfig& config,
|
||||
const std::string& node_name_base);
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_NAME_UTIL_H_
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_OPTIONS_UTIL_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_OPTIONS_UTIL_H_
|
||||
|
||||
#include <typeindex>
|
||||
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
#include "mediapipe/framework/packet.h"
|
||||
#include "mediapipe/framework/packet_generator.pb.h"
|
||||
#include "mediapipe/framework/packet_set.h"
|
||||
#include "mediapipe/framework/port/any_proto.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
|
||||
// Combine a base options with an overriding options.
|
||||
template <typename T>
|
||||
inline T MergeOptions(const T& base, const T& options) {
|
||||
auto result = base;
|
||||
result.MergeFrom(options);
|
||||
return result;
|
||||
}
|
||||
|
||||
// A compile-time detector for the constant |T::ext|.
|
||||
template <typename T>
|
||||
struct IsExtension {
|
||||
private:
|
||||
template <typename U>
|
||||
static decltype(U::ext, std::true_type()) test(int);
|
||||
|
||||
template <typename>
|
||||
static std::false_type test(...);
|
||||
|
||||
public:
|
||||
static constexpr bool value = decltype(test<T>(0))();
|
||||
};
|
||||
|
||||
// A map from object type to object.
|
||||
class TypeMap {
|
||||
public:
|
||||
template <class T>
|
||||
bool Has() const {
|
||||
return content_.count(typeid(T)) > 0;
|
||||
}
|
||||
template <class T>
|
||||
T* Get() const {
|
||||
if (!Has<T>()) {
|
||||
content_[typeid(T)] = std::make_shared<T>();
|
||||
}
|
||||
return static_cast<T*>(content_[typeid(T)].get());
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::map<std::type_index, std::shared_ptr<void>> content_;
|
||||
};
|
||||
|
||||
template <class T,
|
||||
typename std::enable_if<IsExtension<T>::value, int>::type = 0>
|
||||
void GetExtension(const CalculatorOptions& options, T* result) {
|
||||
if (options.HasExtension(T::ext)) {
|
||||
*result = options.GetExtension(T::ext);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T,
|
||||
typename std::enable_if<!IsExtension<T>::value, int>::type = 0>
|
||||
void GetExtension(const CalculatorOptions& options, T* result) {}
|
||||
|
||||
template <class T>
|
||||
void GetNodeOptions(const CalculatorGraphConfig::Node& node_config, T* result) {
|
||||
#if defined(MEDIAPIPE_PROTO_LITE) && defined(MEDIAPIPE_PROTO_THIRD_PARTY)
|
||||
// protobuf::Any is unavailable with third_party/protobuf:protobuf-lite.
|
||||
#else
|
||||
for (const ::mediapipe::protobuf::Any& options : node_config.node_options()) {
|
||||
if (options.Is<T>()) {
|
||||
options.UnpackTo(result);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Combine a base options message with an optional side packet. The specified
|
||||
// packet can hold either the specified options type T or CalculatorOptions.
|
||||
// Fields are either replaced or merged depending on field merge_fields.
|
||||
template <typename T>
|
||||
inline T RetrieveOptions(const T& base, const PacketSet& packet_set,
|
||||
const std::string& tag_name) {
|
||||
if (packet_set.HasTag(tag_name)) {
|
||||
const Packet& packet = packet_set.Tag(tag_name);
|
||||
T packet_options;
|
||||
if (packet.ValidateAsType<T>().ok()) {
|
||||
packet_options = packet.Get<T>();
|
||||
} else if (packet.ValidateAsType<CalculatorOptions>().ok()) {
|
||||
GetExtension<T>(packet.Get<CalculatorOptions>(), &packet_options);
|
||||
}
|
||||
return tool::MergeOptions(base, packet_options);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
// Extracts the options message of a specified type from a
|
||||
// CalculatorGraphConfig::Node.
|
||||
class OptionsMap {
|
||||
public:
|
||||
OptionsMap& Initialize(const CalculatorGraphConfig::Node& node_config) {
|
||||
node_config_ = &node_config;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Returns the options data for a CalculatorGraphConfig::Node, from
|
||||
// either "options" or "node_options" using either GetExtension or UnpackTo.
|
||||
template <class T>
|
||||
const T& Get() const {
|
||||
if (options_.Has<T>()) {
|
||||
return *options_.Get<T>();
|
||||
}
|
||||
T* result = options_.Get<T>();
|
||||
if (node_config_->has_options()) {
|
||||
GetExtension(node_config_->options(), result);
|
||||
} else {
|
||||
GetNodeOptions(*node_config_, result);
|
||||
}
|
||||
return *result;
|
||||
}
|
||||
|
||||
const CalculatorGraphConfig::Node* node_config_;
|
||||
TypeMap options_;
|
||||
};
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_OPTIONS_UTIL_H_
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_PACKET_UTIL_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_PACKET_UTIL_H_
|
||||
|
||||
#include "mediapipe/framework/packet.h"
|
||||
#include "tensorflow/core/example/example.pb.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
// The CLIF-friendly util functions to create and access a typed MediaPipe
|
||||
// Packet from MediaPipe Python interface.
|
||||
|
||||
// Functions for SequenceExample Packets.
|
||||
|
||||
// Make a SequenceExample packet from a serialized SequenceExample.
|
||||
// The SequenceExample in the Packet is owned by the C++ packet.
|
||||
Packet CreateSequenceExamplePacketFromString(std::string* serialized_content) {
|
||||
tensorflow::SequenceExample sequence_example;
|
||||
sequence_example.ParseFromString(*serialized_content);
|
||||
return MakePacket<tensorflow::SequenceExample>(sequence_example);
|
||||
}
|
||||
|
||||
// Get a serialized SequenceExample std::string from a Packet.
|
||||
// The ownership of the returned std::string will be transferred to the Python
|
||||
// object.
|
||||
std::unique_ptr<std::string> GetSerializedSequenceExample(Packet* packet) {
|
||||
return absl::make_unique<std::string>(
|
||||
packet->Get<tensorflow::SequenceExample>().SerializeAsString());
|
||||
}
|
||||
|
||||
// Make a String packet
|
||||
Packet CreateStringPacket(std::string* input_string) {
|
||||
return MakePacket<std::string>(*input_string);
|
||||
}
|
||||
|
||||
// Get the std::string from a Packet<std::string>
|
||||
std::unique_ptr<std::string> GetString(Packet* packet) {
|
||||
return absl::make_unique<std::string>(packet->Get<std::string>());
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_PACKET_UTIL_H_
|
||||
@@ -0,0 +1,393 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/proto_util_lite.h"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "mediapipe/framework/port/canonical_errors.h"
|
||||
#include "mediapipe/framework/port/logging.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/type_map.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
using proto_ns::io::ArrayInputStream;
|
||||
using proto_ns::io::CodedInputStream;
|
||||
using proto_ns::io::CodedOutputStream;
|
||||
using proto_ns::io::StringOutputStream;
|
||||
using WireFormatLite = ProtoUtilLite::WireFormatLite;
|
||||
using FieldAccess = ProtoUtilLite::FieldAccess;
|
||||
using FieldValue = ProtoUtilLite::FieldValue;
|
||||
using ProtoPath = ProtoUtilLite::ProtoPath;
|
||||
using FieldType = ProtoUtilLite::FieldType;
|
||||
|
||||
// Returns true if a wire type includes a length indicator.
|
||||
bool IsLengthDelimited(WireFormatLite::WireType wire_type) {
|
||||
return wire_type == WireFormatLite::WIRETYPE_LENGTH_DELIMITED;
|
||||
}
|
||||
|
||||
// Reads a single data value for a wire type.
|
||||
::mediapipe::Status ReadFieldValue(uint32 tag, CodedInputStream* in,
|
||||
std::string* result) {
|
||||
WireFormatLite::WireType wire_type = WireFormatLite::GetTagWireType(tag);
|
||||
if (IsLengthDelimited(wire_type)) {
|
||||
uint32 length;
|
||||
RET_CHECK(in->ReadVarint32(&length));
|
||||
RET_CHECK(in->ReadString(result, length));
|
||||
} else {
|
||||
std::string field_data;
|
||||
StringOutputStream sos(&field_data);
|
||||
CodedOutputStream cos(&sos);
|
||||
RET_CHECK(WireFormatLite::SkipField(in, tag, &cos));
|
||||
// Skip the tag written by SkipField.
|
||||
int tag_size = CodedOutputStream::VarintSize32(tag);
|
||||
cos.Trim();
|
||||
result->assign(field_data, tag_size, std::string::npos);
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// Reads the packed sequence of data values for a wire type.
|
||||
::mediapipe::Status ReadPackedValues(WireFormatLite::WireType wire_type,
|
||||
CodedInputStream* in,
|
||||
std::vector<std::string>* field_values) {
|
||||
uint32 data_size;
|
||||
RET_CHECK(in->ReadVarint32(&data_size));
|
||||
// fake_tag encodes the wire-type for calls to WireFormatLite::SkipField.
|
||||
uint32 fake_tag = WireFormatLite::MakeTag(1, wire_type);
|
||||
while (data_size > 0) {
|
||||
std::string number;
|
||||
RETURN_IF_ERROR(ReadFieldValue(fake_tag, in, &number));
|
||||
RET_CHECK_LE(number.size(), data_size);
|
||||
field_values->push_back(number);
|
||||
data_size -= number.size();
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// Extracts the data value(s) for one field from a serialized message.
|
||||
// The message with these field values removed is written to |out|.
|
||||
::mediapipe::Status GetFieldValues(uint32 field_id,
|
||||
WireFormatLite::WireType wire_type,
|
||||
CodedInputStream* in, CodedOutputStream* out,
|
||||
std::vector<std::string>* field_values) {
|
||||
uint32 tag;
|
||||
while ((tag = in->ReadTag()) != 0) {
|
||||
int field_number = WireFormatLite::GetTagFieldNumber(tag);
|
||||
if (field_number == field_id) {
|
||||
if (!IsLengthDelimited(wire_type) &&
|
||||
IsLengthDelimited(WireFormatLite::GetTagWireType(tag))) {
|
||||
RETURN_IF_ERROR(ReadPackedValues(wire_type, in, field_values));
|
||||
} else {
|
||||
std::string value;
|
||||
RETURN_IF_ERROR(ReadFieldValue(tag, in, &value));
|
||||
field_values->push_back(value);
|
||||
}
|
||||
} else {
|
||||
RET_CHECK(WireFormatLite::SkipField(in, tag, out));
|
||||
}
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// Injects the data value(s) for one field into a serialized message.
|
||||
void SetFieldValues(uint32 field_id, WireFormatLite::WireType wire_type,
|
||||
const std::vector<std::string>& field_values,
|
||||
CodedOutputStream* out) {
|
||||
uint32 tag = WireFormatLite::MakeTag(field_id, wire_type);
|
||||
for (const std::string& field_value : field_values) {
|
||||
out->WriteVarint32(tag);
|
||||
if (IsLengthDelimited(wire_type)) {
|
||||
out->WriteVarint32(field_value.length());
|
||||
}
|
||||
out->WriteRaw(field_value.data(), field_value.length());
|
||||
}
|
||||
}
|
||||
|
||||
FieldAccess::FieldAccess(uint32 field_id, FieldType field_type)
|
||||
: field_id_(field_id), field_type_(field_type) {}
|
||||
|
||||
::mediapipe::Status FieldAccess::SetMessage(const std::string& message) {
|
||||
ArrayInputStream ais(message.data(), message.size());
|
||||
CodedInputStream in(&ais);
|
||||
StringOutputStream sos(&message_);
|
||||
CodedOutputStream out(&sos);
|
||||
WireFormatLite::WireType wire_type =
|
||||
WireFormatLite::WireTypeForFieldType(field_type_);
|
||||
return GetFieldValues(field_id_, wire_type, &in, &out, &field_values_);
|
||||
}
|
||||
|
||||
void FieldAccess::GetMessage(std::string* result) {
|
||||
*result = message_;
|
||||
StringOutputStream sos(result);
|
||||
CodedOutputStream out(&sos);
|
||||
WireFormatLite::WireType wire_type =
|
||||
WireFormatLite::WireTypeForFieldType(field_type_);
|
||||
SetFieldValues(field_id_, wire_type, field_values_, &out);
|
||||
}
|
||||
|
||||
std::vector<FieldValue>* FieldAccess::mutable_field_values() {
|
||||
return &field_values_;
|
||||
}
|
||||
|
||||
// Replaces a range of field values for one field nested within a protobuf.
|
||||
::mediapipe::Status ProtoUtilLite::ReplaceFieldRange(
|
||||
FieldValue* message, ProtoPath proto_path, int length, FieldType field_type,
|
||||
const std::vector<FieldValue>& field_values) {
|
||||
int field_id, index;
|
||||
std::tie(field_id, index) = proto_path.front();
|
||||
proto_path.erase(proto_path.begin());
|
||||
FieldAccess access(field_id, !proto_path.empty()
|
||||
? WireFormatLite::TYPE_MESSAGE
|
||||
: field_type);
|
||||
RETURN_IF_ERROR(access.SetMessage(*message));
|
||||
std::vector<std::string>& v = *access.mutable_field_values();
|
||||
if (!proto_path.empty()) {
|
||||
RET_CHECK(index >= 0 && index < v.size());
|
||||
RETURN_IF_ERROR(ReplaceFieldRange(&v[index], proto_path, length, field_type,
|
||||
field_values));
|
||||
} else {
|
||||
RET_CHECK(index >= 0 && index <= v.size());
|
||||
RET_CHECK(index + length >= 0 && index + length <= v.size());
|
||||
v.erase(v.begin() + index, v.begin() + index + length);
|
||||
v.insert(v.begin() + index, field_values.begin(), field_values.end());
|
||||
}
|
||||
message->clear();
|
||||
access.GetMessage(message);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// Returns a range of field values from one field nested within a protobuf.
|
||||
::mediapipe::Status ProtoUtilLite::GetFieldRange(
|
||||
const FieldValue& message, ProtoPath proto_path, int length,
|
||||
FieldType field_type, std::vector<FieldValue>* field_values) {
|
||||
int field_id, index;
|
||||
std::tie(field_id, index) = proto_path.front();
|
||||
proto_path.erase(proto_path.begin());
|
||||
FieldAccess access(field_id, !proto_path.empty()
|
||||
? WireFormatLite::TYPE_MESSAGE
|
||||
: field_type);
|
||||
RETURN_IF_ERROR(access.SetMessage(message));
|
||||
std::vector<std::string>& v = *access.mutable_field_values();
|
||||
if (!proto_path.empty()) {
|
||||
RET_CHECK(index >= 0 && index < v.size());
|
||||
RETURN_IF_ERROR(
|
||||
GetFieldRange(v[index], proto_path, length, field_type, field_values));
|
||||
} else {
|
||||
RET_CHECK(index >= 0 && index <= v.size());
|
||||
RET_CHECK(index + length >= 0 && index + length <= v.size());
|
||||
field_values->insert(field_values->begin(), v.begin() + index,
|
||||
v.begin() + index + length);
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// If ok, returns OkStatus, otherwise returns InvalidArgumentError.
|
||||
template <typename T>
|
||||
::mediapipe::Status SyntaxStatus(bool ok, const std::string& text, T* result) {
|
||||
return ok ? ::mediapipe::OkStatus()
|
||||
: ::mediapipe::InvalidArgumentError(absl::StrCat(
|
||||
"Syntax error: \"", text, "\"",
|
||||
" for type: ", MediaPipeTypeStringOrDemangled<T>(), "."));
|
||||
}
|
||||
|
||||
// Templated parsing of a std::string value.
|
||||
template <typename T>
|
||||
::mediapipe::Status ParseValue(const std::string& text, T* result) {
|
||||
return SyntaxStatus(absl::SimpleAtoi(text, result), text, result);
|
||||
}
|
||||
template <>
|
||||
::mediapipe::Status ParseValue<double>(const std::string& text,
|
||||
double* result) {
|
||||
return SyntaxStatus(absl::SimpleAtod(text, result), text, result);
|
||||
}
|
||||
template <>
|
||||
::mediapipe::Status ParseValue<float>(const std::string& text, float* result) {
|
||||
return SyntaxStatus(absl::SimpleAtof(text, result), text, result);
|
||||
}
|
||||
template <>
|
||||
::mediapipe::Status ParseValue<bool>(const std::string& text, bool* result) {
|
||||
return SyntaxStatus(absl::SimpleAtob(text, result), text, result);
|
||||
}
|
||||
template <>
|
||||
::mediapipe::Status ParseValue<std::string>(const std::string& text,
|
||||
std::string* result) {
|
||||
*result = text;
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// Templated formatting of a primitive value.
|
||||
template <typename T>
|
||||
std::string FormatValue(T v) {
|
||||
return FieldValue(absl::StrCat(v));
|
||||
}
|
||||
|
||||
// A helper function to parse and serialize one primtive value.
|
||||
template <typename T>
|
||||
::mediapipe::Status WritePrimitive(
|
||||
void (*writer)(T, proto_ns::io::CodedOutputStream*),
|
||||
const std::string& text, CodedOutputStream* out) {
|
||||
T value;
|
||||
RETURN_IF_ERROR(ParseValue<T>(text, &value));
|
||||
(*writer)(value, out);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// Serializes a protobuf FieldValue.
|
||||
static ::mediapipe::Status SerializeValue(const std::string& text,
|
||||
FieldType field_type,
|
||||
FieldValue* field_value) {
|
||||
::mediapipe::Status status;
|
||||
StringOutputStream sos(field_value);
|
||||
CodedOutputStream out(&sos);
|
||||
|
||||
using W = WireFormatLite;
|
||||
switch (field_type) {
|
||||
case W::TYPE_DOUBLE:
|
||||
return WritePrimitive(W::WriteDoubleNoTag, text, &out);
|
||||
case W::TYPE_FLOAT:
|
||||
return WritePrimitive(W::WriteFloatNoTag, text, &out);
|
||||
case W::TYPE_INT64:
|
||||
return WritePrimitive(W::WriteInt64NoTag, text, &out);
|
||||
case W::TYPE_UINT64:
|
||||
return WritePrimitive(W::WriteUInt64NoTag, text, &out);
|
||||
case W::TYPE_INT32:
|
||||
return WritePrimitive(W::WriteInt32NoTag, text, &out);
|
||||
case W::TYPE_FIXED64:
|
||||
return WritePrimitive(W::WriteFixed64NoTag, text, &out);
|
||||
case W::TYPE_FIXED32:
|
||||
return WritePrimitive(W::WriteFixed32NoTag, text, &out);
|
||||
case W::TYPE_BOOL: {
|
||||
return WritePrimitive(W::WriteBoolNoTag, text, &out);
|
||||
}
|
||||
case W::TYPE_BYTES:
|
||||
case W::TYPE_STRING: {
|
||||
out.WriteRaw(text.data(), text.size());
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
case W::TYPE_GROUP:
|
||||
case W::TYPE_MESSAGE:
|
||||
return ::mediapipe::UnimplementedError(
|
||||
"SerializeValue cannot serialize a Message.");
|
||||
case W::TYPE_UINT32:
|
||||
return WritePrimitive(W::WriteUInt32NoTag, text, &out);
|
||||
case W::TYPE_ENUM:
|
||||
return WritePrimitive(W::WriteEnumNoTag, text, &out);
|
||||
case W::TYPE_SFIXED32:
|
||||
return WritePrimitive(W::WriteSFixed32NoTag, text, &out);
|
||||
case W::TYPE_SFIXED64:
|
||||
return WritePrimitive(W::WriteSFixed64NoTag, text, &out);
|
||||
case W::TYPE_SINT32:
|
||||
return WritePrimitive(W::WriteSInt32NoTag, text, &out);
|
||||
case W::TYPE_SINT64:
|
||||
return WritePrimitive(W::WriteSInt64NoTag, text, &out);
|
||||
}
|
||||
return ::mediapipe::UnimplementedError("SerializeValue unimplemented type.");
|
||||
}
|
||||
|
||||
// A helper function for deserializing one text value.
|
||||
template <typename CType, FieldType DeclaredType>
|
||||
static ::mediapipe::Status ReadPrimitive(CodedInputStream* input,
|
||||
std::string* result) {
|
||||
CType value;
|
||||
if (!WireFormatLite::ReadPrimitive<CType, DeclaredType>(input, &value)) {
|
||||
return ::mediapipe::InvalidArgumentError(absl::StrCat(
|
||||
"Bad serialized value: ", MediaPipeTypeStringOrDemangled<CType>(),
|
||||
"."));
|
||||
}
|
||||
*result = FormatValue(value);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// Deserializes a protobuf FieldValue.
|
||||
static ::mediapipe::Status DeserializeValue(const FieldValue& bytes,
|
||||
FieldType field_type,
|
||||
std::string* result) {
|
||||
ArrayInputStream ais(bytes.data(), bytes.size());
|
||||
CodedInputStream input(&ais);
|
||||
typedef WireFormatLite W;
|
||||
switch (field_type) {
|
||||
case W::TYPE_DOUBLE:
|
||||
return ReadPrimitive<double, W::TYPE_DOUBLE>(&input, result);
|
||||
case W::TYPE_FLOAT:
|
||||
return ReadPrimitive<float, W::TYPE_FLOAT>(&input, result);
|
||||
case W::TYPE_INT64:
|
||||
return ReadPrimitive<proto_int64, W::TYPE_INT64>(&input, result);
|
||||
case W::TYPE_UINT64:
|
||||
return ReadPrimitive<proto_uint64, W::TYPE_UINT64>(&input, result);
|
||||
case W::TYPE_INT32:
|
||||
return ReadPrimitive<int32, W::TYPE_INT32>(&input, result);
|
||||
case W::TYPE_FIXED64:
|
||||
return ReadPrimitive<proto_uint64, W::TYPE_FIXED64>(&input, result);
|
||||
case W::TYPE_FIXED32:
|
||||
return ReadPrimitive<uint32, W::TYPE_FIXED32>(&input, result);
|
||||
case W::TYPE_BOOL:
|
||||
return ReadPrimitive<bool, W::TYPE_BOOL>(&input, result);
|
||||
case W::TYPE_BYTES:
|
||||
case W::TYPE_STRING: {
|
||||
*result = bytes;
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
case W::TYPE_GROUP:
|
||||
case W::TYPE_MESSAGE:
|
||||
CHECK(false) << "DeserializeValue cannot deserialize a Message.";
|
||||
case W::TYPE_UINT32:
|
||||
return ReadPrimitive<uint32, W::TYPE_UINT32>(&input, result);
|
||||
case W::TYPE_ENUM:
|
||||
return ReadPrimitive<int, W::TYPE_ENUM>(&input, result);
|
||||
case W::TYPE_SFIXED32:
|
||||
return ReadPrimitive<int32, W::TYPE_SFIXED32>(&input, result);
|
||||
case W::TYPE_SFIXED64:
|
||||
return ReadPrimitive<proto_int64, W::TYPE_SFIXED64>(&input, result);
|
||||
case W::TYPE_SINT32:
|
||||
return ReadPrimitive<int32, W::TYPE_SINT32>(&input, result);
|
||||
case W::TYPE_SINT64:
|
||||
return ReadPrimitive<proto_int64, W::TYPE_SINT64>(&input, result);
|
||||
}
|
||||
return ::mediapipe::UnimplementedError(
|
||||
"DeserializeValue unimplemented type.");
|
||||
}
|
||||
|
||||
::mediapipe::Status ProtoUtilLite::Serialize(
|
||||
const std::vector<std::string>& text_values, FieldType field_type,
|
||||
std::vector<FieldValue>* result) {
|
||||
result->clear();
|
||||
result->reserve(text_values.size());
|
||||
for (const std::string& text_value : text_values) {
|
||||
FieldValue field_value;
|
||||
RETURN_IF_ERROR(SerializeValue(text_value, field_type, &field_value));
|
||||
result->push_back(field_value);
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status ProtoUtilLite::Deserialize(
|
||||
const std::vector<FieldValue>& field_values, FieldType field_type,
|
||||
std::vector<std::string>* result) {
|
||||
result->clear();
|
||||
result->reserve(field_values.size());
|
||||
for (const FieldValue& field_value : field_values) {
|
||||
std::string text_value;
|
||||
RETURN_IF_ERROR(DeserializeValue(field_value, field_type, &text_value));
|
||||
result->push_back(text_value);
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_PROTO_UTIL_LITE_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_PROTO_UTIL_LITE_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "mediapipe/framework/port/advanced_proto_lite_inc.h"
|
||||
#include "mediapipe/framework/port/integral_types.h"
|
||||
#include "mediapipe/framework/port/proto_ns.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
class ProtoUtilLite {
|
||||
public:
|
||||
// Defines field types and tag formats.
|
||||
using WireFormatLite = proto_ns::internal::WireFormatLite;
|
||||
|
||||
// Defines a sequence of nested field-number field-index pairs.
|
||||
using ProtoPath = std::vector<std::pair<int, int>>;
|
||||
|
||||
// The serialized value for a protobuf field.
|
||||
using FieldValue = std::string;
|
||||
|
||||
// The serialized data type for a protobuf field.
|
||||
using FieldType = WireFormatLite::FieldType;
|
||||
|
||||
class FieldAccess {
|
||||
public:
|
||||
// Provides access to a certain protobuf field.
|
||||
FieldAccess(uint32 field_id, FieldType field_type);
|
||||
|
||||
// Specifies the original serialized protobuf message.
|
||||
::mediapipe::Status SetMessage(const FieldValue& message);
|
||||
|
||||
// Returns the serialized protobuf message with updated field values.
|
||||
void GetMessage(FieldValue* result);
|
||||
|
||||
// Returns the serialized values of the protobuf field.
|
||||
std::vector<FieldValue>* mutable_field_values();
|
||||
|
||||
private:
|
||||
const uint32 field_id_;
|
||||
const FieldType field_type_;
|
||||
std::string message_;
|
||||
std::vector<FieldValue> field_values_;
|
||||
};
|
||||
|
||||
// Replace a range of field values nested within a protobuf.
|
||||
// Starting at the proto_path index, "length" values are replaced.
|
||||
static ::mediapipe::Status ReplaceFieldRange(
|
||||
FieldValue* message, ProtoPath proto_path, int length,
|
||||
FieldType field_type, const std::vector<FieldValue>& field_values);
|
||||
|
||||
// Retrieve a range of field values nested within a protobuf.
|
||||
// Starting at the proto_path index, "length" values are retrieved.
|
||||
static ::mediapipe::Status GetFieldRange(
|
||||
const FieldValue& message, ProtoPath proto_path, int length,
|
||||
FieldType field_type, std::vector<FieldValue>* field_values);
|
||||
|
||||
// Serialize one or more protobuf field values from text.
|
||||
static ::mediapipe::Status Serialize(
|
||||
const std::vector<std::string>& text_values, FieldType field_type,
|
||||
std::vector<FieldValue>* result);
|
||||
|
||||
// Deserialize one or more protobuf field values to text.
|
||||
static ::mediapipe::Status Deserialize(
|
||||
const std::vector<FieldValue>& field_values, FieldType field_type,
|
||||
std::vector<std::string>* result);
|
||||
};
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_PROTO_UTIL_LITE_H_
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/simulation_clock.h"
|
||||
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "mediapipe/framework/port/logging.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
absl::Time SimulationClock::TimeNow() {
|
||||
absl::MutexLock l(&time_mutex_);
|
||||
return time_;
|
||||
}
|
||||
|
||||
void SimulationClock::Sleep(absl::Duration d) {
|
||||
absl::MutexLock l(&time_mutex_);
|
||||
SleepInternal(time_ + d);
|
||||
}
|
||||
|
||||
void SimulationClock::SleepUntil(absl::Time wakeup_time) {
|
||||
absl::MutexLock l(&time_mutex_);
|
||||
SleepInternal(wakeup_time);
|
||||
}
|
||||
|
||||
void SimulationClock::SleepInternal(absl::Time wakeup_time) {
|
||||
Waiter waiter;
|
||||
waiters_.insert({wakeup_time, &waiter});
|
||||
num_running_--;
|
||||
TryAdvanceTime();
|
||||
while (waiter.sleeping) {
|
||||
waiter.cond.Wait(&time_mutex_);
|
||||
}
|
||||
num_running_++;
|
||||
}
|
||||
|
||||
void SimulationClock::ThreadStart() {
|
||||
absl::MutexLock l(&time_mutex_);
|
||||
num_running_++;
|
||||
}
|
||||
|
||||
void SimulationClock::ThreadFinish() {
|
||||
absl::MutexLock l(&time_mutex_);
|
||||
num_running_--;
|
||||
TryAdvanceTime();
|
||||
}
|
||||
|
||||
void SimulationClock::TryAdvanceTime() {
|
||||
if (num_running_ == 0 && !waiters_.empty()) {
|
||||
VLOG(2) << "Advance time from: " << absl::ToUnixMicros(time_)
|
||||
<< " to: " << absl::ToUnixMicros(waiters_.begin()->first);
|
||||
time_ = waiters_.begin()->first;
|
||||
Waiter* waiter = waiters_.begin()->second;
|
||||
waiters_.erase(waiters_.begin());
|
||||
waiter->sleeping = false;
|
||||
waiter->cond.Signal();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_SIMULATION_CLOCK_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_SIMULATION_CLOCK_H_
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "mediapipe/framework/deps/clock.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
// A Clock that simulates the passage of time as quickly as possible.
|
||||
// Parallel threads can call Sleep() and be woken in the proper order.
|
||||
// Simulated time can be advanced by calling SleepUntil for the desired
|
||||
// clock time. Clock time does not advance until all woken threads have
|
||||
// finished or gone back to sleep.
|
||||
//
|
||||
// In case multiple threads are scheduled to wake at the same time, they
|
||||
// wake in the order in which they called Sleep(). No thread is permitted
|
||||
// to continue until all earlier threads have finished or entered Sleep.
|
||||
// The result is a single well-defined order of events. Any desired
|
||||
// order of events can be defined by adjusting the precise sleep times.
|
||||
class SimulationClock : public mediapipe::Clock {
|
||||
public:
|
||||
SimulationClock() {}
|
||||
~SimulationClock() override {}
|
||||
|
||||
// Returns the simulated time.
|
||||
absl::Time TimeNow() override;
|
||||
|
||||
// Sleeps until the specified duration has elapsed according to this clock.
|
||||
void Sleep(absl::Duration d) override;
|
||||
|
||||
// Sleeps until the specifed wakeup_time.
|
||||
void SleepUntil(absl::Time wakeup_time) override;
|
||||
|
||||
// Informs this clock that a woken thread has started running.
|
||||
void ThreadStart();
|
||||
|
||||
// Informs this clock that a woken thread has finished running.
|
||||
void ThreadFinish();
|
||||
|
||||
protected:
|
||||
// Queue up wake up waiter.
|
||||
void SleepInternal(absl::Time wakeup_time)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(time_mutex_);
|
||||
// Advances to the next wake up time if no related threads are running.
|
||||
void TryAdvanceTime() EXCLUSIVE_LOCKS_REQUIRED(time_mutex_);
|
||||
|
||||
// Represents a thread blocked in SleepUntil.
|
||||
struct Waiter {
|
||||
bool sleeping = true;
|
||||
absl::CondVar cond;
|
||||
};
|
||||
|
||||
protected:
|
||||
absl::Mutex time_mutex_;
|
||||
absl::Time time_ GUARDED_BY(time_mutex_);
|
||||
std::multimap<absl::Time, Waiter*> waiters_ GUARDED_BY(time_mutex_);
|
||||
int num_running_ GUARDED_BY(time_mutex_) = 0;
|
||||
};
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_SIMULATION_CLOCK_H_
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/simulation_clock_executor.h"
|
||||
|
||||
#include "mediapipe/framework/tool/simulation_clock.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
SimulationClockExecutor::SimulationClockExecutor(int num_threads)
|
||||
: ThreadPoolExecutor(num_threads), clock_(new SimulationClock()) {}
|
||||
|
||||
void SimulationClockExecutor::Schedule(std::function<void()> task) {
|
||||
clock_->ThreadStart();
|
||||
ThreadPoolExecutor::Schedule([this, task] {
|
||||
clock_->Sleep(absl::ZeroDuration());
|
||||
task();
|
||||
clock_->ThreadFinish();
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<SimulationClock> SimulationClockExecutor::GetClock() {
|
||||
return clock_;
|
||||
}
|
||||
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_SIMULATION_CLOCK_EXECUTOR_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_SIMULATION_CLOCK_EXECUTOR_H_
|
||||
|
||||
#include "mediapipe/framework/thread_pool_executor.h"
|
||||
#include "mediapipe/framework/tool/simulation_clock.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
// Simulation clock multithreaded executor. This is intended to be used with
|
||||
// graphs that are using SimulationClock class to emulate various parts of the
|
||||
// graph taking specific time to process the incoming packets.
|
||||
class SimulationClockExecutor : public ThreadPoolExecutor {
|
||||
public:
|
||||
explicit SimulationClockExecutor(int num_threads);
|
||||
void Schedule(std::function<void()> task) override;
|
||||
|
||||
// Returns a pointer to the instance of SimulationClock used by
|
||||
// this executor. This instance can be passed down to graph nodes as input
|
||||
// side packet.
|
||||
std::shared_ptr<SimulationClock> GetClock();
|
||||
|
||||
private:
|
||||
// SimulationClock instance used by this executor.
|
||||
std::shared_ptr<SimulationClock> clock_;
|
||||
};
|
||||
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_SIMULATION_CLOCK_EXECUTOR_H_
|
||||
@@ -0,0 +1,246 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/simulation_clock.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/executor.h"
|
||||
#include "mediapipe/framework/input_stream.h"
|
||||
#include "mediapipe/framework/output_stream.h"
|
||||
#include "mediapipe/framework/port/core_proto_inc.h"
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/logging.h"
|
||||
#include "mediapipe/framework/port/parse_text_proto.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h"
|
||||
#include "mediapipe/framework/tool/simulation_clock_executor.h"
|
||||
|
||||
using testing::ElementsAre;
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace {
|
||||
|
||||
class SimulationClockTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUpInFlightGraph() {
|
||||
graph_config_ = ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
|
||||
input_stream: "input_packets_0"
|
||||
node {
|
||||
calculator: 'RealTimeFlowLimiterCalculator'
|
||||
input_stream_handler {
|
||||
input_stream_handler: 'ImmediateInputStreamHandler'
|
||||
}
|
||||
input_side_packet: 'MAX_IN_FLIGHT:max_in_flight'
|
||||
input_stream: 'input_packets_0'
|
||||
input_stream: 'FINISHED:finish_indicator'
|
||||
input_stream_info: { tag_index: 'FINISHED' back_edge: true }
|
||||
output_stream: 'input_0_sampled'
|
||||
}
|
||||
node {
|
||||
calculator: "RoundRobinDemuxCalculator"
|
||||
input_stream: "input_0_sampled"
|
||||
output_stream: "OUTPUT:0:input_0"
|
||||
output_stream: "OUTPUT:1:input_1"
|
||||
}
|
||||
node {
|
||||
calculator: "LambdaCalculator"
|
||||
input_side_packet: 'callback_0'
|
||||
input_stream: "input_0"
|
||||
output_stream: "output_0"
|
||||
}
|
||||
node {
|
||||
calculator: "LambdaCalculator"
|
||||
input_side_packet: 'callback_1'
|
||||
input_stream: "input_1"
|
||||
output_stream: "output_1"
|
||||
}
|
||||
node {
|
||||
calculator: "ImmediateMuxCalculator"
|
||||
input_stream_handler {
|
||||
input_stream_handler: "ImmediateInputStreamHandler"
|
||||
}
|
||||
input_stream: "output_0"
|
||||
input_stream: "output_1"
|
||||
output_stream: 'output_packets_0'
|
||||
output_stream: 'finish_indicator'
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
// Initialize the test clock as a SimulationClock.
|
||||
void SetupSimulationClock() {
|
||||
auto executor = std::make_shared<SimulationClockExecutor>(4);
|
||||
simulation_clock_ = executor->GetClock();
|
||||
clock_ = simulation_clock_.get();
|
||||
MEDIAPIPE_ASSERT_OK(graph_.SetExecutor("", executor));
|
||||
}
|
||||
|
||||
// Initialize the test clock as a RealClock.
|
||||
void SetupRealClock() { clock_ = ::mediapipe::Clock::RealClock(); }
|
||||
|
||||
// Return the values of the timestamps of a vector of Packets.
|
||||
static std::vector<int64> TimestampValues(
|
||||
const std::vector<Packet>& packets) {
|
||||
std::vector<int64> result;
|
||||
for (const Packet& p : packets) {
|
||||
result.push_back(p.Timestamp().Value());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<int64> TimeValues(const std::vector<absl::Time>& times) {
|
||||
std::vector<int64> result;
|
||||
for (const absl::Time& t : times) {
|
||||
result.push_back(absl::ToUnixMicros(t));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<SimulationClock> simulation_clock_;
|
||||
CalculatorGraphConfig graph_config_;
|
||||
CalculatorGraph graph_;
|
||||
::mediapipe::Clock* clock_;
|
||||
};
|
||||
|
||||
// Just directly calls SimulationClock::Sleep on several threads.
|
||||
TEST_F(SimulationClockTest, SleepUntil) {
|
||||
std::vector<absl::Time> start_times;
|
||||
auto executor = std::make_shared<SimulationClockExecutor>(4);
|
||||
simulation_clock_ = executor->GetClock();
|
||||
clock_ = simulation_clock_.get();
|
||||
|
||||
std::function<void(int)> run_chain = [&](int count) {
|
||||
if (count > 0) {
|
||||
start_times.push_back(clock_->TimeNow());
|
||||
clock_->Sleep(absl::Microseconds(10000));
|
||||
run_chain(count - 1);
|
||||
}
|
||||
};
|
||||
simulation_clock_->ThreadStart();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
executor->Schedule([&] { run_chain(3); });
|
||||
clock_->Sleep(absl::Microseconds(2000));
|
||||
}
|
||||
clock_->Sleep(absl::Microseconds(100000));
|
||||
simulation_clock_->ThreadFinish();
|
||||
EXPECT_THAT(
|
||||
TimeValues(start_times), //
|
||||
ElementsAre(0, 2000, 4000, 10000, 12000, 14000, 20000, 22000, 24000));
|
||||
}
|
||||
|
||||
// Directly calls SimulationClock::Sleep with duplicate wake times.
|
||||
TEST_F(SimulationClockTest, DuplicateWakeTimes) {
|
||||
std::vector<absl::Time> start_times;
|
||||
std::vector<int> start_counts;
|
||||
auto executor = std::make_shared<SimulationClockExecutor>(4);
|
||||
simulation_clock_ = executor->GetClock();
|
||||
clock_ = simulation_clock_.get();
|
||||
std::function<void(int)> run_chain = [&](int count) {
|
||||
if (count > 0) {
|
||||
start_times.push_back(clock_->TimeNow());
|
||||
start_counts.push_back(count);
|
||||
clock_->Sleep(absl::Microseconds(10000));
|
||||
run_chain(count - 1);
|
||||
}
|
||||
};
|
||||
simulation_clock_->ThreadStart();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
executor->Schedule([&] { run_chain(3); });
|
||||
clock_->Sleep(absl::Microseconds(10000));
|
||||
}
|
||||
clock_->Sleep(absl::Microseconds(100000));
|
||||
simulation_clock_->ThreadFinish();
|
||||
EXPECT_THAT(
|
||||
TimeValues(start_times),
|
||||
ElementsAre(0, 10000, 10000, 20000, 20000, 20000, 30000, 30000, 40000));
|
||||
EXPECT_THAT(start_counts, ElementsAre(3, 2, 3, 1, 2, 3, 1, 2, 1));
|
||||
}
|
||||
|
||||
// A Calculator::Process callback function.
|
||||
typedef std::function<::mediapipe::Status(const InputStreamShardSet&,
|
||||
OutputStreamShardSet*)>
|
||||
ProcessFunction;
|
||||
|
||||
// A testing callback function that passes through all packets.
|
||||
::mediapipe::Status PassThrough(const InputStreamShardSet& inputs,
|
||||
OutputStreamShardSet* outputs) {
|
||||
for (int i = 0; i < inputs.NumEntries(); ++i) {
|
||||
if (!inputs.Index(i).Value().IsEmpty()) {
|
||||
outputs->Index(i).AddPacket(inputs.Index(i).Value());
|
||||
}
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// This test shows sim clock synchronizing a bunch of parallel tasks.
|
||||
TEST_F(SimulationClockTest, InFlight) {
|
||||
// Callbacks to control the MockCalculators.
|
||||
// SetupSimulationClock can be replaced by SetupRealClock to run
|
||||
// the test over 200 ms of real time rather simulated time.
|
||||
SetupSimulationClock();
|
||||
ProcessFunction wait_0 = [&](const InputStreamShardSet& inputs,
|
||||
OutputStreamShardSet* outputs) {
|
||||
clock_->Sleep(absl::Microseconds(20000));
|
||||
return PassThrough(inputs, outputs);
|
||||
};
|
||||
ProcessFunction wait_1 = [&](const InputStreamShardSet& inputs,
|
||||
OutputStreamShardSet* outputs) {
|
||||
clock_->Sleep(absl::Microseconds(30000));
|
||||
return PassThrough(inputs, outputs);
|
||||
};
|
||||
|
||||
// Start the graph with the callbacks.
|
||||
SetUpInFlightGraph();
|
||||
std::vector<Packet> out_packets;
|
||||
tool::AddVectorSink("output_packets_0", &graph_config_, &out_packets);
|
||||
MEDIAPIPE_ASSERT_OK(graph_.Initialize(
|
||||
graph_config_, {
|
||||
{"max_in_flight", MakePacket<int>(2)},
|
||||
{"callback_0", Adopt(new auto(wait_0))},
|
||||
{"callback_1", Adopt(new auto(wait_1))},
|
||||
}));
|
||||
MEDIAPIPE_ASSERT_OK(graph_.StartRun({}));
|
||||
simulation_clock_->ThreadStart();
|
||||
|
||||
// Add 10 input packets to the graph, one each 10 ms, starting after 11 ms
|
||||
// of clock time. Timestamps lag clock times by 1 ms.
|
||||
clock_->Sleep(absl::Microseconds(11000));
|
||||
for (uint64 ts = 10000; ts <= 100000; ts += 10000) {
|
||||
MEDIAPIPE_EXPECT_OK(graph_.AddPacketToInputStream(
|
||||
"input_packets_0", MakePacket<uint64>(ts).At(Timestamp(ts))));
|
||||
clock_->Sleep(absl::Microseconds(10000));
|
||||
}
|
||||
|
||||
// Wait for 100 ms of clock time, then close the graph.
|
||||
clock_->Sleep(absl::Microseconds(100000));
|
||||
simulation_clock_->ThreadFinish();
|
||||
MEDIAPIPE_ASSERT_OK(graph_.CloseAllInputStreams());
|
||||
MEDIAPIPE_ASSERT_OK(graph_.WaitUntilDone());
|
||||
|
||||
// Validate the graph run.
|
||||
EXPECT_THAT(TimestampValues(out_packets),
|
||||
ElementsAre(10000, 20000, 40000, 60000, 70000, 100000));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,361 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Forked from mediapipe/framework/tool/source.proto.
|
||||
// The forked proto must remain identical to the original proto and should be
|
||||
// ONLY used by mediapipe open source project.
|
||||
|
||||
#include "mediapipe/framework/tool/sink.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "mediapipe/calculators/internal/callback_packet_calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
#include "mediapipe/framework/calculator_base.h"
|
||||
#include "mediapipe/framework/calculator_graph.h"
|
||||
#include "mediapipe/framework/calculator_registry.h"
|
||||
#include "mediapipe/framework/input_stream.h"
|
||||
#include "mediapipe/framework/packet.h"
|
||||
#include "mediapipe/framework/packet_type.h"
|
||||
#include "mediapipe/framework/port/logging.h"
|
||||
#include "mediapipe/framework/port/source_location.h"
|
||||
#include "mediapipe/framework/port/status_builder.h"
|
||||
#include "mediapipe/framework/tool/name_util.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
namespace {
|
||||
// Produces an output packet with the PostStream timestamp containing the
|
||||
// input side packet.
|
||||
class MediaPipeInternalSidePacketToPacketStreamCalculator
|
||||
: public CalculatorBase {
|
||||
public:
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
|
||||
cc->InputSidePackets().Index(0).SetAny();
|
||||
cc->Outputs().Index(0).SetSameAs(&cc->InputSidePackets().Index(0));
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) final {
|
||||
cc->Outputs().Index(0).AddPacket(
|
||||
cc->InputSidePackets().Index(0).At(Timestamp::PostStream()));
|
||||
cc->Outputs().Index(0).Close();
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Process(CalculatorContext* cc) final {
|
||||
// The framework treats this calculator as a source calculator.
|
||||
return ::mediapipe::tool::StatusStop();
|
||||
}
|
||||
};
|
||||
REGISTER_CALCULATOR(MediaPipeInternalSidePacketToPacketStreamCalculator);
|
||||
} // namespace
|
||||
|
||||
void AddVectorSink(const std::string& stream_name, //
|
||||
CalculatorGraphConfig* config, //
|
||||
std::vector<Packet>* dumped_data) {
|
||||
CHECK(config);
|
||||
CHECK(dumped_data);
|
||||
|
||||
std::string input_side_packet_name;
|
||||
tool::AddCallbackCalculator(stream_name, config, &input_side_packet_name,
|
||||
/*use_std_function=*/true);
|
||||
|
||||
auto* node = config->add_node();
|
||||
node->set_name(GetUnusedNodeName(
|
||||
*config, absl::StrCat("callback_packet_calculator_that_generators_",
|
||||
input_side_packet_name)));
|
||||
node->set_calculator("CallbackPacketCalculator");
|
||||
node->add_output_side_packet(input_side_packet_name);
|
||||
CallbackPacketCalculatorOptions* options =
|
||||
node->mutable_options()->MutableExtension(
|
||||
CallbackPacketCalculatorOptions::ext);
|
||||
options->set_type(CallbackPacketCalculatorOptions::VECTOR_PACKET);
|
||||
char address[17];
|
||||
int written = snprintf(address, sizeof(address), "%p", dumped_data);
|
||||
CHECK(written > 0 && written < sizeof(address));
|
||||
options->set_pointer(address);
|
||||
}
|
||||
|
||||
void AddPostStreamPacketSink(const std::string& stream_name,
|
||||
CalculatorGraphConfig* config,
|
||||
Packet* post_stream_packet) {
|
||||
CHECK(config);
|
||||
CHECK(post_stream_packet);
|
||||
|
||||
std::string input_side_packet_name;
|
||||
tool::AddCallbackCalculator(stream_name, config, &input_side_packet_name,
|
||||
/*use_std_function=*/true);
|
||||
auto* node = config->add_node();
|
||||
node->set_name(GetUnusedNodeName(
|
||||
*config, absl::StrCat("callback_packet_calculator_that_generators_",
|
||||
input_side_packet_name)));
|
||||
node->set_calculator("CallbackPacketCalculator");
|
||||
node->add_output_side_packet(input_side_packet_name);
|
||||
CallbackPacketCalculatorOptions* options =
|
||||
node->mutable_options()->MutableExtension(
|
||||
CallbackPacketCalculatorOptions::ext);
|
||||
options->set_type(CallbackPacketCalculatorOptions::POST_STREAM_PACKET);
|
||||
char address[17];
|
||||
int written = snprintf(address, sizeof(address), "%p", post_stream_packet);
|
||||
CHECK(written > 0 && written < sizeof(address));
|
||||
options->set_pointer(address);
|
||||
}
|
||||
|
||||
void AddSidePacketSink(const std::string& side_packet_name,
|
||||
CalculatorGraphConfig* config, Packet* dumped_packet) {
|
||||
CHECK(config);
|
||||
CHECK(dumped_packet);
|
||||
|
||||
CalculatorGraphConfig::Node* conversion_node = config->add_node();
|
||||
const std::string node_name = GetUnusedNodeName(
|
||||
*config,
|
||||
absl::StrCat("calculator_converts_side_packet_", side_packet_name));
|
||||
conversion_node->set_name(node_name);
|
||||
conversion_node->set_calculator(
|
||||
"MediaPipeInternalSidePacketToPacketStreamCalculator");
|
||||
conversion_node->add_input_side_packet(
|
||||
GetUnusedSidePacketName(*config, side_packet_name));
|
||||
|
||||
const std::string output_stream_name =
|
||||
absl::StrCat(node_name, "_output_stream");
|
||||
conversion_node->add_output_stream(output_stream_name);
|
||||
AddPostStreamPacketSink(output_stream_name, config, dumped_packet);
|
||||
}
|
||||
|
||||
void AddCallbackCalculator(const std::string& stream_name,
|
||||
CalculatorGraphConfig* config,
|
||||
std::string* callback_side_packet_name,
|
||||
bool use_std_function) {
|
||||
CHECK(config);
|
||||
CHECK(callback_side_packet_name);
|
||||
CalculatorGraphConfig::Node* sink_node = config->add_node();
|
||||
sink_node->set_name(GetUnusedNodeName(
|
||||
*config,
|
||||
absl::StrCat("callback_calculator_that_collects_stream_", stream_name)));
|
||||
sink_node->set_calculator("CallbackCalculator");
|
||||
sink_node->add_input_stream(stream_name);
|
||||
|
||||
const std::string input_side_packet_name =
|
||||
GetUnusedSidePacketName(*config, absl::StrCat(stream_name, "_callback"));
|
||||
*callback_side_packet_name = input_side_packet_name;
|
||||
if (use_std_function) {
|
||||
// Uses tag "CALLBACK" if the input side packet contains a std::function.
|
||||
sink_node->add_input_side_packet(
|
||||
absl::StrCat("CALLBACK:", input_side_packet_name));
|
||||
} else {
|
||||
LOG(FATAL) << "AddCallbackCalculator must use std::function";
|
||||
}
|
||||
}
|
||||
|
||||
void AddMultiStreamCallback(
|
||||
const std::vector<std::string>& streams,
|
||||
std::function<void(const std::vector<Packet>&)> callback,
|
||||
CalculatorGraphConfig* config,
|
||||
std::pair<std::string, Packet>* side_packet) {
|
||||
CHECK(config);
|
||||
CHECK(side_packet);
|
||||
CalculatorGraphConfig::Node* sink_node = config->add_node();
|
||||
const std::string name = GetUnusedNodeName(
|
||||
*config, absl::StrCat("multi_callback_", absl::StrJoin(streams, "_")));
|
||||
sink_node->set_name(name);
|
||||
sink_node->set_calculator("CallbackCalculator");
|
||||
for (const auto& stream_name : streams) {
|
||||
sink_node->add_input_stream(stream_name);
|
||||
}
|
||||
|
||||
const std::string input_side_packet_name =
|
||||
GetUnusedSidePacketName(*config, absl::StrCat(name, "_callback"));
|
||||
side_packet->first = input_side_packet_name;
|
||||
sink_node->add_input_side_packet(
|
||||
absl::StrCat("VECTOR_CALLBACK:", input_side_packet_name));
|
||||
|
||||
side_packet->second =
|
||||
MakePacket<std::function<void(const std::vector<Packet>&)>>(
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void AddCallbackWithHeaderCalculator(const std::string& stream_name,
|
||||
const std::string& stream_header,
|
||||
CalculatorGraphConfig* config,
|
||||
std::string* callback_side_packet_name,
|
||||
bool use_std_function) {
|
||||
CHECK(config);
|
||||
CHECK(callback_side_packet_name);
|
||||
CalculatorGraphConfig::Node* sink_node = config->add_node();
|
||||
sink_node->set_name(GetUnusedNodeName(
|
||||
*config,
|
||||
absl::StrCat("callback_calculator_that_collects_stream_and_header_",
|
||||
stream_name, "_", stream_header)));
|
||||
sink_node->set_calculator("CallbackWithHeaderCalculator");
|
||||
sink_node->add_input_stream(absl::StrCat("INPUT:", stream_name));
|
||||
sink_node->add_input_stream(absl::StrCat("HEADER:", stream_header));
|
||||
|
||||
const std::string input_side_packet_name = GetUnusedSidePacketName(
|
||||
*config, absl::StrCat(stream_name, "_", stream_header, "_callback"));
|
||||
*callback_side_packet_name = input_side_packet_name;
|
||||
|
||||
if (use_std_function) {
|
||||
// Uses tag "CALLBACK" if the input side packet contains a std::function.
|
||||
sink_node->add_input_side_packet(
|
||||
absl::StrCat("CALLBACK:", input_side_packet_name));
|
||||
} else {
|
||||
LOG(FATAL) << "AddCallbackWithHeaderCalculator must use std::function";
|
||||
}
|
||||
}
|
||||
|
||||
// CallbackCalculator
|
||||
|
||||
// static
|
||||
::mediapipe::Status CallbackCalculator::GetContract(CalculatorContract* cc) {
|
||||
bool allow_multiple_streams = false;
|
||||
// If the input side packet is specified using tag "CALLBACK" it must contain
|
||||
// a std::function, which may be generated by CallbackPacketCalculator.
|
||||
|
||||
if (cc->InputSidePackets().HasTag("CALLBACK")) {
|
||||
cc->InputSidePackets()
|
||||
.Tag("CALLBACK")
|
||||
.Set<std::function<void(const Packet&)>>();
|
||||
} else if (cc->InputSidePackets().HasTag("VECTOR_CALLBACK")) {
|
||||
cc->InputSidePackets()
|
||||
.Tag("VECTOR_CALLBACK")
|
||||
.Set<std::function<void(const std::vector<Packet>&)>>();
|
||||
allow_multiple_streams = true;
|
||||
} else {
|
||||
return ::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "InputSidePackets must use tags.";
|
||||
}
|
||||
|
||||
int count = allow_multiple_streams ? cc->Inputs().NumEntries("") : 1;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
cc->Inputs().Index(i).SetAny();
|
||||
}
|
||||
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status CallbackCalculator::Open(CalculatorContext* cc) {
|
||||
if (cc->InputSidePackets().HasTag("CALLBACK")) {
|
||||
callback_ = cc->InputSidePackets()
|
||||
.Tag("CALLBACK")
|
||||
.Get<std::function<void(const Packet&)>>();
|
||||
} else if (cc->InputSidePackets().HasTag("VECTOR_CALLBACK")) {
|
||||
vector_callback_ =
|
||||
cc->InputSidePackets()
|
||||
.Tag("VECTOR_CALLBACK")
|
||||
.Get<std::function<void(const std::vector<Packet>&)>>();
|
||||
} else {
|
||||
LOG(FATAL) << "InputSidePackets must use tags.";
|
||||
}
|
||||
if (callback_ == nullptr && vector_callback_ == nullptr) {
|
||||
return ::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "missing callback.";
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status CallbackCalculator::Process(CalculatorContext* cc) {
|
||||
if (callback_) {
|
||||
callback_(cc->Inputs().Index(0).Value());
|
||||
} else if (vector_callback_) {
|
||||
int count = cc->Inputs().NumEntries("");
|
||||
std::vector<Packet> packets;
|
||||
packets.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
packets.push_back(cc->Inputs().Index(i).Value());
|
||||
}
|
||||
vector_callback_(packets);
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
REGISTER_CALCULATOR(CallbackCalculator);
|
||||
|
||||
// CallbackWithHeaderCalculator
|
||||
|
||||
// static
|
||||
::mediapipe::Status CallbackWithHeaderCalculator::GetContract(
|
||||
CalculatorContract* cc) {
|
||||
cc->Inputs().Tag("INPUT").SetAny();
|
||||
cc->Inputs().Tag("HEADER").SetAny();
|
||||
|
||||
if (cc->InputSidePackets().UsesTags()) {
|
||||
CHECK(cc->InputSidePackets().HasTag("CALLBACK"));
|
||||
cc->InputSidePackets()
|
||||
.Tag("CALLBACK")
|
||||
.Set<std::function<void(const Packet&, const Packet&)>>();
|
||||
} else {
|
||||
return ::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "InputSidePackets must use tags.";
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status CallbackWithHeaderCalculator::Open(CalculatorContext* cc) {
|
||||
if (cc->InputSidePackets().UsesTags()) {
|
||||
callback_ = cc->InputSidePackets()
|
||||
.Tag("CALLBACK")
|
||||
.Get<std::function<void(const Packet&, const Packet&)>>();
|
||||
} else {
|
||||
LOG(FATAL) << "InputSidePackets must use tags.";
|
||||
}
|
||||
if (callback_ == nullptr) {
|
||||
return ::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "callback is nullptr.";
|
||||
}
|
||||
if (!cc->Inputs().HasTag("INPUT")) {
|
||||
return ::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "No input stream connected.";
|
||||
}
|
||||
if (!cc->Inputs().HasTag("HEADER")) {
|
||||
// Note: for the current MediaPipe header implementation, we just need to
|
||||
// connect the output stream to both of the two inputs: INPUT and HEADER.
|
||||
return ::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "No header stream connected.";
|
||||
}
|
||||
// If the input stream has the header, just use it as the header. Otherwise,
|
||||
// assume the header is coming from HEADER stream.
|
||||
if (!cc->Inputs().Tag("INPUT").Header().IsEmpty()) {
|
||||
header_packet_ = cc->Inputs().Tag("INPUT").Header();
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status CallbackWithHeaderCalculator::Process(
|
||||
CalculatorContext* cc) {
|
||||
if (!cc->Inputs().Tag("INPUT").Value().IsEmpty() &&
|
||||
header_packet_.IsEmpty()) {
|
||||
// Header packet should be available before we receive any normal input
|
||||
// stream packet.
|
||||
return ::mediapipe::UnknownErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "Header not available!";
|
||||
}
|
||||
if (header_packet_.IsEmpty() &&
|
||||
!cc->Inputs().Tag("HEADER").Value().IsEmpty()) {
|
||||
header_packet_ = cc->Inputs().Tag("HEADER").Value();
|
||||
}
|
||||
if (!cc->Inputs().Tag("INPUT").Value().IsEmpty()) {
|
||||
callback_(cc->Inputs().Tag("INPUT").Value(), header_packet_);
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
REGISTER_CALCULATOR(CallbackWithHeaderCalculator);
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Functions for adding Calculators that dump data from a Graph.
|
||||
//
|
||||
// Specifically this is accomplished by adding a CallbackCalculator to
|
||||
// the CalculatorGraphConfig and adding a corresponding InputSidePacket
|
||||
// to a CalculatorGraph such that data which is sent on a stream will
|
||||
// be captured in the desired way. These functions are meant to isolate
|
||||
// clients from such messy details.
|
||||
//
|
||||
// Although these functions are basically manipulations on a
|
||||
// CalculatorGraphConfig they are not placed in tool/graph.h since they
|
||||
// also depend on CalculatorGraph and having them in tool/graph.h would
|
||||
// introduce a circular dependency.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_SINK_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_SINK_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/macros.h"
|
||||
#include "mediapipe/framework/calculator_base.h"
|
||||
#include "mediapipe/framework/packet_type.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
class CalculatorGraph;
|
||||
class Packet;
|
||||
|
||||
namespace tool {
|
||||
|
||||
////////////////////////////////////////
|
||||
// All functions in this file should be avoided when possible, since they
|
||||
// only work when the CalculatorGraph is being explicitly instantiated
|
||||
// on the local machine. Instead, prefer methods which only manipulate
|
||||
// the CalculatorGraphConfig.
|
||||
////////////////////////////////////////
|
||||
|
||||
// Add a CallbackCalculator to the config and an InputSidePacket to
|
||||
// the uninitialized_graph such that the packets from stream_name
|
||||
// will get dumped to dumped_data when the CalculatorGraph is Run.
|
||||
// The provided graph must have been constructed but not initialized
|
||||
// (this allows an InputSidePacket to be added to the CalculatorGraph,
|
||||
// and also allows the CalculatorGraphConfig to still be changed before
|
||||
// it is used for initialization). Any number of these functions can
|
||||
// be called on graph.
|
||||
//
|
||||
// Example usage:
|
||||
// CalculatorGraphConfig config = tool::ParseGraphFromFileOrDie("config.txt");
|
||||
// std::vector<Packet> packet_dump;
|
||||
// tool::AddVectorSink("output_samples", &config, &packet_dump,
|
||||
// /*use_std_function=*/true);
|
||||
// // Call tool::AddVectorSink() more times if you wish.
|
||||
// CalculatorGraph graph;
|
||||
// CHECK_OK(graph.Initialize(config));
|
||||
// // Set other input side packets.
|
||||
// CHECK_OK(graph.Run());
|
||||
// for (const Packet& packet : packet_dump) {
|
||||
// // Do something.
|
||||
// }
|
||||
void AddVectorSink(const std::string& stream_name, //
|
||||
CalculatorGraphConfig* config, //
|
||||
std::vector<Packet>* dumped_data);
|
||||
|
||||
// Same as above, but only extract the Timestamp::PostStream() packet
|
||||
// of the stream.
|
||||
void AddPostStreamPacketSink(const std::string& stream_name,
|
||||
CalculatorGraphConfig* config,
|
||||
Packet* post_stream_packet);
|
||||
|
||||
// Gets a side packet from a graph.
|
||||
// Adds a conversion calculator to convert a side packet to a stream with a
|
||||
// single packet at timestamp PostStream and then calls AddPostStreamPacketSink
|
||||
// to dump the packet.
|
||||
ABSL_DEPRECATED("Use CalculatorGraph::GetOutputSidePacket(const std::string&)")
|
||||
void AddSidePacketSink(const std::string& side_packet_name,
|
||||
CalculatorGraphConfig* config, Packet* dumped_packet);
|
||||
|
||||
// Add a CallbackCalculator to intercept packets sent on stream
|
||||
// stream_name. The input side packet with the produced name
|
||||
// callback_side_packet_name must be set to an appropriate callback
|
||||
// before the Graph is run. If use_std_function is true, the input side packet
|
||||
// of the CallbackCalculator must be a std::function.
|
||||
void AddCallbackCalculator(const std::string& stream_name,
|
||||
CalculatorGraphConfig* config,
|
||||
std::string* callback_side_packet_name,
|
||||
bool use_std_function = false);
|
||||
|
||||
// Adds a CallbackCalculator that collects multiple streams. The callback will
|
||||
// receive a vector with one packet per stream, in the order specified by the
|
||||
// streams argument. All streams will be synchronized according to their
|
||||
// timestamp, using the standard synchronization policy. If some streams are
|
||||
// missing a packet for a given input timestamp, the vector will contain empty
|
||||
// packets at their positions.
|
||||
//
|
||||
// Once a graph is constructed from the modified config, the packet in
|
||||
// side_packet.second must be passed to it, with the name in side_packet.first.
|
||||
// TODO: remove the need to pass the side packet manually.
|
||||
void AddMultiStreamCallback(
|
||||
const std::vector<std::string>& streams,
|
||||
std::function<void(const std::vector<Packet>&)> callback,
|
||||
CalculatorGraphConfig* config, std::pair<std::string, Packet>* side_packet);
|
||||
|
||||
// Add a CallbackWithHeaderCalculator to intercept packets sent on
|
||||
// stream stream_name, and the header packet on stream stream_header.
|
||||
// The input side packet with the produced name callback_side_packet_name
|
||||
// must be set to an appropriate callback before the Graph is run.
|
||||
// If use_std_function is true, the input side packet of the
|
||||
// AddCallbackWithHeaderCalculator must be a std::function.
|
||||
ABSL_DEPRECATED("Header packets are being deprecated.")
|
||||
void AddCallbackWithHeaderCalculator(const std::string& stream_name,
|
||||
const std::string& stream_header,
|
||||
CalculatorGraphConfig* config,
|
||||
std::string* callback_side_packet_name,
|
||||
bool use_std_function = false);
|
||||
|
||||
// TODO Move CallbackCalculator and CallbackWithHeaderCalculator to
|
||||
// a separate library, and the library will be alwayslink. Then, the "sink"
|
||||
// cc_library can depend on that library, and it does not need to be alwayslink.
|
||||
//
|
||||
// CallbackCalculator calls a user settable callback on every incoming
|
||||
// packet. It must have a single input stream and no output streams.
|
||||
// A single input side packet must be given which contains a std::function of
|
||||
// void(const Packet&). The input side packet must have the tag "CALLBACK" in
|
||||
// the graph config.
|
||||
//
|
||||
// Example Usage:
|
||||
//
|
||||
// // Callback function.
|
||||
// void MyClass::MyFunction(const Packet& packet) {
|
||||
// count_ += packet.Get<int>();
|
||||
// }
|
||||
//
|
||||
// void MyClass::Run() {
|
||||
// CalculatorGraphConfig config;
|
||||
// LoadPartialConfigSomehow(&config);
|
||||
// std::string input_side_packet_name;
|
||||
// tool::AddCallbackCalculator("the_output_stream", &config,
|
||||
// &input_side_packet_name, true);
|
||||
// CalculatorGraph graph(config);
|
||||
// CHECK_OK(graph.Run(
|
||||
// {{input_side_packet_name,
|
||||
// MakePacket<std::function<void(const Packet&)>>(
|
||||
// std::bind(&MyClass::MyFunction, this, std::placeholders::_1))}}
|
||||
// ));
|
||||
// }
|
||||
class CallbackCalculator : public CalculatorBase {
|
||||
public:
|
||||
CallbackCalculator() {}
|
||||
CallbackCalculator(const CallbackCalculator&) = delete;
|
||||
CallbackCalculator& operator=(const CallbackCalculator&) = delete;
|
||||
|
||||
~CallbackCalculator() override {}
|
||||
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc);
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override;
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
std::function<void(const Packet&)> callback_;
|
||||
std::function<void(const std::vector<Packet>&)> vector_callback_;
|
||||
};
|
||||
|
||||
class CallbackWithHeaderCalculator : public CalculatorBase {
|
||||
public:
|
||||
CallbackWithHeaderCalculator() : callback_(nullptr) {}
|
||||
CallbackWithHeaderCalculator(const CallbackWithHeaderCalculator&) = delete;
|
||||
CallbackWithHeaderCalculator& operator=(const CallbackWithHeaderCalculator&) =
|
||||
delete;
|
||||
|
||||
~CallbackWithHeaderCalculator() override {}
|
||||
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc);
|
||||
|
||||
::mediapipe::Status Open(CalculatorContext* cc) override;
|
||||
::mediapipe::Status Process(CalculatorContext* cc) override;
|
||||
|
||||
private:
|
||||
std::function<void(const Packet&, const Packet&)> callback_;
|
||||
// The header packet read from the stream.
|
||||
// Header packet is only going to be read once, either during the Open() for
|
||||
// the current implementation, or in the Process() call when the header stream
|
||||
// has the packet.
|
||||
Packet header_packet_;
|
||||
};
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_SINK_H_
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/sink.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/calculator_runner.h"
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/parse_text_proto.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h"
|
||||
#include "mediapipe/framework/tool/validate_type.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace {
|
||||
class CountAndOutputSummarySidePacketInCloseCalculator : public CalculatorBase {
|
||||
public:
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
|
||||
cc->Inputs().Index(0).SetAny();
|
||||
cc->OutputSidePackets().Index(0).Set<int>();
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Process(CalculatorContext* cc) final {
|
||||
++count_;
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Close(CalculatorContext* cc) final {
|
||||
cc->OutputSidePackets().Index(0).Set(
|
||||
MakePacket<int>(count_).At(Timestamp::Unset()));
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
int count_ = 0;
|
||||
};
|
||||
REGISTER_CALCULATOR(CountAndOutputSummarySidePacketInCloseCalculator);
|
||||
|
||||
TEST(CallbackFromGeneratorTest, TestAddVectorSink) {
|
||||
CalculatorGraphConfig graph_config;
|
||||
std::vector<Packet> dumped_data;
|
||||
tool::AddVectorSink("input_packets", &graph_config, &dumped_data);
|
||||
graph_config.add_input_stream("input_packets");
|
||||
CalculatorGraph graph(graph_config);
|
||||
MEDIAPIPE_ASSERT_OK(graph.StartRun({}));
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
MEDIAPIPE_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"input_packets", MakePacket<int>(i).At(Timestamp(i))));
|
||||
MEDIAPIPE_ASSERT_OK(graph.WaitUntilIdle());
|
||||
}
|
||||
MEDIAPIPE_ASSERT_OK(graph.CloseInputStream("input_packets"));
|
||||
MEDIAPIPE_ASSERT_OK(graph.WaitUntilDone());
|
||||
ASSERT_EQ(10, dumped_data.size());
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
EXPECT_EQ(Timestamp(i), dumped_data[i].Timestamp());
|
||||
EXPECT_EQ(i, dumped_data[i].Get<int>());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CalculatorGraph, OutputSummarySidePacketInClose) {
|
||||
CalculatorGraphConfig config =
|
||||
::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
|
||||
input_stream: "input_packets"
|
||||
node {
|
||||
calculator: "CountAndOutputSummarySidePacketInCloseCalculator"
|
||||
input_stream: "input_packets"
|
||||
output_side_packet: "num_of_packets"
|
||||
}
|
||||
)");
|
||||
|
||||
Packet summary_packet;
|
||||
tool::AddSidePacketSink("num_of_packets", &config, &summary_packet);
|
||||
CalculatorGraph graph;
|
||||
MEDIAPIPE_ASSERT_OK(graph.Initialize(config));
|
||||
|
||||
// Run the graph twice.
|
||||
int max_count = 100;
|
||||
for (int run = 0; run < 1; ++run) {
|
||||
MEDIAPIPE_ASSERT_OK(graph.StartRun({}));
|
||||
for (int i = 0; i < max_count; ++i) {
|
||||
MEDIAPIPE_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"input_packets", MakePacket<int>(i).At(Timestamp(i))));
|
||||
}
|
||||
MEDIAPIPE_ASSERT_OK(graph.CloseInputStream("input_packets"));
|
||||
MEDIAPIPE_ASSERT_OK(graph.WaitUntilDone());
|
||||
EXPECT_EQ(max_count, summary_packet.Get<int>());
|
||||
EXPECT_EQ(Timestamp::PostStream(), summary_packet.Timestamp());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CallbackTest, TestAddMultiStreamCallback) {
|
||||
CalculatorGraphConfig graph_config;
|
||||
graph_config.add_input_stream("foo");
|
||||
graph_config.add_input_stream("bar");
|
||||
|
||||
std::vector<int> sums;
|
||||
|
||||
std::pair<std::string, Packet> cb_packet;
|
||||
tool::AddMultiStreamCallback(
|
||||
{"foo", "bar"},
|
||||
[&sums](const std::vector<Packet>& packets) {
|
||||
Packet foo_p = packets[0];
|
||||
Packet bar_p = packets[1];
|
||||
int foo = foo_p.IsEmpty() ? 0 : foo_p.Get<int>();
|
||||
int bar = bar_p.IsEmpty() ? 0 : bar_p.Get<int>();
|
||||
sums.push_back(foo + bar);
|
||||
},
|
||||
&graph_config, &cb_packet);
|
||||
|
||||
CalculatorGraph graph(graph_config);
|
||||
MEDIAPIPE_ASSERT_OK(graph.StartRun({cb_packet}));
|
||||
|
||||
MEDIAPIPE_ASSERT_OK(graph.AddPacketToInputStream(
|
||||
"foo", MakePacket<int>(10).At(Timestamp(1))));
|
||||
MEDIAPIPE_ASSERT_OK(
|
||||
graph.AddPacketToInputStream("bar", MakePacket<int>(5).At(Timestamp(1))));
|
||||
|
||||
MEDIAPIPE_ASSERT_OK(
|
||||
graph.AddPacketToInputStream("foo", MakePacket<int>(7).At(Timestamp(2))));
|
||||
// no bar input at 2
|
||||
|
||||
MEDIAPIPE_ASSERT_OK(
|
||||
graph.AddPacketToInputStream("foo", MakePacket<int>(4).At(Timestamp(3))));
|
||||
MEDIAPIPE_ASSERT_OK(
|
||||
graph.AddPacketToInputStream("bar", MakePacket<int>(5).At(Timestamp(3))));
|
||||
|
||||
MEDIAPIPE_ASSERT_OK(graph.CloseAllInputStreams());
|
||||
MEDIAPIPE_ASSERT_OK(graph.WaitUntilDone());
|
||||
|
||||
EXPECT_THAT(sums, testing::ElementsAre(15, 7, 9));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/macros.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/port/canonical_errors.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/tool/source.pb.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
|
||||
// A calculator which takes N input side packets and passes them as
|
||||
// N outputs. Each input side packet contains a vector of Packets, or a single
|
||||
// Packet, as given in the options. The elements of the vector contained in
|
||||
// the i-th input side packet are output as individual packets to the i-th
|
||||
// output stream. Optionally, the packets can be timestamped, with either their
|
||||
// index within the vector, or with Timestamp::PostStream(). No type
|
||||
// checking is performed. It is only checked that the calculator receives 0
|
||||
// inputs and the number of outputs equals the number of input side packets.
|
||||
class SidePacketsToStreamsCalculator : public CalculatorBase {
|
||||
public:
|
||||
SidePacketsToStreamsCalculator() {}
|
||||
SidePacketsToStreamsCalculator(const SidePacketsToStreamsCalculator&) =
|
||||
delete;
|
||||
SidePacketsToStreamsCalculator& operator=(
|
||||
const SidePacketsToStreamsCalculator&) = delete;
|
||||
~SidePacketsToStreamsCalculator() override {}
|
||||
|
||||
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
|
||||
auto& options = cc->Options<SidePacketsToStreamsCalculatorOptions>();
|
||||
if (options.has_num_inputs() &&
|
||||
(options.num_inputs() != cc->InputSidePackets().NumEntries() ||
|
||||
options.num_inputs() != cc->Outputs().NumEntries())) {
|
||||
return ::mediapipe::InvalidArgumentError(
|
||||
"If num_inputs is specified it must be equal to the number of "
|
||||
"input side packets and output streams.");
|
||||
}
|
||||
if (!options.vectors_of_packets() &&
|
||||
options.set_timestamp() ==
|
||||
SidePacketsToStreamsCalculatorOptions::NONE) {
|
||||
return ::mediapipe::InvalidArgumentError(
|
||||
"If set_timestamp is NONE, vectors_of_packets must not be false.");
|
||||
}
|
||||
for (int i = 0; i < cc->InputSidePackets().NumEntries(); ++i) {
|
||||
if (options.vectors_of_packets()) {
|
||||
cc->InputSidePackets().Index(i).Set<std::vector<Packet>>();
|
||||
} else {
|
||||
cc->InputSidePackets().Index(i).SetAny();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < cc->InputSidePackets().NumEntries(); ++i) {
|
||||
if (options.vectors_of_packets()) {
|
||||
cc->Outputs().Index(i).SetAny();
|
||||
} else {
|
||||
cc->Outputs().Index(i).SetSameAs(&cc->InputSidePackets().Index(i));
|
||||
}
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status Process(CalculatorContext* cc) final {
|
||||
const auto& options = cc->Options<SidePacketsToStreamsCalculatorOptions>();
|
||||
// The i-th input side packet contains a vector of packets corresponding
|
||||
// to the values of this input for all batch elements.
|
||||
|
||||
int batch_size = -1;
|
||||
if (options.vectors_of_packets()) {
|
||||
// Verify the batch size is consistent.
|
||||
for (const Packet& input_side_packet : cc->InputSidePackets()) {
|
||||
const auto& packets = input_side_packet.Get<std::vector<Packet>>();
|
||||
if (batch_size >= 0) {
|
||||
if (packets.size() != batch_size) {
|
||||
return ::mediapipe::InvalidArgumentError(
|
||||
"The specified input side packets contain vectors of different "
|
||||
"sizes.");
|
||||
}
|
||||
} else {
|
||||
batch_size = packets.size();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
batch_size = 1;
|
||||
}
|
||||
|
||||
for (int b = 0; b < batch_size; ++b) {
|
||||
for (int i = 0; i < cc->InputSidePackets().NumEntries(); ++i) {
|
||||
Packet packet;
|
||||
if (options.vectors_of_packets()) {
|
||||
const auto& packets =
|
||||
cc->InputSidePackets().Index(i).Get<std::vector<Packet>>();
|
||||
packet = packets[b];
|
||||
} else {
|
||||
packet = cc->InputSidePackets().Index(i);
|
||||
}
|
||||
switch (options.set_timestamp()) {
|
||||
case SidePacketsToStreamsCalculatorOptions::VECTOR_INDEX:
|
||||
cc->Outputs().Index(i).AddPacket(packet.At(Timestamp(b)));
|
||||
break;
|
||||
case SidePacketsToStreamsCalculatorOptions::WHOLE_STREAM:
|
||||
cc->Outputs().Index(i).AddPacket(
|
||||
packet.At(Timestamp::PostStream()));
|
||||
break;
|
||||
case SidePacketsToStreamsCalculatorOptions::PRE_STREAM:
|
||||
cc->Outputs().Index(i).AddPacket(packet.At(Timestamp::PreStream()));
|
||||
break;
|
||||
default:
|
||||
// SidePacketsToStreamsCalculatorOptions::NONE
|
||||
cc->Outputs().Index(i).AddPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
return tool::StatusStop();
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_CALCULATOR(SidePacketsToStreamsCalculator);
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Forked from mediapipe/framework/tool/source.proto.
|
||||
// The forked proto must remain identical to the original proto and should be
|
||||
// ONLY used by mediapipe open source project.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package mediapipe;
|
||||
|
||||
import "mediapipe/framework/calculator.proto";
|
||||
|
||||
message SidePacketsToStreamsCalculatorOptions {
|
||||
extend CalculatorOptions {
|
||||
optional SidePacketsToStreamsCalculatorOptions ext = 60982839;
|
||||
}
|
||||
|
||||
// Number of side packets which are fed to graph internal streams.
|
||||
optional int32 num_inputs = 1 [default = 1];
|
||||
// We need to accommodate various timestamp modes depending on what
|
||||
// we're connecting to.
|
||||
enum SetTimestampMode {
|
||||
// For vectors of packets, the timestamp is the index of the packet
|
||||
// within the vector. For single packets, the timestamp is zero.
|
||||
VECTOR_INDEX = 0;
|
||||
// Timestamps are always set to PreStream.
|
||||
PRE_STREAM = 1;
|
||||
// Timestamps are always set to PostStream.
|
||||
// TODO Rename to POST_STREAM.
|
||||
WHOLE_STREAM = 2;
|
||||
// Do not set timestamp. Can only be used if vectors_of_packets is
|
||||
// true. Will cause Timestamp::Unset() run-time errors if the inner
|
||||
// packets in the vectors do not already have Timestamps.
|
||||
NONE = 3;
|
||||
}
|
||||
// If true, then a timestamp is set for each packet.
|
||||
optional SetTimestampMode set_timestamp = 2 [default = VECTOR_INDEX];
|
||||
// If true, then side packets are vectors of packets; otherwise,
|
||||
// they are single packets.
|
||||
optional bool vectors_of_packets = 3 [default = true];
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/status_util.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
::mediapipe::Status StatusInvalid(const std::string& message) {
|
||||
return ::mediapipe::Status(::mediapipe::StatusCode::kInvalidArgument,
|
||||
message);
|
||||
}
|
||||
|
||||
::mediapipe::Status StatusFail(const std::string& message) {
|
||||
return ::mediapipe::Status(::mediapipe::StatusCode::kUnknown, message);
|
||||
}
|
||||
|
||||
::mediapipe::Status StatusStop() {
|
||||
return ::mediapipe::Status(::mediapipe::StatusCode::kOutOfRange,
|
||||
"::mediapipe::tool::StatusStop()");
|
||||
}
|
||||
|
||||
::mediapipe::Status AddStatusPrefix(const std::string& prefix,
|
||||
const ::mediapipe::Status& status) {
|
||||
return ::mediapipe::Status(status.code(),
|
||||
absl::StrCat(prefix, status.message()));
|
||||
}
|
||||
|
||||
::mediapipe::Status CombinedStatus(
|
||||
const std::string& general_comment,
|
||||
const std::vector<::mediapipe::Status>& statuses) {
|
||||
// The final error code is ::mediapipe::StatusCode::kUnknown if not all
|
||||
// the error codes are the same. Otherwise it is the same error code
|
||||
// as all of the (non-OK) statuses. If statuses is empty or they are
|
||||
// all OK, then ::mediapipe::OkStatus() is returned.
|
||||
::mediapipe::StatusCode error_code = ::mediapipe::StatusCode::kOk;
|
||||
std::vector<std::string> errors;
|
||||
for (const ::mediapipe::Status& status : statuses) {
|
||||
if (!status.ok()) {
|
||||
errors.emplace_back(status.message());
|
||||
if (error_code == ::mediapipe::StatusCode::kOk) {
|
||||
error_code = status.code();
|
||||
} else if (error_code != status.code()) {
|
||||
error_code = ::mediapipe::StatusCode::kUnknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ::mediapipe::Status(
|
||||
error_code,
|
||||
absl::StrCat(general_comment, "\n", absl::StrJoin(errors, "\n")));
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_STATUS_UTIL_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_STATUS_UTIL_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/macros.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
// Return a status which signals that an action should stop. For example,
|
||||
// a source Calculator is done producing output (and Process() should not
|
||||
// be called on it again). When returned from a non-source Calculator
|
||||
// it signals that the graph should be cancelled (which is handled by
|
||||
// closing all source Calculators and waiting for the graph to finish).
|
||||
::mediapipe::Status StatusStop();
|
||||
|
||||
// Return a status which signals an invalid initial condition (for
|
||||
// example an InputSidePacket does not include all necessary fields).
|
||||
ABSL_DEPRECATED("Use ::mediapipe::InvalidArgumentError(error_message) instead.")
|
||||
::mediapipe::Status StatusInvalid(const std::string& error_message);
|
||||
|
||||
// Return a status which signals that something unexpectedly failed.
|
||||
ABSL_DEPRECATED("Use ::mediapipe::UnknownError(error_message) instead.")
|
||||
::mediapipe::Status StatusFail(const std::string& error_message);
|
||||
|
||||
// Prefixes the given std::string to the error message in status.
|
||||
// This function should be considered internal to the framework.
|
||||
// TODO Replace usage of AddStatusPrefix with util::Annotate().
|
||||
::mediapipe::Status AddStatusPrefix(const std::string& prefix,
|
||||
const ::mediapipe::Status& status);
|
||||
|
||||
// Combine a vector of ::mediapipe::Status into a single status. If statuses
|
||||
// is empty or all statuses are OK then ::mediapipe::OkStatus() will be
|
||||
// returned.
|
||||
// This function should be considered internal to the framework.
|
||||
// TODO Move this function to somewhere with less visibility.
|
||||
::mediapipe::Status CombinedStatus(
|
||||
const std::string& general_comment,
|
||||
const std::vector<::mediapipe::Status>& statuses);
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_STATUS_UTIL_H_
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2018 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/status_util.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "mediapipe/framework/port/canonical_errors.h"
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace {
|
||||
|
||||
TEST(StatusTest, StatusStopIsNotOk) { EXPECT_FALSE(tool::StatusStop().ok()); }
|
||||
|
||||
TEST(StatusTest, Prefix) {
|
||||
const std::string base_error_message("error_with_this_string");
|
||||
const std::string prefix_error_message("error_with_prefix: ");
|
||||
::mediapipe::Status base_status = ::mediapipe::Status(
|
||||
::mediapipe::StatusCode::kInvalidArgument, base_error_message);
|
||||
::mediapipe::Status status =
|
||||
tool::AddStatusPrefix(prefix_error_message, base_status);
|
||||
EXPECT_THAT(status.ToString(), testing::HasSubstr(base_error_message));
|
||||
EXPECT_THAT(status.ToString(), testing::HasSubstr(prefix_error_message));
|
||||
EXPECT_EQ(::mediapipe::StatusCode::kInvalidArgument, status.code());
|
||||
}
|
||||
|
||||
TEST(StatusTest, CombinedStatus) {
|
||||
std::vector< ::mediapipe::Status> errors;
|
||||
const std::string prefix_error_message("error_with_prefix: ");
|
||||
::mediapipe::Status status;
|
||||
|
||||
errors.clear();
|
||||
errors.emplace_back(::mediapipe::StatusCode::kInvalidArgument,
|
||||
"error_with_this_string");
|
||||
errors.emplace_back(::mediapipe::StatusCode::kInvalidArgument,
|
||||
"error_with_that_string");
|
||||
status = tool::CombinedStatus(prefix_error_message, errors);
|
||||
EXPECT_THAT(status.ToString(), testing::HasSubstr(errors[0].error_message()));
|
||||
EXPECT_THAT(status.ToString(), testing::HasSubstr(errors[1].error_message()));
|
||||
EXPECT_THAT(status.ToString(), testing::HasSubstr(prefix_error_message));
|
||||
EXPECT_EQ(::mediapipe::StatusCode::kInvalidArgument, status.code());
|
||||
|
||||
errors.clear();
|
||||
errors.emplace_back(::mediapipe::StatusCode::kNotFound,
|
||||
"error_with_this_string");
|
||||
errors.emplace_back(::mediapipe::StatusCode::kInvalidArgument,
|
||||
"error_with_that_string");
|
||||
status = tool::CombinedStatus(prefix_error_message, errors);
|
||||
EXPECT_THAT(status.ToString(), testing::HasSubstr(errors[0].error_message()));
|
||||
EXPECT_THAT(status.ToString(), testing::HasSubstr(errors[1].error_message()));
|
||||
EXPECT_THAT(status.ToString(), testing::HasSubstr(prefix_error_message));
|
||||
EXPECT_EQ(::mediapipe::StatusCode::kUnknown, status.code());
|
||||
errors.clear();
|
||||
errors.emplace_back(::mediapipe::StatusCode::kOk, "error_with_this_string");
|
||||
errors.emplace_back(::mediapipe::StatusCode::kInvalidArgument,
|
||||
"error_with_that_string");
|
||||
status = tool::CombinedStatus(prefix_error_message, errors);
|
||||
EXPECT_THAT(status.ToString(), testing::HasSubstr(errors[1].error_message()));
|
||||
EXPECT_THAT(status.ToString(), testing::HasSubstr(prefix_error_message));
|
||||
EXPECT_EQ(::mediapipe::StatusCode::kInvalidArgument, status.code());
|
||||
|
||||
errors.clear();
|
||||
errors.emplace_back(::mediapipe::StatusCode::kOk, "error_with_this_string");
|
||||
errors.emplace_back(::mediapipe::StatusCode::kOk, "error_with_that_string");
|
||||
MEDIAPIPE_EXPECT_OK(tool::CombinedStatus(prefix_error_message, errors));
|
||||
|
||||
errors.clear();
|
||||
MEDIAPIPE_EXPECT_OK(tool::CombinedStatus(prefix_error_message, errors));
|
||||
}
|
||||
|
||||
// Verify tool::StatusInvalid() and tool::StatusFail() and the alternatives
|
||||
// recommended by their ABSL_DEPRECATED messages return the same
|
||||
// ::mediapipe::Status objects.
|
||||
TEST(StatusTest, Deprecated) {
|
||||
const std::string error_message = "an error message";
|
||||
EXPECT_EQ(tool::StatusInvalid(error_message), // NOLINT
|
||||
::mediapipe::InvalidArgumentError(error_message));
|
||||
EXPECT_EQ(tool::StatusFail(error_message), // NOLINT
|
||||
::mediapipe::UnknownError(error_message));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,264 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/subgraph_expansion.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "mediapipe/framework/packet_generator.pb.h"
|
||||
#include "mediapipe/framework/port.h"
|
||||
#include "mediapipe/framework/port/core_proto_inc.h"
|
||||
#include "mediapipe/framework/port/logging.h"
|
||||
#include "mediapipe/framework/port/map_util.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/port/status_builder.h"
|
||||
#include "mediapipe/framework/port/status_macros.h"
|
||||
#include "mediapipe/framework/status_handler.pb.h"
|
||||
#include "mediapipe/framework/subgraph.h"
|
||||
#include "mediapipe/framework/tool/tag_map.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
|
||||
::mediapipe::Status TransformStreamNames(
|
||||
proto_ns::RepeatedPtrField<ProtoString>* streams,
|
||||
const std::function<std::string(absl::string_view)>& transform) {
|
||||
for (auto& stream : *streams) {
|
||||
absl::string_view port_and_name(stream);
|
||||
auto colon_pos = port_and_name.find_last_of(":");
|
||||
auto name_pos = colon_pos == absl::string_view::npos ? 0 : colon_pos + 1;
|
||||
stream =
|
||||
absl::StrCat(port_and_name.substr(0, name_pos),
|
||||
transform(absl::ClippedSubstr(port_and_name, name_pos)));
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status TransformNames(
|
||||
CalculatorGraphConfig* config,
|
||||
const std::function<std::string(absl::string_view)>& transform) {
|
||||
RET_CHECK_EQ(config->packet_factory().size(), 0);
|
||||
for (auto* streams :
|
||||
{config->mutable_input_stream(), config->mutable_output_stream(),
|
||||
config->mutable_input_side_packet(),
|
||||
config->mutable_output_side_packet()}) {
|
||||
RETURN_IF_ERROR(TransformStreamNames(streams, transform));
|
||||
}
|
||||
for (auto& node : *config->mutable_node()) {
|
||||
for (auto* streams :
|
||||
{node.mutable_input_stream(), node.mutable_output_stream(),
|
||||
node.mutable_input_side_packet(),
|
||||
node.mutable_output_side_packet()}) {
|
||||
RETURN_IF_ERROR(TransformStreamNames(streams, transform));
|
||||
}
|
||||
if (!node.name().empty()) {
|
||||
node.set_name(transform(node.name()));
|
||||
}
|
||||
}
|
||||
for (auto& generator : *config->mutable_packet_generator()) {
|
||||
for (auto* streams : {generator.mutable_input_side_packet(),
|
||||
generator.mutable_output_side_packet()}) {
|
||||
RETURN_IF_ERROR(TransformStreamNames(streams, transform));
|
||||
}
|
||||
}
|
||||
for (auto& status_handler : *config->mutable_status_handler()) {
|
||||
RETURN_IF_ERROR(TransformStreamNames(
|
||||
status_handler.mutable_input_side_packet(), transform));
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// Adds a prefix to the name of each stream, side packet and node in the
|
||||
// config. Each call to this method should use a different subgraph_index
|
||||
// to produce a different numerical prefix. For example:
|
||||
// 1, { foo, bar } --PrefixNames-> { __sg_1_foo, __sg_1_bar }
|
||||
// 2, { foo, bar } --PrefixNames-> { __sg_2_foo, __sg_2_bar }
|
||||
// This means that two copies of the same subgraph will not interfere with
|
||||
// each other.
|
||||
static ::mediapipe::Status PrefixNames(int subgraph_index,
|
||||
CalculatorGraphConfig* config) {
|
||||
// TODO: prefix with subgraph name instead (see cl/157677233
|
||||
// discussion).
|
||||
// TODO: since we expand nested subgraphs outside-in, we should
|
||||
// append the prefix to the existing prefix, if any. This is unimportant
|
||||
// with the meaningless prefix we use now, but it should be considered
|
||||
// when prefixing with names.
|
||||
std::string prefix = absl::StrCat("__sg", subgraph_index, "_");
|
||||
auto add_prefix = [&prefix](absl::string_view s) {
|
||||
return absl::StrCat(prefix, s);
|
||||
};
|
||||
return TransformNames(config, add_prefix);
|
||||
}
|
||||
|
||||
::mediapipe::Status FindCorrespondingStreams(
|
||||
std::map<std::string, std::string>* stream_map,
|
||||
const proto_ns::RepeatedPtrField<ProtoString>& src_streams,
|
||||
const proto_ns::RepeatedPtrField<ProtoString>& dst_streams) {
|
||||
ASSIGN_OR_RETURN(auto src_map, tool::TagMap::Create(src_streams));
|
||||
ASSIGN_OR_RETURN(auto dst_map, tool::TagMap::Create(dst_streams));
|
||||
for (auto it : dst_map->Mapping()) {
|
||||
const std::string& tag = it.first;
|
||||
const TagMap::TagData* src_tag_data =
|
||||
::mediapipe::FindOrNull(src_map->Mapping(), tag);
|
||||
if (!src_tag_data) {
|
||||
return ::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "Tag \"" << tag << "\" does not exist in the subgraph config.";
|
||||
}
|
||||
const TagMap::TagData& dst_tag_data = it.second;
|
||||
CollectionItemId src_id = src_tag_data->id;
|
||||
CollectionItemId dst_id = dst_tag_data.id;
|
||||
if (dst_tag_data.count > src_tag_data->count) {
|
||||
return ::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "Tag \"" << tag << "\" has " << dst_tag_data.count
|
||||
<< " indexes in the subgraph node but has only "
|
||||
<< src_tag_data->count << " indexes in the subgraph config.";
|
||||
}
|
||||
CollectionItemId src_end_id =
|
||||
src_id + std::min(src_tag_data->count, dst_tag_data.count);
|
||||
for (; src_id < src_end_id; ++src_id, ++dst_id) {
|
||||
const std::string& src_name = src_map->Names()[src_id.value()];
|
||||
const std::string& dst_name = dst_map->Names()[dst_id.value()];
|
||||
(*stream_map)[src_name] = dst_name;
|
||||
}
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// The following fields can be used in a Node message for a subgraph:
|
||||
// name, calculator, input_stream, output_stream, input_side_packet,
|
||||
// output_side_packet, options.
|
||||
// All other fields are only applicable to calculators.
|
||||
::mediapipe::Status ValidateSubgraphFields(
|
||||
const CalculatorGraphConfig::Node& subgraph_node) {
|
||||
if (subgraph_node.source_layer() || subgraph_node.buffer_size_hint() ||
|
||||
subgraph_node.has_input_stream_handler() ||
|
||||
subgraph_node.has_output_stream_handler() ||
|
||||
subgraph_node.input_stream_info_size() != 0 ||
|
||||
!subgraph_node.executor().empty()) {
|
||||
return ::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "Subgraph \"" << subgraph_node.name()
|
||||
<< "\" has a field that is only applicable to calculators.";
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status ConnectSubgraphStreams(
|
||||
const CalculatorGraphConfig::Node& subgraph_node,
|
||||
CalculatorGraphConfig* subgraph_config) {
|
||||
std::map<std::string, std::string> stream_map;
|
||||
RETURN_IF_ERROR(FindCorrespondingStreams(&stream_map,
|
||||
subgraph_config->input_stream(),
|
||||
subgraph_node.input_stream()))
|
||||
.SetPrepend()
|
||||
<< "while processing the input streams of subgraph node "
|
||||
<< subgraph_node.calculator() << ": ";
|
||||
RETURN_IF_ERROR(FindCorrespondingStreams(&stream_map,
|
||||
subgraph_config->output_stream(),
|
||||
subgraph_node.output_stream()))
|
||||
.SetPrepend()
|
||||
<< "while processing the output streams of subgraph node "
|
||||
<< subgraph_node.calculator() << ": ";
|
||||
std::map<std::string, std::string> side_packet_map;
|
||||
RETURN_IF_ERROR(FindCorrespondingStreams(&side_packet_map,
|
||||
subgraph_config->input_side_packet(),
|
||||
subgraph_node.input_side_packet()))
|
||||
.SetPrepend()
|
||||
<< "while processing the input side packets of subgraph node "
|
||||
<< subgraph_node.calculator() << ": ";
|
||||
RETURN_IF_ERROR(FindCorrespondingStreams(
|
||||
&side_packet_map, subgraph_config->output_side_packet(),
|
||||
subgraph_node.output_side_packet()))
|
||||
.SetPrepend()
|
||||
<< "while processing the output side packets of subgraph node "
|
||||
<< subgraph_node.calculator() << ": ";
|
||||
std::map<std::string, std::string>* name_map;
|
||||
auto replace_names = [&name_map](absl::string_view s) {
|
||||
std::string original(s);
|
||||
std::string* replacement = ::mediapipe::FindOrNull(*name_map, original);
|
||||
return replacement ? *replacement : original;
|
||||
};
|
||||
for (auto& node : *subgraph_config->mutable_node()) {
|
||||
name_map = &stream_map;
|
||||
RETURN_IF_ERROR(
|
||||
TransformStreamNames(node.mutable_input_stream(), replace_names));
|
||||
RETURN_IF_ERROR(
|
||||
TransformStreamNames(node.mutable_output_stream(), replace_names));
|
||||
name_map = &side_packet_map;
|
||||
RETURN_IF_ERROR(
|
||||
TransformStreamNames(node.mutable_input_side_packet(), replace_names));
|
||||
RETURN_IF_ERROR(
|
||||
TransformStreamNames(node.mutable_output_side_packet(), replace_names));
|
||||
}
|
||||
name_map = &side_packet_map;
|
||||
for (auto& generator : *subgraph_config->mutable_packet_generator()) {
|
||||
RETURN_IF_ERROR(TransformStreamNames(generator.mutable_input_side_packet(),
|
||||
replace_names));
|
||||
RETURN_IF_ERROR(TransformStreamNames(generator.mutable_output_side_packet(),
|
||||
replace_names));
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status ExpandSubgraphs(CalculatorGraphConfig* config,
|
||||
const GraphRegistry* graph_registry) {
|
||||
graph_registry =
|
||||
graph_registry ? graph_registry : &GraphRegistry::global_graph_registry;
|
||||
RET_CHECK(config);
|
||||
auto* nodes = config->mutable_node();
|
||||
int subgraph_counter = 0;
|
||||
while (1) {
|
||||
auto subgraph_nodes_start = std::stable_partition(
|
||||
nodes->begin(), nodes->end(),
|
||||
[config, graph_registry](CalculatorGraphConfig::Node& node) {
|
||||
return !graph_registry->IsRegistered(config->package(),
|
||||
node.calculator());
|
||||
});
|
||||
if (subgraph_nodes_start == nodes->end()) break;
|
||||
std::vector<CalculatorGraphConfig> subgraphs;
|
||||
for (auto it = subgraph_nodes_start; it != nodes->end(); ++it) {
|
||||
const auto& node = *it;
|
||||
RETURN_IF_ERROR(ValidateSubgraphFields(node));
|
||||
ASSIGN_OR_RETURN(auto subgraph, graph_registry->CreateByName(
|
||||
config->package(), node.calculator(),
|
||||
&node.options()));
|
||||
RETURN_IF_ERROR(PrefixNames(subgraph_counter++, &subgraph));
|
||||
RETURN_IF_ERROR(ConnectSubgraphStreams(node, &subgraph));
|
||||
subgraphs.push_back(subgraph);
|
||||
}
|
||||
nodes->erase(subgraph_nodes_start, nodes->end());
|
||||
for (const auto& subgraph : subgraphs) {
|
||||
std::copy(subgraph.node().begin(), subgraph.node().end(),
|
||||
proto_ns::RepeatedPtrFieldBackInserter(nodes));
|
||||
std::copy(subgraph.packet_generator().begin(),
|
||||
subgraph.packet_generator().end(),
|
||||
proto_ns::RepeatedPtrFieldBackInserter(
|
||||
config->mutable_packet_generator()));
|
||||
std::copy(subgraph.status_handler().begin(),
|
||||
subgraph.status_handler().end(),
|
||||
proto_ns::RepeatedPtrFieldBackInserter(
|
||||
config->mutable_status_handler()));
|
||||
}
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_SUBGRAPH_EXPANSION_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_SUBGRAPH_EXPANSION_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
#include "mediapipe/framework/port/proto_ns.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/subgraph.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
|
||||
// Apply the given transformation function to the names of streams and
|
||||
// side packets.
|
||||
::mediapipe::Status TransformStreamNames(
|
||||
proto_ns::RepeatedPtrField<ProtoString>* streams,
|
||||
const std::function<std::string(absl::string_view)>& transform);
|
||||
|
||||
// Apply the given transformation function to the names of streams,
|
||||
// side packets, and nodes.
|
||||
::mediapipe::Status TransformNames(
|
||||
CalculatorGraphConfig* config,
|
||||
const std::function<std::string(absl::string_view)>& transform);
|
||||
|
||||
// Updates the given map with entries mapping the names of streams in the
|
||||
// source set to those of the corresponding streams in the destination set.
|
||||
// Corresponding streams are those with the same tag and index. Streams with
|
||||
// no match are ignored.
|
||||
//
|
||||
// For instance, given:
|
||||
// src: FOO:abc dst: FOO:bob
|
||||
// BAR:def
|
||||
// The entry 'abc' -> 'bob' is added to the map.
|
||||
::mediapipe::Status FindCorrespondingStreams(
|
||||
std::map<std::string, std::string>* stream_map,
|
||||
const proto_ns::RepeatedPtrField<ProtoString>& src_streams,
|
||||
const proto_ns::RepeatedPtrField<ProtoString>& dst_streams);
|
||||
|
||||
// Validates the fields in the given Node message that specifies a subgraph.
|
||||
// Returns an error status if the Node message contains any field that is only
|
||||
// applicable to calculators.
|
||||
::mediapipe::Status ValidateSubgraphFields(
|
||||
const CalculatorGraphConfig::Node& subgraph_node);
|
||||
|
||||
// Renames the streams in a subgraph config to match the connections on the
|
||||
// wrapping node.
|
||||
::mediapipe::Status ConnectSubgraphStreams(
|
||||
const CalculatorGraphConfig::Node& subgraph_node,
|
||||
CalculatorGraphConfig* subgraph_config);
|
||||
|
||||
// Replaces subgraph nodes in the given config with the contents of the
|
||||
// corresponding subgraphs. Nested subgraphs are retrieved from the
|
||||
// graph registry and expanded recursively.
|
||||
::mediapipe::Status ExpandSubgraphs(
|
||||
CalculatorGraphConfig* config,
|
||||
const GraphRegistry* graph_registry = nullptr);
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_SUBGRAPH_EXPANSION_H_
|
||||
@@ -0,0 +1,276 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/tag_map.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "mediapipe/framework/port/canonical_errors.h"
|
||||
#include "mediapipe/framework/port/core_proto_inc.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/port/status_builder.h"
|
||||
#include "mediapipe/framework/tool/validate_name.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
void TagMap::InitializeNames(
|
||||
const std::map<std::string, std::vector<std::string>>& tag_to_names) {
|
||||
names_.reserve(num_entries_);
|
||||
for (const auto& item : tag_to_names) {
|
||||
names_.insert(names_.end(), item.second.begin(), item.second.end());
|
||||
}
|
||||
}
|
||||
|
||||
::mediapipe::Status TagMap::Initialize(
|
||||
const proto_ns::RepeatedPtrField<ProtoString>& tag_index_names) {
|
||||
std::map<std::string, std::vector<std::string>> tag_to_names;
|
||||
for (const auto& tag_index_name : tag_index_names) {
|
||||
std::string tag;
|
||||
int index;
|
||||
std::string name;
|
||||
RETURN_IF_ERROR(ParseTagIndexName(tag_index_name, &tag, &index, &name));
|
||||
|
||||
// Get a reference to the tag data (possibly creating it).
|
||||
TagData& tag_data = mapping_[tag];
|
||||
|
||||
// If index == -1, then we get the index from the number of times
|
||||
// the tag has been used (this is only used for tag "").
|
||||
if (index == -1) {
|
||||
index = tag_data.count;
|
||||
}
|
||||
++tag_data.count;
|
||||
|
||||
// Add to the per tag names, being careful about allowing indexes
|
||||
// to be out of order.
|
||||
std::vector<std::string>& names = tag_to_names[tag];
|
||||
if (names.size() <= index) {
|
||||
names.resize(index + 1);
|
||||
}
|
||||
if (!names[index].empty()) {
|
||||
return ::mediapipe::FailedPreconditionErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "tag \"" << tag << "\" index " << index
|
||||
<< " already had a name \"" << names[index]
|
||||
<< "\" but is being reassigned a name \"" << name << "\"";
|
||||
}
|
||||
names[index] = name;
|
||||
}
|
||||
|
||||
// Set all the initial indexes to an index in the data vector.
|
||||
int current_index = 0;
|
||||
for (auto& item : mapping_) {
|
||||
TagData& tag_data = item.second;
|
||||
// Ensure that a name was assigned for each index of the tag.
|
||||
// If the number of indexes used matches the size of names array
|
||||
// (and an index couldn't have been reused due to the check in the
|
||||
// loop above), this means that all indexes were used exactly once.
|
||||
const std::vector<std::string>& names = tag_to_names[item.first];
|
||||
if (tag_data.count != names.size()) {
|
||||
auto builder = ::mediapipe::FailedPreconditionErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "Not all indexes were assigned names. Tag \""
|
||||
<< item.first << "\" has the following:\n";
|
||||
// Note, names.size() will always be larger than tag_data.count.
|
||||
for (int index = 0; index < names.size(); ++index) {
|
||||
if (!names[index].empty()) {
|
||||
builder << "index " << index << " name \"" << names[index] << "\"\n";
|
||||
} else {
|
||||
builder << "index " << index << " name <missing>\n";
|
||||
}
|
||||
}
|
||||
return std::move(builder);
|
||||
}
|
||||
tag_data.id = CollectionItemId(current_index);
|
||||
current_index += tag_data.count;
|
||||
}
|
||||
num_entries_ = current_index;
|
||||
|
||||
InitializeNames(tag_to_names);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status TagMap::Initialize(const TagAndNameInfo& info) {
|
||||
if (info.tags.empty()) {
|
||||
if (!info.names.empty()) {
|
||||
mapping_.emplace(
|
||||
std::piecewise_construct, std::forward_as_tuple(""),
|
||||
std::forward_as_tuple(CollectionItemId(0), info.names.size()));
|
||||
names_ = info.names;
|
||||
}
|
||||
num_entries_ = info.names.size();
|
||||
} else {
|
||||
std::map<std::string, std::vector<std::string>> tag_to_names;
|
||||
if (info.tags.size() != info.names.size()) {
|
||||
return ::mediapipe::FailedPreconditionError(
|
||||
"Expected info.tags.size() == info.names.size()");
|
||||
}
|
||||
|
||||
// Add the tags (unsorted).
|
||||
for (int i = 0; i < info.tags.size(); ++i) {
|
||||
auto item = mapping_.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(info.tags[i]),
|
||||
std::forward_as_tuple());
|
||||
RET_CHECK(item.second) << "Tag was used twice.";
|
||||
tag_to_names[info.tags[i]].emplace_back(info.names[i]);
|
||||
}
|
||||
// Assign descriptor values (sorted).
|
||||
int current_index = 0;
|
||||
for (auto& item : mapping_) {
|
||||
item.second.id = CollectionItemId(current_index);
|
||||
item.second.count = 1;
|
||||
++current_index;
|
||||
}
|
||||
num_entries_ = current_index;
|
||||
|
||||
// Now create the names_ array in the correctly sorted order.
|
||||
InitializeNames(tag_to_names);
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
proto_ns::RepeatedPtrField<ProtoString> TagMap::CanonicalEntries() const {
|
||||
proto_ns::RepeatedPtrField<ProtoString> fields;
|
||||
for (const auto& item : mapping_) {
|
||||
const std::string& tag = item.first;
|
||||
const TagData& tag_data = item.second;
|
||||
if (tag.empty()) {
|
||||
// "no_tag1", "no_tag2".
|
||||
for (int i = 0; i < tag_data.count; ++i) {
|
||||
*fields.Add() = names_[tag_data.id.value() + i];
|
||||
}
|
||||
} else if (tag_data.count <= 1) {
|
||||
// "ONLY_ONE_INDEX:name"
|
||||
*fields.Add() = absl::StrCat(tag, ":", names_[tag_data.id.value()]);
|
||||
} else {
|
||||
// "TAG:0:name0", "TAG:1:name1"
|
||||
for (int i = 0; i < tag_data.count; ++i) {
|
||||
*fields.Add() =
|
||||
absl::StrCat(tag, ":", i, ":", names_[tag_data.id.value() + i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
// Examples:
|
||||
// BLAH:0:blah1
|
||||
// BLAH:1:blah2
|
||||
//
|
||||
// A:a
|
||||
// B:b
|
||||
//
|
||||
// A:0:a0
|
||||
// A:1:a1
|
||||
// A:2:a2
|
||||
// B:0:b0
|
||||
// B:1:b1
|
||||
// C:c0
|
||||
std::string TagMap::DebugString() const {
|
||||
if (num_entries_ == 0) {
|
||||
return "empty";
|
||||
}
|
||||
return absl::StrJoin(CanonicalEntries(), "\n");
|
||||
}
|
||||
|
||||
// Note, this is also currently used internally to check for equivalence.
|
||||
//
|
||||
// Examples:
|
||||
// {"BLAH", 2}
|
||||
//
|
||||
// {"A", 1}, {"B", 1}
|
||||
//
|
||||
// {"A", 3}, {"B", 2}, {"C", 1}
|
||||
//
|
||||
// {"", 4}, {"A", 3}, {"B", 2}, {"C", 1}
|
||||
std::string TagMap::ShortDebugString() const {
|
||||
if (num_entries_ == 0) {
|
||||
return "empty";
|
||||
}
|
||||
std::string output;
|
||||
for (const auto& item : mapping_) {
|
||||
if (!output.empty()) {
|
||||
absl::StrAppend(&output, ", ");
|
||||
}
|
||||
if (item.second.count == 0) {
|
||||
absl::StrAppend(&output, "\"", item.first, "\"");
|
||||
} else {
|
||||
absl::StrAppend(&output, "{\"", item.first, "\", ", item.second.count,
|
||||
"}");
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
bool TagMap::HasTag(const std::string& tag) const {
|
||||
return mapping_.find(tag) != mapping_.end();
|
||||
}
|
||||
|
||||
int TagMap::NumEntries(const std::string& tag) const {
|
||||
const auto it = mapping_.find(tag);
|
||||
if (it == mapping_.end()) {
|
||||
return 0;
|
||||
}
|
||||
return it->second.count;
|
||||
}
|
||||
|
||||
CollectionItemId TagMap::GetId(const std::string& tag, int index) const {
|
||||
const auto it = mapping_.find(tag);
|
||||
if (it == mapping_.end()) {
|
||||
return CollectionItemId::GetInvalid();
|
||||
}
|
||||
if (index < 0 || index >= it->second.count) {
|
||||
return CollectionItemId::GetInvalid();
|
||||
}
|
||||
return it->second.id + index;
|
||||
}
|
||||
|
||||
std::pair<std::string, int> TagMap::TagAndIndexFromId(
|
||||
CollectionItemId id) const {
|
||||
for (const auto& item : mapping_) {
|
||||
if (id >= item.second.id && id < item.second.id + item.second.count) {
|
||||
return std::make_pair(item.first, (id - item.second.id).value());
|
||||
}
|
||||
}
|
||||
return {"", -1};
|
||||
}
|
||||
|
||||
CollectionItemId TagMap::BeginId(const std::string& tag) const {
|
||||
return GetId(tag, 0);
|
||||
}
|
||||
|
||||
CollectionItemId TagMap::EndId(const std::string& tag) const {
|
||||
const auto it = mapping_.find(tag);
|
||||
if (it == mapping_.end()) {
|
||||
return CollectionItemId::GetInvalid();
|
||||
}
|
||||
return it->second.id + it->second.count;
|
||||
}
|
||||
|
||||
std::set<std::string> TagMap::GetTags() const {
|
||||
std::set<std::string> tag_names;
|
||||
for (const auto& item : mapping_) {
|
||||
tag_names.insert(item.first);
|
||||
}
|
||||
return tag_names;
|
||||
}
|
||||
|
||||
bool TagMap::SameAs(const TagMap& other) const {
|
||||
return &other == this || ShortDebugString() == other.ShortDebugString();
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_TAG_MAP_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_TAG_MAP_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/macros.h"
|
||||
#include "mediapipe/framework/collection_item_id.h"
|
||||
#include "mediapipe/framework/port/canonical_errors.h"
|
||||
#include "mediapipe/framework/port/core_proto_inc.h"
|
||||
#include "mediapipe/framework/port/logging.h"
|
||||
#include "mediapipe/framework/port/proto_ns.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/port/status_macros.h"
|
||||
#include "mediapipe/framework/port/statusor.h"
|
||||
#include "mediapipe/framework/tool/validate_name.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
// Holds the information needed for tag/index retrieval for stream and
|
||||
// side packet lists.
|
||||
class TagMap {
|
||||
public:
|
||||
// Struct to hold the initial id and the number of indexes per tag.
|
||||
struct TagData {
|
||||
TagData() : id(-1), count(0) {}
|
||||
TagData(CollectionItemId first_id, int the_count)
|
||||
: id(first_id), count(the_count) {}
|
||||
|
||||
// The initial id for this tag.
|
||||
CollectionItemId id;
|
||||
// The number of entries with this tag.
|
||||
int count;
|
||||
};
|
||||
|
||||
// Create a TagMap from a repeated std::string proto field of
|
||||
// TAG:<index>:name. This is the most common usage:
|
||||
// ASSIGN_OR_RETURN(std::shared_ptr<TagMap> tag_map,
|
||||
// tool::TagMap::Create(node.input_streams()));
|
||||
static ::mediapipe::StatusOr<std::shared_ptr<TagMap>> Create(
|
||||
const proto_ns::RepeatedPtrField<ProtoString>& tag_index_names) {
|
||||
std::shared_ptr<TagMap> output(new TagMap());
|
||||
RETURN_IF_ERROR(output->Initialize(tag_index_names));
|
||||
return std::move(output);
|
||||
}
|
||||
|
||||
// Create a TagMap from a TagAndNameInfo.
|
||||
// TODO: Migrate callers and delete this method.
|
||||
ABSL_DEPRECATED(
|
||||
"Use mediapipe::tool::TagMap::Create(tag_index_names) instead.")
|
||||
static ::mediapipe::StatusOr<std::shared_ptr<TagMap>> Create(
|
||||
const TagAndNameInfo& info) {
|
||||
std::shared_ptr<TagMap> output(new TagMap());
|
||||
RETURN_IF_ERROR(output->Initialize(info));
|
||||
return std::move(output);
|
||||
}
|
||||
|
||||
// Returns a reference to the mapping from tag to tag data.
|
||||
const std::map<std::string, TagData>& Mapping() const { return mapping_; }
|
||||
|
||||
// Returns the vector of names (indexed by CollectionItemId).
|
||||
const std::vector<std::string>& Names() const { return names_; }
|
||||
|
||||
// Returns true if "this" and "other" use equivalent tags and indexes
|
||||
// (disregards stream/side packet names).
|
||||
bool SameAs(const TagMap& other) const;
|
||||
|
||||
// Returns canonicalized strings describing the TagMap.
|
||||
proto_ns::RepeatedPtrField<ProtoString> CanonicalEntries() const;
|
||||
// Returns a std::string description for debug purposes.
|
||||
std::string DebugString() const;
|
||||
// Returns a shorter description for debug purposes (doesn't include
|
||||
// stream/side packet names).
|
||||
std::string ShortDebugString() const;
|
||||
|
||||
// The following functions are directly utilized by collection.h see
|
||||
// that file for comments.
|
||||
bool HasTag(const std::string& tag) const;
|
||||
int NumEntries() const { return num_entries_; }
|
||||
int NumEntries(const std::string& tag) const;
|
||||
CollectionItemId GetId(const std::string& tag, int index) const;
|
||||
std::set<std::string> GetTags() const;
|
||||
std::pair<std::string, int> TagAndIndexFromId(CollectionItemId id) const;
|
||||
CollectionItemId BeginId() const { return CollectionItemId(0); }
|
||||
CollectionItemId EndId() const { return CollectionItemId(num_entries_); }
|
||||
CollectionItemId BeginId(const std::string& tag) const;
|
||||
CollectionItemId EndId(const std::string& tag) const;
|
||||
|
||||
private:
|
||||
// Use static factory function TagMap::Create().
|
||||
TagMap() {}
|
||||
|
||||
// Initialize the TagMap. Due to only having a factory function for
|
||||
// creation, there is no way for a user to have an uninitialized TagMap.
|
||||
::mediapipe::Status Initialize(
|
||||
const proto_ns::RepeatedPtrField<ProtoString>& tag_index_names);
|
||||
|
||||
// Initialize from a TagAndNameInfo.
|
||||
ABSL_DEPRECATED("Use Initialize(tag_index_names) instead.")
|
||||
::mediapipe::Status Initialize(const TagAndNameInfo& info);
|
||||
|
||||
// Initialize names_ using a map from tag to the names for that tag.
|
||||
void InitializeNames(
|
||||
const std::map<std::string, std::vector<std::string>>& tag_to_names);
|
||||
|
||||
// The total number of entries under all tags.
|
||||
int num_entries_;
|
||||
// Mapping from tag to tag data.
|
||||
std::map<std::string, TagData> mapping_;
|
||||
// The names of the data (indexed by CollectionItemId).
|
||||
std::vector<std::string> names_;
|
||||
};
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_TAG_MAP_H_
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/tag_map_helper.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "mediapipe/framework/port/core_proto_inc.h"
|
||||
#include "mediapipe/framework/port/logging.h"
|
||||
#include "mediapipe/framework/port/proto_ns.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/port/status_macros.h"
|
||||
#include "mediapipe/framework/port/statusor.h"
|
||||
#include "mediapipe/framework/tool/tag_map.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
// Create using a vector of TAG:<index>:name.
|
||||
::mediapipe::StatusOr<std::shared_ptr<TagMap>> CreateTagMap(
|
||||
const std::vector<std::string>& tag_index_names) {
|
||||
proto_ns::RepeatedPtrField<ProtoString> fields;
|
||||
for (const auto& tag_index_name : tag_index_names) {
|
||||
*fields.Add() = tag_index_name;
|
||||
}
|
||||
return TagMap::Create(fields);
|
||||
}
|
||||
|
||||
// Create using an integer number of entries (for tag "").
|
||||
::mediapipe::StatusOr<std::shared_ptr<TagMap>> CreateTagMap(int num_entries) {
|
||||
RET_CHECK_LE(0, num_entries);
|
||||
proto_ns::RepeatedPtrField<ProtoString> fields;
|
||||
for (int i = 0; i < num_entries; ++i) {
|
||||
*fields.Add() = absl::StrCat("name", i);
|
||||
}
|
||||
return TagMap::Create(fields);
|
||||
}
|
||||
|
||||
// Create using a vector of just tag names.
|
||||
::mediapipe::StatusOr<std::shared_ptr<TagMap>> CreateTagMapFromTags(
|
||||
const std::vector<std::string>& tags) {
|
||||
proto_ns::RepeatedPtrField<ProtoString> fields;
|
||||
for (int i = 0; i < tags.size(); ++i) {
|
||||
*fields.Add() = absl::StrCat(tags[i], ":name", i);
|
||||
}
|
||||
return TagMap::Create(fields);
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_TAG_MAP_HELPER_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_TAG_MAP_HELPER_H_
|
||||
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/port/statusor.h"
|
||||
#include "mediapipe/framework/tool/tag_map.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
// Create a TagMap using a vector of TAG:<index>:name.
|
||||
::mediapipe::StatusOr<std::shared_ptr<TagMap>> CreateTagMap(
|
||||
const std::vector<std::string>& tag_index_names);
|
||||
|
||||
// Create a TagMap using an integer number of entries (for tag "").
|
||||
::mediapipe::StatusOr<std::shared_ptr<TagMap>> CreateTagMap(int num_entries);
|
||||
|
||||
// Create a TagMap using a vector of just tag names.
|
||||
::mediapipe::StatusOr<std::shared_ptr<TagMap>> CreateTagMapFromTags(
|
||||
const std::vector<std::string>& tags);
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_TAG_MAP_HELPER_H_
|
||||
@@ -0,0 +1,446 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/tag_map.h"
|
||||
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/map_util.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h"
|
||||
#include "mediapipe/framework/tool/tag_map_helper.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace {
|
||||
|
||||
TEST(TagMapTest, Create) {
|
||||
// Create using tags.
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMapFromTags({}));
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMapFromTags({"BLAH"}));
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMapFromTags({"BLAH1", "BLAH2"}));
|
||||
// Tags must be uppercase.
|
||||
EXPECT_FALSE(tool::CreateTagMapFromTags({"blah1", "BLAH2"}).ok());
|
||||
|
||||
// Create with TAG:<index>:names.
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap({}));
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap({"blah"}));
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap({"blah1", "blah2"}));
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap({"BLAH:blah"}));
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap({"BLAH1:blah1", "BLAH2:blah2"}));
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap({"BLAH:0:blah1", "BLAH:1:blah2"}));
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap({"BLAH:blah1", "BLAH:1:blah2"}));
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap(
|
||||
{"A:2:a2", "B:1:b1", "C:c0", "A:0:a0", "B:b0", "A:1:a1"}));
|
||||
MEDIAPIPE_EXPECT_OK(
|
||||
tool::CreateTagMap({"w", "A:2:a2", "x", "B:1:b1", "C:c0", "y", "A:0:a0",
|
||||
"B:b0", "z", "A:1:a1"}));
|
||||
MEDIAPIPE_EXPECT_OK(
|
||||
tool::CreateTagMap({"A:2:a2", "w", "x", "B:1:b1", "C:c0", "y", "A:0:a0",
|
||||
"B:b0", "z", "A:1:a1"}));
|
||||
|
||||
// Reuse name.
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap({"a", "A:a"}));
|
||||
// Reuse name.
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap({"a", "a"}));
|
||||
// Reuse name.
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap({"C:c", "a", "a"}));
|
||||
// Reuse name.
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap({"A:a", "B:a"}));
|
||||
|
||||
// Reuse same tag.
|
||||
EXPECT_FALSE(tool::CreateTagMap({"BLAH:blah1", "BLAH:blah2"}).ok());
|
||||
// Tag starts with number.
|
||||
EXPECT_FALSE(tool::CreateTagMap({"0:blah1", "1:blah2"}).ok());
|
||||
// Skipped index 0.
|
||||
EXPECT_FALSE(tool::CreateTagMap({"BLAH:1:blah1", "BLAH:2:blah2"}).ok());
|
||||
// Reuse index 0.
|
||||
EXPECT_FALSE(tool::CreateTagMap({"BLAH:blah1", "BLAH:0:blah2"}).ok());
|
||||
// Mixing tags and no tags.
|
||||
EXPECT_FALSE(
|
||||
tool::CreateTagMap({"blah0", "BLAH:1:blah1", "BLAH:2:blah2"}).ok());
|
||||
|
||||
// Create using an index.
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap(0));
|
||||
MEDIAPIPE_EXPECT_OK(tool::CreateTagMap(3));
|
||||
// Negative number of entries.
|
||||
EXPECT_FALSE(tool::CreateTagMap(-1).ok());
|
||||
|
||||
// Create using a TagAndNameInfo.
|
||||
tool::TagAndNameInfo info;
|
||||
info.names = {"blah1", "blah2"};
|
||||
MEDIAPIPE_EXPECT_OK(tool::TagMap::Create(info));
|
||||
info.tags = {"BLAH1", "BLAH2", "BLAH3"};
|
||||
// Number of tags and names do not match.
|
||||
EXPECT_FALSE(tool::TagMap::Create(info).ok());
|
||||
info.names.push_back("blah3");
|
||||
MEDIAPIPE_EXPECT_OK(tool::TagMap::Create(info));
|
||||
}
|
||||
|
||||
void TestSuccessTagMap(const std::vector<std::string>& tag_index_names,
|
||||
bool create_from_tags, int num_entries,
|
||||
const std::vector<std::string>& tags,
|
||||
const std::vector<std::string>& names) {
|
||||
std::shared_ptr<tool::TagMap> tag_map;
|
||||
if (create_from_tags) {
|
||||
tag_map = tool::CreateTagMapFromTags(tag_index_names).ValueOrDie();
|
||||
} else {
|
||||
tag_map = tool::CreateTagMap(tag_index_names).ValueOrDie();
|
||||
}
|
||||
|
||||
EXPECT_EQ(num_entries, tag_map->NumEntries())
|
||||
<< "Parameters: in " << tag_map->DebugString();
|
||||
EXPECT_EQ(tags.size(), tag_map->Mapping().size())
|
||||
<< "Parameters: in " << tag_map->DebugString();
|
||||
for (int i = 0; i < tags.size(); ++i) {
|
||||
EXPECT_TRUE(::mediapipe::ContainsKey(tag_map->Mapping(), tags[i]))
|
||||
<< "Parameters: Trying to find \"" << tags[i] << "\" in\n"
|
||||
<< tag_map->DebugString();
|
||||
}
|
||||
EXPECT_THAT(tag_map->Names(), testing::ContainerEq(names));
|
||||
}
|
||||
|
||||
TEST(TagMapTest, AccessFunctions) {
|
||||
TestSuccessTagMap({}, /*create_from_tags=*/true, 0, {}, {});
|
||||
TestSuccessTagMap({"BLAH"}, /*create_from_tags=*/true, 1, {"BLAH"},
|
||||
{"name0"});
|
||||
TestSuccessTagMap({"BLAH1", "BLAH2"}, /*create_from_tags=*/true, 2,
|
||||
{"BLAH1", "BLAH2"}, {"name0", "name1"});
|
||||
|
||||
// Just names.
|
||||
TestSuccessTagMap({}, /*create_from_tags=*/false, 0, {}, {});
|
||||
TestSuccessTagMap({"blah"}, /*create_from_tags=*/false, 1, {""}, {"blah"});
|
||||
TestSuccessTagMap({"blah1", "blah2"}, /*create_from_tags=*/false, 2, {""},
|
||||
{"blah1", "blah2"});
|
||||
|
||||
// Just Tags.
|
||||
// 1 tag.
|
||||
TestSuccessTagMap({"BLAH:blah"}, /*create_from_tags=*/false, 1, {"BLAH"},
|
||||
{"blah"});
|
||||
// 2 tags.
|
||||
TestSuccessTagMap({"BLAH1:blah1", "BLAH2:blah2"}, /*create_from_tags=*/false,
|
||||
2, {"BLAH1", "BLAH2"}, {"blah1", "blah2"});
|
||||
TestSuccessTagMap({"BLAH2:blah2", "BLAH1:blah1"}, /*create_from_tags=*/false,
|
||||
2, {"BLAH1", "BLAH2"}, {"blah1", "blah2"});
|
||||
|
||||
// 1 Tag, 2 indexes.
|
||||
TestSuccessTagMap({"BLAH:0:blah1", "BLAH:1:blah2"},
|
||||
/*create_from_tags=*/false, 2, {"BLAH"},
|
||||
{"blah1", "blah2"});
|
||||
TestSuccessTagMap({"BLAH:blah1", "BLAH:1:blah2"},
|
||||
/*create_from_tags=*/false, 2, {"BLAH"},
|
||||
{"blah1", "blah2"});
|
||||
TestSuccessTagMap({"BLAH:1:blah2", "BLAH:blah1"},
|
||||
/*create_from_tags=*/false, 2, {"BLAH"},
|
||||
{"blah1", "blah2"});
|
||||
TestSuccessTagMap({"BLAH:1:blah2", "BLAH:0:blah1"},
|
||||
/*create_from_tags=*/false, 2, {"BLAH"},
|
||||
{"blah1", "blah2"});
|
||||
|
||||
// Mixing tags with 1 index and more indexes.
|
||||
TestSuccessTagMap({"A:2:a2", "B:1:b1", "C:c0", "A:0:a0", "B:b0", "A:1:a1"},
|
||||
/*create_from_tags=*/false, 6, {"A", "B", "C"},
|
||||
{"a0", "a1", "a2", "b0", "b1", "c0"});
|
||||
// Mixing tags with 1 index and more indexes and positional names (tag "").
|
||||
TestSuccessTagMap({"w", "A:2:a2", "x", "B:1:b1", "C:c0", "y", "A:0:a0",
|
||||
"B:b0", "z", "A:1:a1"},
|
||||
/*create_from_tags=*/false, 10, {"", "A", "B", "C"},
|
||||
{"w", "x", "y", "z", "a0", "a1", "a2", "b0", "b1", "c0"});
|
||||
// Same as previous, but change the order (so we don't start with "w").
|
||||
TestSuccessTagMap({"A:2:a2", "w", "x", "B:1:b1", "C:c0", "y", "A:0:a0",
|
||||
"B:b0", "z", "A:1:a1"},
|
||||
/*create_from_tags=*/false, 10, {"", "A", "B", "C"},
|
||||
{"w", "x", "y", "z", "a0", "a1", "a2", "b0", "b1", "c0"});
|
||||
}
|
||||
|
||||
TEST(TagMapTest, SameAs) {
|
||||
// A bunch of initialization vectors and their equivalence classes.
|
||||
// First argument is the equivalence class id. Everything is SameAs()
|
||||
// everything else with the same id and different from (!SameAs())
|
||||
// everything in a different equivalence class. Second argument is
|
||||
// whether the vector is of just tags (and not a full tag/index/name).
|
||||
int count = 0;
|
||||
std::vector<std::tuple<int, bool, std::vector<std::string>>>
|
||||
initialization_parameters = {
|
||||
std::make_tuple(++count, true, std::vector<std::string>({})),
|
||||
std::make_tuple(count, false, std::vector<std::string>({})),
|
||||
|
||||
// One tag.
|
||||
std::make_tuple(++count, true, std::vector<std::string>({"BLAH"})),
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"BLAH:blah"})),
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"BLAH:different"})),
|
||||
|
||||
// Two tags.
|
||||
std::make_tuple(++count, true,
|
||||
std::vector<std::string>({"BLAH1", "BLAH2"})),
|
||||
std::make_tuple(
|
||||
count, false,
|
||||
std::vector<std::string>({"BLAH1:blah1", "BLAH2:blah2"})),
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"BLAH1:a", "BLAH2:b"})),
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"BLAH2:a", "BLAH1:b"})),
|
||||
|
||||
// Two (different) tags.
|
||||
std::make_tuple(++count, true,
|
||||
std::vector<std::string>({"BLAH2", "BLAH3"})),
|
||||
std::make_tuple(
|
||||
count, false,
|
||||
std::vector<std::string>({"BLAH2:blah1", "BLAH3:blah2"})),
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"BLAH3:a", "BLAH2:b"})),
|
||||
|
||||
// Three tags.
|
||||
std::make_tuple(++count, true,
|
||||
std::vector<std::string>({"A", "B", "C"})),
|
||||
std::make_tuple(
|
||||
count, false,
|
||||
std::vector<std::string>({"B:blah1", "A:blah3", "C:blah2"})),
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"C:a", "A:b", "B:c"})),
|
||||
|
||||
// 2 indexes.
|
||||
std::make_tuple(++count, false, std::vector<std::string>({"a", "b"})),
|
||||
std::make_tuple(count, false, std::vector<std::string>({"c", "d"})),
|
||||
std::make_tuple(count, false, std::vector<std::string>({"a", "d"})),
|
||||
std::make_tuple(count, false, std::vector<std::string>({"d", "a"})),
|
||||
|
||||
// 3 indexes (switch with different sort orders).
|
||||
std::make_tuple(++count, false,
|
||||
std::vector<std::string>({"a", "b", "c"})),
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"c", "b", "a"})),
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"d", "e", "f"})),
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"a", "b", "f"})),
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"f", "e", "d"})),
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"f", "c", "d"})),
|
||||
|
||||
// 1 Tag, 2 indexes.
|
||||
std::make_tuple(
|
||||
++count, false,
|
||||
std::vector<std::string>({"BLAH:0:blah1", "BLAH:1:blah2"})),
|
||||
std::make_tuple(
|
||||
count, false,
|
||||
std::vector<std::string>({"BLAH:blah1", "BLAH:1:blah2"})),
|
||||
std::make_tuple(
|
||||
count, false,
|
||||
std::vector<std::string>({"BLAH:1:blah1", "BLAH:0:blah2"})),
|
||||
std::make_tuple(
|
||||
count, false,
|
||||
std::vector<std::string>({"BLAH:1:blah1", "BLAH:blah2"})),
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"BLAH:1:a", "BLAH:b"})),
|
||||
|
||||
// Mixing tags with 1 index and more indexes.
|
||||
std::make_tuple(
|
||||
++count, false,
|
||||
std::vector<std::string>(
|
||||
{"A:2:a2", "B:1:b1", "C:c0", "A:0:a0", "B:b0", "A:1:a1"})),
|
||||
// Reordered.
|
||||
std::make_tuple(
|
||||
count, false,
|
||||
std::vector<std::string>(
|
||||
{"A:0:a0", "A:2:a2", "A:1:a1", "B:1:b1", "C:c0", "B:b0"})),
|
||||
// Renamed names.
|
||||
std::make_tuple(count, false,
|
||||
std::vector<std::string>({"A:0:a", "A:2:b", "A:1:c",
|
||||
"B:1:d", "C:e", "B:f"})),
|
||||
// Change which strings have index 0 specified.
|
||||
std::make_tuple(
|
||||
count, false,
|
||||
std::vector<std::string>(
|
||||
{"A:a", "A:2:b", "A:1:c", "B:1:d", "C:0:e", "B:0:f"})),
|
||||
|
||||
// Mixing tags with 1 index and more indexes and positional
|
||||
// names (tag "").
|
||||
std::make_tuple(
|
||||
++count, false,
|
||||
std::vector<std::string>({"w", "A:2:a2", "x", "B:1:b1", "C:c0",
|
||||
"y", "A:0:a0", "B:b0", "z", "A:1:a1"})),
|
||||
// Reordered.
|
||||
std::make_tuple(
|
||||
count, false,
|
||||
std::vector<std::string>({"C:c0", "A:1:a1", "y", "A:0:a0", "w",
|
||||
"A:2:a2", "x", "B:1:b1", "B:b0", "z"})),
|
||||
// Rename names.
|
||||
std::make_tuple(
|
||||
count, false,
|
||||
std::vector<std::string>({"C:a", "A:1:b", "c", "A:2:d", "e",
|
||||
"B:1:f", "g", "A:0:h", "B:i", "j"})),
|
||||
// Change which strings have index 0 specified.
|
||||
std::make_tuple(
|
||||
count, false,
|
||||
std::vector<std::string>({"C:0:a", "A:1:b", "c", "A:2:d", "e",
|
||||
"B:1:f", "g", "A:h", "B:0:i", "j"})),
|
||||
};
|
||||
|
||||
// Create a TagMap for each entry in initialization_parameters.
|
||||
std::vector<std::shared_ptr<tool::TagMap>> tag_maps;
|
||||
for (const auto& parameters : initialization_parameters) {
|
||||
if (std::get<1>(parameters)) {
|
||||
auto statusor_tag_map =
|
||||
tool::CreateTagMapFromTags(std::get<2>(parameters));
|
||||
MEDIAPIPE_ASSERT_OK(statusor_tag_map);
|
||||
tag_maps.push_back(std::move(statusor_tag_map.ValueOrDie()));
|
||||
} else {
|
||||
auto statusor_tag_map = tool::CreateTagMap(std::get<2>(parameters));
|
||||
MEDIAPIPE_ASSERT_OK(statusor_tag_map);
|
||||
tag_maps.push_back(std::move(statusor_tag_map.ValueOrDie()));
|
||||
}
|
||||
}
|
||||
|
||||
// Check every TagMap against every other (in both orders).
|
||||
for (int i = 0; i < initialization_parameters.size(); ++i) {
|
||||
int equivalence = std::get<0>(initialization_parameters[i]);
|
||||
for (int k = 0; k < initialization_parameters.size(); ++k) {
|
||||
EXPECT_EQ(std::get<0>(initialization_parameters[k]) == equivalence,
|
||||
tag_maps[i]->SameAs(*tag_maps[k]))
|
||||
<< "ShortDebugStrings i, k\n"
|
||||
<< tag_maps[i]->ShortDebugString() << "\n"
|
||||
<< tag_maps[k]->ShortDebugString() << "\nDebugString tag_maps[i]\n"
|
||||
<< tag_maps[i]->DebugString() << "\nDebugString tag_maps[k]\n"
|
||||
<< tag_maps[k]->DebugString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A helper function to test that a TagMap's debug std::string and short
|
||||
// debug std::string each satisfy a matcher.
|
||||
template <typename Matcher>
|
||||
void TestDebugString(const ::mediapipe::StatusOr<std::shared_ptr<tool::TagMap>>&
|
||||
statusor_tag_map,
|
||||
const std::vector<std::string>& canonical_entries,
|
||||
Matcher short_string_matcher) {
|
||||
MEDIAPIPE_ASSERT_OK(statusor_tag_map);
|
||||
tool::TagMap& tag_map = *statusor_tag_map.ValueOrDie();
|
||||
std::string debug_string = tag_map.DebugString();
|
||||
std::string short_string = tag_map.ShortDebugString();
|
||||
LOG(INFO) << "ShortDebugString:\n" << short_string << "\n";
|
||||
LOG(INFO) << "DebugString:\n" << debug_string << "\n\n";
|
||||
|
||||
std::vector<std::string> actual_entries;
|
||||
for (const auto& field : tag_map.CanonicalEntries()) {
|
||||
actual_entries.push_back(field);
|
||||
}
|
||||
EXPECT_THAT(actual_entries, testing::ContainerEq(canonical_entries));
|
||||
if (canonical_entries.empty()) {
|
||||
EXPECT_THAT(debug_string, testing::Eq("empty"));
|
||||
} else {
|
||||
EXPECT_THAT(debug_string,
|
||||
testing::Eq(absl::StrJoin(canonical_entries, "\n")));
|
||||
}
|
||||
EXPECT_THAT(short_string, short_string_matcher);
|
||||
}
|
||||
|
||||
TEST(TagMapTest, DebugStrings) {
|
||||
// The ContainsRegex test checks a tag and a number (of indexes)
|
||||
// appear together.
|
||||
// For example: testing::ContainsRegex("\"BLAH\"[^\\d]+\\b2\\b") tests
|
||||
// that "BLAH" is followed by the number 2 (with no numbers in between)
|
||||
// and that the number 2 is surrounded by word breaks (\b).
|
||||
|
||||
// In addition to testing the tag name and the number of indexes in it,
|
||||
// the presence of each stream name is tested (that it exists in the
|
||||
// DebugString() and doesn't in the ShortDebugString()).
|
||||
TestDebugString(
|
||||
// The TagMap to test.
|
||||
tool::CreateTagMap({"BLAH:blah1", "BLAH:1:blah2"}),
|
||||
// Canonical Entries (used to test DebugString() too.
|
||||
{"BLAH:0:blah1", "BLAH:1:blah2"},
|
||||
// Must be satisfied by ShortDebugString().
|
||||
testing::AllOf(testing::ContainsRegex("\"BLAH\"[^\\d]+\\b2\\b"),
|
||||
testing::Not(testing::HasSubstr("\"blah1\"")),
|
||||
testing::Not(testing::HasSubstr("\"blah2\""))));
|
||||
|
||||
TestDebugString(tool::CreateTagMap({"A:a", "B:b"}), {"A:a", "B:b"},
|
||||
testing::AllOf(testing::ContainsRegex("\"A\"[^\\d]+\\b1\\b"),
|
||||
testing::ContainsRegex("\"B\"[^\\d]+\\b1\\b"),
|
||||
testing::Not(testing::HasSubstr("\"a\"")),
|
||||
testing::Not(testing::HasSubstr("\"b\""))));
|
||||
TestDebugString(tool::CreateTagMap({"B:b", "A:a"}), {"A:a", "B:b"},
|
||||
testing::AllOf(testing::ContainsRegex("\"A\"[^\\d]+\\b1\\b"),
|
||||
testing::ContainsRegex("\"B\"[^\\d]+\\b1\\b"),
|
||||
testing::Not(testing::HasSubstr("\"a\"")),
|
||||
testing::Not(testing::HasSubstr("\"b\""))));
|
||||
TestDebugString(tool::CreateTagMap({"a", "b"}), {"a", "b"},
|
||||
testing::AllOf(testing::ContainsRegex("\"\"[^\\d]+\\b2\\b"),
|
||||
testing::Not(testing::HasSubstr("\"a\"")),
|
||||
testing::Not(testing::HasSubstr("\"b\""))));
|
||||
TestDebugString(tool::CreateTagMap({"b", "a"}), {"b", "a"},
|
||||
testing::AllOf(testing::ContainsRegex("\"\"[^\\d]+\\b2\\b"),
|
||||
testing::Not(testing::HasSubstr("\"a\"")),
|
||||
testing::Not(testing::HasSubstr("\"b\""))));
|
||||
TestDebugString(tool::CreateTagMap(3), {"name0", "name1", "name2"},
|
||||
testing::ContainsRegex("\"\"[^\\d]+\\b3\\b"));
|
||||
TestDebugString(tool::CreateTagMap(
|
||||
{"A:2:a2", "B:1:b1", "C:c0", "A:0:a0", "B:b0", "A:1:a1"}),
|
||||
{"A:0:a0", "A:1:a1", "A:2:a2", "B:0:b0", "B:1:b1", "C:c0"},
|
||||
testing::AllOf(testing::ContainsRegex("\"A\"[^\\d]+\\b3\\b"),
|
||||
testing::ContainsRegex("\"B\"[^\\d]+\\b2\\b"),
|
||||
testing::ContainsRegex("\"C\"[^\\d]+\\b1\\b"),
|
||||
testing::Not(testing::HasSubstr("\"a0\"")),
|
||||
testing::Not(testing::HasSubstr("\"a1\"")),
|
||||
testing::Not(testing::HasSubstr("\"a2\"")),
|
||||
testing::Not(testing::HasSubstr("\"b0\"")),
|
||||
testing::Not(testing::HasSubstr("\"b1\"")),
|
||||
testing::Not(testing::HasSubstr("\"c0\""))));
|
||||
TestDebugString(tool::CreateTagMap({"A:2:a2", "x", "B:1:b1", "C:c0", "y",
|
||||
"A:0:a0", "B:b0", "z", "A:1:a1", "w"}),
|
||||
{"x", "y", "z", "w", "A:0:a0", "A:1:a1", "A:2:a2", "B:0:b0",
|
||||
"B:1:b1", "C:c0"},
|
||||
testing::AllOf(testing::ContainsRegex("\"\"[^\\d]+\\b4\\b"),
|
||||
testing::ContainsRegex("\"A\"[^\\d]+\\b3\\b"),
|
||||
testing::ContainsRegex("\"B\"[^\\d]+\\b2\\b"),
|
||||
testing::ContainsRegex("\"C\"[^\\d]+\\b1\\b"),
|
||||
testing::Not(testing::HasSubstr("\"w\"")),
|
||||
testing::Not(testing::HasSubstr("\"x\"")),
|
||||
testing::Not(testing::HasSubstr("\"y\"")),
|
||||
testing::Not(testing::HasSubstr("\"z\"")),
|
||||
testing::Not(testing::HasSubstr("\"a0\"")),
|
||||
testing::Not(testing::HasSubstr("\"a1\"")),
|
||||
testing::Not(testing::HasSubstr("\"a2\"")),
|
||||
testing::Not(testing::HasSubstr("\"b0\"")),
|
||||
testing::Not(testing::HasSubstr("\"b1\"")),
|
||||
testing::Not(testing::HasSubstr("\"c0\""))));
|
||||
|
||||
// Test that empty TagMap states "empty" as its DebugString and
|
||||
// ShortDebugString().
|
||||
TestDebugString(tool::CreateTagMap(0), {},
|
||||
testing::ContainsRegex("\\bempty\\b"));
|
||||
TestDebugString(tool::CreateTagMap({}), {},
|
||||
testing::ContainsRegex("\\bempty\\b"));
|
||||
TestDebugString(tool::CreateTagMapFromTags({}), {},
|
||||
testing::ContainsRegex("\\bempty\\b"));
|
||||
|
||||
// Test that TagAndNameInfo can be used as well.
|
||||
tool::TagAndNameInfo info;
|
||||
info.names = {"blah1", "blah2", "blah3"};
|
||||
info.tags = {"BLAH1", "BLAH2", "BLAH3"};
|
||||
TestDebugString(
|
||||
tool::TagMap::Create(info), {"BLAH1:blah1", "BLAH2:blah2", "BLAH3:blah3"},
|
||||
testing::AllOf(testing::ContainsRegex("\"BLAH1\"[^\\d]+\\b1\\b"),
|
||||
testing::ContainsRegex("\"BLAH2\"[^\\d]+\\b1\\b"),
|
||||
testing::ContainsRegex("\"BLAH3\"[^\\d]+\\b1\\b"),
|
||||
testing::Not(testing::HasSubstr("\"blah1\"")),
|
||||
testing::Not(testing::HasSubstr("\"blah2\"")),
|
||||
testing::Not(testing::HasSubstr("\"blah3\""))));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,653 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/template_expander.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/ascii.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
#include "mediapipe/framework/port/canonical_errors.h"
|
||||
#include "mediapipe/framework/port/logging.h"
|
||||
#include "mediapipe/framework/port/numbers.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/tool/calculator_graph_template.pb.h"
|
||||
#include "mediapipe/framework/tool/proto_util_lite.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
|
||||
using mediapipe::proto_ns::MessageLite;
|
||||
using mediapipe::tool::ProtoUtilLite;
|
||||
using WireFormatLite = ProtoUtilLite::WireFormatLite;
|
||||
using FieldValue = ProtoUtilLite::FieldValue;
|
||||
using FieldType = ProtoUtilLite::FieldType;
|
||||
using ProtoPath = ProtoUtilLite::ProtoPath;
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns a template argument by name.
|
||||
TemplateArgument* GetItem(TemplateDict* args, const std::string& name) {
|
||||
for (TemplateDict::Parameter& arg : *args->mutable_arg()) {
|
||||
if (arg.key() == name) {
|
||||
return arg.mutable_value();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Sets the template argument for a param name.
|
||||
void PutItem(TemplateDict* args, const std::string& name,
|
||||
const TemplateArgument* value) {
|
||||
for (int i = args->arg_size() - 1; i >= 0; --i) {
|
||||
if (args->arg()[i].key() == name) {
|
||||
if (value != nullptr) {
|
||||
*args->mutable_arg(i)->mutable_value() = *value;
|
||||
} else {
|
||||
args->mutable_arg()->erase(args->mutable_arg()->begin() + i);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (value != nullptr) {
|
||||
TemplateDict::Parameter* arg = args->add_arg();
|
||||
*arg->mutable_key() = name;
|
||||
*arg->mutable_value() = *value;
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a deep copy of a message.
|
||||
std::unique_ptr<MessageLite> CloneMessage(const MessageLite& message) {
|
||||
std::unique_ptr<MessageLite> result(message.New());
|
||||
result->CheckTypeAndMergeFrom(message);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns the (tag, index) pairs in a field path.
|
||||
// For example, returns {{1, 1}, {2, 1}, {3, 1}} for path "/1[1]/2[1]/3[1]".
|
||||
mediapipe::Status ProtoPathSplit(const std::string& path, ProtoPath* result) {
|
||||
mediapipe::Status status;
|
||||
std::vector<std::string> ids = absl::StrSplit(path, '/');
|
||||
for (const std::string& id : ids) {
|
||||
if (id.length() > 0) {
|
||||
std::pair<std::string, std::string> id_pair =
|
||||
absl::StrSplit(id, absl::ByAnyChar("[]"));
|
||||
int tag = 0;
|
||||
int index = 0;
|
||||
bool ok = absl::SimpleAtoi(id_pair.first, &tag) &&
|
||||
absl::SimpleAtoi(id_pair.second, &index);
|
||||
if (!ok) {
|
||||
status.Update(::mediapipe::InvalidArgumentError(path));
|
||||
}
|
||||
result->push_back(std::make_pair(tag, index));
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Returns true if one proto path is prefix by another.
|
||||
bool ProtoPathStartsWith(const std::string& path, const std::string& prefix) {
|
||||
return absl::StartsWith(path, prefix);
|
||||
}
|
||||
|
||||
// Returns the part of one proto path after a prefix proto path.
|
||||
std::string ProtoPathRelative(const std::string& field_path,
|
||||
const std::string& base_path) {
|
||||
CHECK(ProtoPathStartsWith(field_path, base_path));
|
||||
return field_path.substr(base_path.length());
|
||||
}
|
||||
|
||||
// Returns the target ProtoUtilLite::FieldType of a rule.
|
||||
FieldType GetFieldType(const TemplateExpression& rule) {
|
||||
return static_cast<FieldType>(rule.field_type());
|
||||
}
|
||||
|
||||
// Returns the count of field values at a ProtoPath.
|
||||
int FieldCount(const FieldValue& base, ProtoPath field_path,
|
||||
FieldType field_type) {
|
||||
int field_id, index;
|
||||
std::tie(field_id, index) = field_path.back();
|
||||
field_path.pop_back();
|
||||
std::vector<FieldValue> parent;
|
||||
if (field_path.empty()) {
|
||||
parent.push_back(base);
|
||||
} else {
|
||||
MEDIAPIPE_CHECK_OK(ProtoUtilLite::GetFieldRange(
|
||||
base, field_path, 1, WireFormatLite::TYPE_MESSAGE, &parent));
|
||||
}
|
||||
ProtoUtilLite::FieldAccess access(field_id, field_type);
|
||||
MEDIAPIPE_CHECK_OK(access.SetMessage(parent[0]));
|
||||
return access.mutable_field_values()->size();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// The default implementation for the mediapipe template rule interpreter.
|
||||
class TemplateExpanderImpl {
|
||||
public:
|
||||
explicit TemplateExpanderImpl(std::vector<::mediapipe::Status>* errors)
|
||||
: errors_(errors) {}
|
||||
|
||||
// Applies the rules specified in a CalculatorGraphTemplate to a
|
||||
// CalculatorGraphConfig. Each rule references a nested field-value or
|
||||
// message and defines zero or more replacement values for it.
|
||||
bool ExpandTemplates(const TemplateDict& args,
|
||||
const CalculatorGraphTemplate& templ,
|
||||
CalculatorGraphConfig* output) {
|
||||
// Extract the serialized CalculatorGraphConfig.
|
||||
FieldValue base_value;
|
||||
if (!templ.config().SerializeToString(&base_value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the CalculatorGraphTemplate rules.
|
||||
template_rules_ = templ;
|
||||
template_rules_.clear_config();
|
||||
|
||||
// Invoke recursive rule expansion.
|
||||
environment_ = args;
|
||||
std::vector<FieldValue> result;
|
||||
if (!ExpandNestedRules(0, "", base_value, &result)) {
|
||||
return false;
|
||||
}
|
||||
return output->ParseFromString(result[0]);
|
||||
}
|
||||
|
||||
private:
|
||||
// Expands a template rule of a specific type.
|
||||
// Modifies a base message to produce one or more expanded messages.
|
||||
// Ownership of the result messages is transferred to the caller.
|
||||
bool ExpandTemplateRule(int base_index, const FieldValue& base_message,
|
||||
std::vector<FieldValue>* result) {
|
||||
// Exapand a template rule of a specific type.
|
||||
const TemplateExpression& rule = template_rules_.rule().Get(base_index);
|
||||
if (rule.op() == "for") {
|
||||
ExpandIterationRule(base_index, base_message, result);
|
||||
} else if (rule.op() == "if") {
|
||||
ExpandConditionalRule(base_index, base_message, result);
|
||||
} else if (rule.op() == "param") {
|
||||
ExpandDeclaration(base_index, base_message, result);
|
||||
} else {
|
||||
ExpandExpressionRule(base_index, result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Apply any remaining rules on the current field.
|
||||
// If the next rule also applies to the current field, apply it.
|
||||
// Otherwise, apply rules for nested fields.
|
||||
bool ExpandPeerRules(int base_index, const FieldValue& base_message,
|
||||
std::vector<FieldValue>* result) {
|
||||
// If the next rule applies to the same message, apply it now.
|
||||
auto& base_rule = template_rules_.rule().Get(base_index);
|
||||
int next_index = base_index + 1;
|
||||
if (next_index < template_rules_.rule().size()) {
|
||||
auto& next_rule = template_rules_.rule().Get(next_index);
|
||||
if (next_rule.path() == base_rule.path()) {
|
||||
return ExpandTemplateRule(next_index, base_message, result);
|
||||
}
|
||||
}
|
||||
|
||||
// Otheriwse, apply rules for nested fields.
|
||||
return ExpandNestedRules(next_index, base_rule.path(), base_message,
|
||||
result);
|
||||
}
|
||||
|
||||
// Return the field values addressed by a template rule.
|
||||
mediapipe::Status GetBaseValue(const std::string& base_path,
|
||||
const TemplateExpression& rule,
|
||||
const FieldValue& output,
|
||||
std::vector<FieldValue>* base) {
|
||||
if (!rule.has_path()) {
|
||||
base->push_back(output);
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
if (rule.has_field_value()) {
|
||||
// For a non-repeated field, the field value is stored only in the rule.
|
||||
base->push_back(rule.field_value());
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
ProtoPath field_path;
|
||||
mediapipe::Status status =
|
||||
ProtoPathSplit(ProtoPathRelative(rule.path(), base_path), &field_path);
|
||||
if (!status.ok()) return status;
|
||||
return ProtoUtilLite::GetFieldRange(output, field_path, 1,
|
||||
GetFieldType(rule), base);
|
||||
}
|
||||
|
||||
// Replace the field values addressed by a template rule.
|
||||
mediapipe::Status ReplaceBaseValue(
|
||||
const std::string& base_path, const TemplateExpression& rule,
|
||||
const std::vector<FieldValue>& field_values, FieldValue* output) {
|
||||
if (!rule.has_path()) {
|
||||
*output = field_values[0];
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
ProtoPath field_path;
|
||||
RET_CHECK_OK(
|
||||
ProtoPathSplit(ProtoPathRelative(rule.path(), base_path), &field_path));
|
||||
int field_count = 1;
|
||||
if (rule.has_field_value()) {
|
||||
// For a non-repeated field, only one value can be specified.
|
||||
if (!field_values.empty() &&
|
||||
FieldCount(*output, field_path, GetFieldType(rule)) > 0) {
|
||||
return ::mediapipe::InvalidArgumentError(absl::StrCat(
|
||||
"Multiple values specified for non-repeated field: ", rule.path()));
|
||||
}
|
||||
// For a non-repeated field, the field value is stored only in the rule.
|
||||
field_path[field_path.size() - 1].second = 0;
|
||||
field_count = 0;
|
||||
}
|
||||
return ProtoUtilLite::ReplaceFieldRange(output, field_path, field_count,
|
||||
GetFieldType(rule), field_values);
|
||||
}
|
||||
|
||||
// Replaces nested fields by following nested template rules.
|
||||
bool ExpandNestedRules(int base_index, const std::string& base_path,
|
||||
const FieldValue& base_message,
|
||||
std::vector<FieldValue>* result) {
|
||||
mediapipe::Status status;
|
||||
FieldValue output = base_message;
|
||||
|
||||
// Evaluate the rules nested below base_path in lexical order.
|
||||
std::vector<int> rules = GetNestedRules(base_index, base_path);
|
||||
std::vector<std::vector<FieldValue>> edits;
|
||||
for (int i = 0; i < rules.size(); ++i) {
|
||||
const auto& rule = template_rules_.rule().Get(rules[i]);
|
||||
std::vector<FieldValue> base;
|
||||
status = GetBaseValue(base_path, rule, output, &base);
|
||||
if (!status.ok()) break;
|
||||
std::vector<FieldValue> values;
|
||||
if (!ExpandTemplateRule(rules[i], base[0], &values)) {
|
||||
status = ::mediapipe::InternalError("ExpandTemplateRule failed");
|
||||
break;
|
||||
}
|
||||
edits.push_back(values);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
RecordError(status);
|
||||
return false;
|
||||
}
|
||||
// Replace base field values with the evaluated results.
|
||||
// Edits are applied in reverse order since later indices are invalidated.
|
||||
for (int i = edits.size() - 1; i >= 0; --i) {
|
||||
const auto& rule = template_rules_.rule().Get(rules[i]);
|
||||
status = ReplaceBaseValue(base_path, rule, edits[i], &output);
|
||||
if (!status.ok()) break;
|
||||
}
|
||||
if (!status.ok()) {
|
||||
RecordError(status);
|
||||
return false;
|
||||
}
|
||||
result->push_back(output);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns indexes of the rules directly nested within a certain rule.
|
||||
std::vector<int> GetNestedRules(int rule_index,
|
||||
const std::string& rule_path) {
|
||||
std::vector<int> result;
|
||||
std::string prev_path = "-1[-1]";
|
||||
for (int i = rule_index; i < template_rules_.rule().size(); ++i) {
|
||||
auto& rule = template_rules_.rule().Get(i);
|
||||
if (!ProtoPathStartsWith(rule.path(), rule_path)) {
|
||||
break;
|
||||
}
|
||||
if (!ProtoPathStartsWith(rule.path(), prev_path)) {
|
||||
result.push_back(i);
|
||||
prev_path = rule.path();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Apply a "for" operation to a base message.
|
||||
// Expands nested rules once for each iteration range value.
|
||||
bool ExpandIterationRule(int base_index, const FieldValue& base_message,
|
||||
std::vector<FieldValue>* result) {
|
||||
// Retrieve the var param and the range expression.
|
||||
const TemplateExpression& rule = template_rules_.rule().Get(base_index);
|
||||
std::string var_param = rule.arg().Get(0).param();
|
||||
const TemplateExpression& range_expr = rule.arg().Get(1);
|
||||
TemplateArgument range = EvalExpression(range_expr);
|
||||
|
||||
// For each value of the range param, expand all nested rules.
|
||||
TemplateArgument* shadow_item = GetItem(&environment_, var_param);
|
||||
for (const TemplateArgument& item : range.element()) {
|
||||
PutItem(&environment_, var_param, &item);
|
||||
ExpandPeerRules(base_index, base_message, result);
|
||||
}
|
||||
PutItem(&environment_, var_param, shadow_item);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Initializes a parameter in the parameter environment.
|
||||
bool ExpandDeclaration(int base_index, const FieldValue& base_message,
|
||||
std::vector<FieldValue>* result) {
|
||||
// Retrieve the var param and the range expression.
|
||||
const TemplateExpression& rule = template_rules_.rule().Get(base_index);
|
||||
if (rule.arg().empty() || rule.arg().size() > 2) {
|
||||
RecordError(::mediapipe::InvalidArgumentError(
|
||||
"Param declaration must specify a parameter name and "
|
||||
"may specify a single default value."));
|
||||
}
|
||||
// TODO: Validate that all params are declared or none.
|
||||
// Delarations for required params will have no default value.
|
||||
if (rule.arg().size() == 2) {
|
||||
std::string var_param = rule.arg().Get(0).param();
|
||||
const TemplateExpression& item_expr = rule.arg().Get(1);
|
||||
TemplateArgument item = EvalExpression(item_expr);
|
||||
// The parameter default value is used if no other value is specified.
|
||||
if (GetItem(&environment_, var_param) == nullptr) {
|
||||
PutItem(&environment_, var_param, &item);
|
||||
}
|
||||
}
|
||||
ExpandPeerRules(base_index, base_message, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Applies an "if" operation to a base message.
|
||||
// Expands nested rules zero or more times.
|
||||
bool ExpandConditionalRule(int base_index, const FieldValue& base_message,
|
||||
std::vector<FieldValue>* result) {
|
||||
// Retrieve the condition expression.
|
||||
const TemplateExpression& rule = template_rules_.rule().Get(base_index);
|
||||
// Expand this template zero or one times.
|
||||
bool condition = AsBool(EvalExpression(rule.arg(0)));
|
||||
if (condition) {
|
||||
ExpandPeerRules(base_index, base_message, result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// A self-contained expression just defines a single result value.
|
||||
bool ExpandExpressionRule(int base_index, std::vector<FieldValue>* result) {
|
||||
const TemplateExpression& rule = template_rules_.rule().Get(base_index);
|
||||
TemplateArgument item = EvalExpression(rule);
|
||||
std::vector<FieldValue> values;
|
||||
mediapipe::Status status = AsFieldValues(
|
||||
std::vector<TemplateArgument>{item}, GetFieldType(rule), &values);
|
||||
if (!status.ok()) {
|
||||
RecordError(status);
|
||||
return false;
|
||||
}
|
||||
result->push_back(values[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The "param" operation does variable environment lookup.
|
||||
TemplateArgument EvalParam(const TemplateExpression& expr) {
|
||||
TemplateArgument* result = GetItem(&environment_, expr.param());
|
||||
if (result == nullptr) {
|
||||
RecordError(
|
||||
::mediapipe::NotFoundError(absl::StrCat("param: ", expr.param())));
|
||||
return AsArgument(0.0);
|
||||
}
|
||||
return *result;
|
||||
}
|
||||
|
||||
// The "." operator does template dict lookup.
|
||||
TemplateArgument EvalDot(const TemplateExpression& expr) {
|
||||
TemplateArgument lhs = EvalExpression(expr.arg(0));
|
||||
TemplateArgument* result = GetItem(lhs.mutable_dict(), expr.arg(1).param());
|
||||
if (result == nullptr) {
|
||||
RecordError(::mediapipe::NotFoundError(
|
||||
absl::StrCat("param field: ", expr.arg(1).param())));
|
||||
return AsArgument(0.0);
|
||||
}
|
||||
return *result;
|
||||
}
|
||||
|
||||
// Converts a TemplateArgument to double.
|
||||
double AsNum(const TemplateArgument& value) {
|
||||
double result = 0;
|
||||
if (value.has_num()) {
|
||||
result = value.num();
|
||||
}
|
||||
if (value.has_str()) {
|
||||
if (!absl::SimpleAtod(value.str(), &result)) {
|
||||
RecordError(::mediapipe::InvalidArgumentError(value.str()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Converts a TemplateArgument to std::string.
|
||||
std::string AsString(const TemplateArgument& value) {
|
||||
std::string result;
|
||||
if (value.has_num()) {
|
||||
result = absl::StrCat(value.num());
|
||||
}
|
||||
if (value.has_str()) {
|
||||
result = value.str();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Converts a TemplateArgument to bool.
|
||||
bool AsBool(const TemplateArgument& value) {
|
||||
bool result = false;
|
||||
if (value.has_num()) {
|
||||
return value.num() != 0;
|
||||
} else if (value.has_str()) {
|
||||
if (!absl::SimpleAtob(value.str(), &result)) {
|
||||
RecordError(::mediapipe::InvalidArgumentError(value.str()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Converts a vector of TemplateArguments to a dict TemplateArgument.
|
||||
TemplateArgument AsDict(const std::vector<TemplateArgument>& args) {
|
||||
TemplateArgument result;
|
||||
if (args.size() % 2 != 0) {
|
||||
RecordError(::mediapipe::InvalidArgumentError(absl::StrCat(
|
||||
"Dict requires an even number of arguments, got: ", args.size())));
|
||||
return result;
|
||||
}
|
||||
TemplateDict* dict = result.mutable_dict();
|
||||
for (int i = 0; i < args.size(); i += 2) {
|
||||
TemplateDict::Parameter* p = dict->add_arg();
|
||||
*p->mutable_key() = AsString(args[i]);
|
||||
*p->mutable_value() = args[i + 1];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Converts a vector of TemplateArguments to a list TemplateArgument.
|
||||
TemplateArgument AsList(const std::vector<TemplateArgument>& args) {
|
||||
TemplateArgument result;
|
||||
auto list = result.mutable_element();
|
||||
for (int i = 0; i < args.size(); ++i) {
|
||||
*list->Add() = args[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Evaluate each of the sub-expressions of a TemplateExpression.
|
||||
void EvalNestedExpressions(const TemplateExpression& expr,
|
||||
std::vector<TemplateArgument>* result) {
|
||||
for (const TemplateExpression& e : expr.arg()) {
|
||||
result->push_back(EvalExpression(e));
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if a TemplateArgument represents a number.
|
||||
bool IsNum(const TemplateArgument& value) {
|
||||
double r = 0;
|
||||
return value.has_num() || absl::SimpleAtod(value.str(), &r);
|
||||
}
|
||||
|
||||
// Returns 0 if v1 == v1, positive if v1 > v2, negative if v1 < v2.
|
||||
int CompareArgs(const TemplateArgument& v1, const TemplateArgument& v2) {
|
||||
if (IsNum(v1) && IsNum(v2)) {
|
||||
double d = AsNum(v1) - AsNum(v2);
|
||||
return (d < 0) ? -1 : (d > 0) ? 1 : 0;
|
||||
} else {
|
||||
return AsString(v1).compare(AsString(v2));
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluates a TemplateExpression to produce a template argument.
|
||||
TemplateArgument EvalExpression(const TemplateExpression& expr) {
|
||||
if (expr.op() == "literal") {
|
||||
return AsArgument(expr.param());
|
||||
} else if (expr.op() == ".") {
|
||||
return EvalDot(expr);
|
||||
} else if (expr.has_param()) {
|
||||
return EvalParam(expr);
|
||||
}
|
||||
std::vector<TemplateArgument> args;
|
||||
EvalNestedExpressions(expr, &args);
|
||||
TemplateArgument result;
|
||||
if (expr.op() == "paren") {
|
||||
result = args[0];
|
||||
} else if (expr.op() == "+") {
|
||||
if (IsNum(args[0]) && IsNum(args[1])) {
|
||||
result = AsArgument(AsNum(args[0]) + AsNum(args[1]));
|
||||
} else {
|
||||
result = AsArgument(AsString(args[0]) + AsString(args[1]));
|
||||
}
|
||||
} else if (expr.op() == "-") {
|
||||
result = AsArgument(AsNum(args[0]) - AsNum(args[1]));
|
||||
} else if (expr.op() == "*") {
|
||||
result = AsArgument(AsNum(args[0]) * AsNum(args[1]));
|
||||
} else if (expr.op() == "/") {
|
||||
result = AsArgument(AsNum(args[0]) / AsNum(args[1]));
|
||||
} else if (expr.op() == ">") {
|
||||
result = AsArgument(CompareArgs(args[0], args[1]) > 0);
|
||||
} else if (expr.op() == "<") {
|
||||
result = AsArgument(CompareArgs(args[0], args[1]) < 0);
|
||||
} else if (expr.op() == ">=") {
|
||||
result = AsArgument(CompareArgs(args[0], args[1]) >= 0);
|
||||
} else if (expr.op() == "<=") {
|
||||
result = AsArgument(CompareArgs(args[0], args[1]) <= 0);
|
||||
} else if (expr.op() == "==") {
|
||||
result = AsArgument(CompareArgs(args[0], args[1]) == 0);
|
||||
} else if (expr.op() == "!=") {
|
||||
result = AsArgument(CompareArgs(args[0], args[1]) != 0);
|
||||
} else if (expr.op() == "&&") {
|
||||
result = AsArgument(AsBool(args[0]) && AsBool(args[1]));
|
||||
} else if (expr.op() == "||") {
|
||||
result = AsArgument(AsBool(args[0]) || AsBool(args[1]));
|
||||
} else if (expr.op() == "!") {
|
||||
result = AsArgument(!(AsBool(args[0])));
|
||||
} else if (expr.op() == "min") {
|
||||
result = AsArgument(std::min(AsNum(args[0]), AsNum(args[1])));
|
||||
} else if (expr.op() == "max") {
|
||||
result = AsArgument(std::max(AsNum(args[0]), AsNum(args[1])));
|
||||
} else if (expr.op() == "concat") {
|
||||
result = AsArgument(AsString(args[0]) + AsString(args[1]));
|
||||
} else if (expr.op() == "lowercase") {
|
||||
result = AsArgument(absl::AsciiStrToLower(AsString(args[0])));
|
||||
} else if (expr.op() == "uppercase") {
|
||||
result = AsArgument(absl::AsciiStrToUpper(AsString(args[0])));
|
||||
} else if (expr.op() == "dict") {
|
||||
result = AsDict(args);
|
||||
} else if (expr.op() == "list") {
|
||||
result = AsList(args);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Converts a simple value to a template argument for further processing.
|
||||
TemplateArgument AsArgument(const std::string& value) {
|
||||
TemplateArgument result;
|
||||
result.set_str(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Converts a simple value to a template argument for further processing.
|
||||
TemplateArgument AsArgument(double value) {
|
||||
TemplateArgument result;
|
||||
result.set_num(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Converts a boolean result into a template argument for further processing.
|
||||
TemplateArgument AsArgument(bool b) {
|
||||
return AsArgument(static_cast<double>(b));
|
||||
}
|
||||
|
||||
// Convert between a proto feild value and a template argument.
|
||||
mediapipe::Status AsFieldValues(const std::vector<TemplateArgument>& args,
|
||||
FieldType field_type,
|
||||
std::vector<FieldValue>* result) {
|
||||
for (int i = 0; i < args.size(); ++i) {
|
||||
if (args[i].has_dict()) {
|
||||
FieldValue dict_bytes;
|
||||
CHECK(args[i].dict().SerializePartialToString(&dict_bytes));
|
||||
result->push_back(dict_bytes);
|
||||
} else if (args[i].has_num() || args[i].has_str()) {
|
||||
std::string text_value = args[i].has_num()
|
||||
? mediapipe::SimpleDtoa(args[i].num())
|
||||
: args[i].str();
|
||||
std::vector<FieldValue> r;
|
||||
RETURN_IF_ERROR(ProtoUtilLite::Serialize({text_value}, field_type, &r));
|
||||
result->push_back(r[0]);
|
||||
}
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
// Record a Status if it indicates an error.
|
||||
void RecordError(const ::mediapipe::Status& status) {
|
||||
if (!status.ok()) {
|
||||
errors_->push_back(status);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// The list of template rules.
|
||||
mediapipe::CalculatorGraphTemplate template_rules_;
|
||||
|
||||
// The template variable environment.
|
||||
TemplateDict environment_;
|
||||
|
||||
// List of errors found in template parameters.
|
||||
std::vector<::mediapipe::Status>* errors_;
|
||||
};
|
||||
|
||||
TemplateExpander::TemplateExpander() {}
|
||||
|
||||
// Expands template rules within a proto message.
|
||||
// Replaces template rules with expanded sub-messages.
|
||||
::mediapipe::Status TemplateExpander::ExpandTemplates(
|
||||
const TemplateDict& args, const CalculatorGraphTemplate& templ,
|
||||
CalculatorGraphConfig* output) {
|
||||
errors_.clear();
|
||||
TemplateExpanderImpl expander(&errors_);
|
||||
if (!expander.ExpandTemplates(args, templ, output)) {
|
||||
errors_.push_back(::mediapipe::InternalError("ExpandTemplates failed"));
|
||||
}
|
||||
::mediapipe::Status status;
|
||||
for (const ::mediapipe::Status& error : errors_) {
|
||||
LOG(ERROR) << error;
|
||||
status.Update(error);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_TEMPLATE_EXPANDER_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_TEMPLATE_EXPANDER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/tool/calculator_graph_template.pb.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
|
||||
// The interpreter for mediapipe template rules.
|
||||
class TemplateExpander {
|
||||
public:
|
||||
// Creates an interpreter to expand templates.
|
||||
TemplateExpander();
|
||||
|
||||
// Applies the rules specified in a CalculatorGraphTemplate to a
|
||||
// CalculatorGraphConfig. Each rule references a nested field-value or
|
||||
// message and defines zero or more replacement values for it.
|
||||
::mediapipe::Status ExpandTemplates(const TemplateDict& args,
|
||||
const CalculatorGraphTemplate& templ,
|
||||
CalculatorGraphConfig* output);
|
||||
|
||||
private:
|
||||
// List of errors found in template parameters.
|
||||
std::vector<::mediapipe::Status> errors_;
|
||||
};
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_TEMPLATE_EXPANDER_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,163 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_TEMPLATE_PARSER_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_TEMPLATE_PARSER_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/macros.h"
|
||||
#include "mediapipe/framework/port/advanced_proto_inc.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
|
||||
class TemplateParser {
|
||||
public:
|
||||
// A data structure for reporting source code locations.
|
||||
class ParseInfoTree;
|
||||
|
||||
// For parsing mediapipe templates, use this class.
|
||||
class Parser {
|
||||
public:
|
||||
Parser();
|
||||
~Parser();
|
||||
|
||||
// Like TextFormat::Parse().
|
||||
bool Parse(proto_ns::io::ZeroCopyInputStream* input,
|
||||
proto_ns::Message* output);
|
||||
// Like TextFormat::ParseFromString().
|
||||
bool ParseFromString(const std::string& input, proto_ns::Message* output);
|
||||
// Like TextFormat::Merge().
|
||||
bool Merge(proto_ns::io::ZeroCopyInputStream* input,
|
||||
proto_ns::Message* output);
|
||||
// Like TextFormat::MergeFromString().
|
||||
bool MergeFromString(const std::string& input, proto_ns::Message* output);
|
||||
|
||||
// Set where to report parse errors. If NULL (the default), errors will
|
||||
// be printed to stderr.
|
||||
void RecordErrorsTo(proto_ns::io::ErrorCollector* error_collector) {
|
||||
error_collector_ = error_collector;
|
||||
}
|
||||
|
||||
// Set how parser finds extensions. If NULL (the default), the
|
||||
// parser will use the standard Reflection object associated with
|
||||
// the message being parsed.
|
||||
void SetFinder(proto_ns::TextFormat::Finder* finder) { finder_ = finder; }
|
||||
|
||||
// Sets where location information about the parse will be written. If NULL
|
||||
// (the default), then no location will be written.
|
||||
void WriteLocationsTo(ParseInfoTree* tree) { parse_info_tree_ = tree; }
|
||||
|
||||
// Normally parsing fails if, after parsing, output->IsInitialized()
|
||||
// returns false. Call AllowPartialMessage(true) to skip this check.
|
||||
void AllowPartialMessage(bool allow) { allow_partial_ = allow; }
|
||||
|
||||
// Allow field names to be matched case-insensitively.
|
||||
// This is not advisable if there are fields that only differ in case, or
|
||||
// if you want to enforce writing in the canonical form.
|
||||
// This is 'false' by default.
|
||||
void AllowCaseInsensitiveField(bool allow) {
|
||||
allow_case_insensitive_field_ = allow;
|
||||
}
|
||||
|
||||
// Like TextFormat::ParseFieldValueFromString
|
||||
bool ParseFieldValueFromString(const std::string& input,
|
||||
const proto_ns::FieldDescriptor* field,
|
||||
proto_ns::Message* output);
|
||||
|
||||
void AllowFieldNumber(bool allow) { allow_field_number_ = allow; }
|
||||
|
||||
private:
|
||||
// A specialization of ParserImpl for parsing mediapipe template rules.
|
||||
class MediaPipeParserImpl;
|
||||
// The internal class used to parse proto text.
|
||||
class ParserImpl;
|
||||
|
||||
// Like TextFormat::Merge(). The provided implementation is used
|
||||
// to do the parsing.
|
||||
bool MergeUsingImpl(proto_ns::io::ZeroCopyInputStream* input,
|
||||
proto_ns::Message* output, ParserImpl* parser_impl);
|
||||
|
||||
proto_ns::io::ErrorCollector* error_collector_;
|
||||
proto_ns::TextFormat::Finder* finder_;
|
||||
ParseInfoTree* parse_info_tree_;
|
||||
bool allow_partial_;
|
||||
bool allow_case_insensitive_field_;
|
||||
bool allow_unknown_field_;
|
||||
bool allow_unknown_enum_;
|
||||
bool allow_field_number_;
|
||||
bool allow_relaxed_whitespace_;
|
||||
bool allow_singular_overwrites_;
|
||||
};
|
||||
};
|
||||
|
||||
// Data structure which is populated with the locations of each field
|
||||
// value parsed from the text.
|
||||
// Forked from
|
||||
class TemplateParser::ParseInfoTree {
|
||||
public:
|
||||
typedef proto_ns::TextFormat::ParseLocation ParseLocation;
|
||||
typedef proto_ns::FieldDescriptor FieldDescriptor;
|
||||
ParseInfoTree();
|
||||
ParseInfoTree(const ParseInfoTree&) = delete;
|
||||
ParseInfoTree& operator=(const ParseInfoTree&) = delete;
|
||||
~ParseInfoTree();
|
||||
|
||||
// Returns the parse location for index-th value of the field in the parsed
|
||||
// text. If none exists, returns a location with line = -1. Index should be
|
||||
// -1 for not-repeated fields.
|
||||
ParseLocation GetLocation(const FieldDescriptor* field, int index) const;
|
||||
|
||||
// Returns the parse info tree for the given field, which must be a message
|
||||
// type. The nested information tree is owned by the root tree and will be
|
||||
// deleted when it is deleted.
|
||||
ParseInfoTree* GetTreeForNested(const FieldDescriptor* field,
|
||||
int index) const;
|
||||
|
||||
// Records the starting location of a single value for a field.
|
||||
void RecordLocation(const FieldDescriptor* field, ParseLocation location);
|
||||
|
||||
// Create and records a nested tree for a nested message field.
|
||||
ParseInfoTree* CreateNested(const FieldDescriptor* field);
|
||||
|
||||
// Return the proto path for the last index for a field.
|
||||
std::string GetLastPath(const FieldDescriptor* field);
|
||||
|
||||
// Return the proto path for the current message.
|
||||
std::string GetPath();
|
||||
|
||||
private:
|
||||
// Defines the map from the index-th field descriptor to its parse location.
|
||||
typedef std::map<const FieldDescriptor*, std::vector<ParseLocation>>
|
||||
LocationMap;
|
||||
|
||||
// Defines the map from the index-th field descriptor to the nested parse
|
||||
// info tree.
|
||||
typedef std::map<const FieldDescriptor*,
|
||||
std::vector<std::unique_ptr<ParseInfoTree>>>
|
||||
NestedMap;
|
||||
|
||||
LocationMap locations_;
|
||||
NestedMap nested_;
|
||||
std::string path_;
|
||||
};
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_TEMPLATE_PARSER_H_
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_TYPE_UTIL_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_TYPE_UTIL_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <typeinfo>
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
// Helper method that returns a hash code of the given type. This allows for
|
||||
// typeid testing across multiple binaries, unlike FastTypeId which used a
|
||||
// memory location that only works within the same binary. Moreover, we use this
|
||||
// for supporting multiple .so binaries in a single Android app built using the
|
||||
// same compiler and C++ libraries.
|
||||
// Note that std::type_info may still generate the same hash code for different
|
||||
// types, although the c++ standard recommends that implementations avoid this
|
||||
// as much as possible.
|
||||
template <typename T>
|
||||
size_t GetTypeHash() {
|
||||
return typeid(T).hash_code();
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_TYPE_UTIL_H_
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/validate.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
#include "mediapipe/framework/port/canonical_errors.h"
|
||||
#include "mediapipe/framework/port/core_proto_inc.h"
|
||||
#include "mediapipe/framework/port/status_macros.h"
|
||||
#include "mediapipe/framework/tool/validate_name.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
|
||||
::mediapipe::Status ValidateInput(const InputCollection& input_collection) {
|
||||
if (!input_collection.name().empty()) {
|
||||
RETURN_IF_ERROR(tool::ValidateName(input_collection.name())).SetPrepend()
|
||||
<< "InputCollection " << input_collection.name()
|
||||
<< " has improperly specified name: ";
|
||||
}
|
||||
if (input_collection.input_type() <= InputCollection::UNKNOWN ||
|
||||
input_collection.input_type() >= InputCollection::INVALID_UPPER_BOUND) {
|
||||
return ::mediapipe::InvalidArgumentError(
|
||||
"InputCollection must specify a valid input_type.");
|
||||
}
|
||||
if (input_collection.file_name().empty()) {
|
||||
return ::mediapipe::InvalidArgumentError(
|
||||
"InputCollection must specify a file_name.");
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_VALIDATE_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_VALIDATE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
|
||||
// Returns ::mediapipe::OkStatus() if the InputCollection is valid. An input
|
||||
// collection is invalid if it does not have the proper fields set
|
||||
// depending on what its input_type field is. Furthermore, if it uses
|
||||
// INLINE, then the number of value fields in each inputs must match
|
||||
// the number of input_side_packet_name fields.
|
||||
::mediapipe::Status ValidateInput(const InputCollection& input);
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_VALIDATE_H_
|
||||
@@ -0,0 +1,254 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Definition of helper functions.
|
||||
#include "mediapipe/framework/tool/validate_name.h"
|
||||
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "mediapipe/framework/port/canonical_errors.h"
|
||||
#include "mediapipe/framework/port/core_proto_inc.h"
|
||||
#include "mediapipe/framework/port/integral_types.h"
|
||||
#include "mediapipe/framework/port/logging.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/port/source_location.h"
|
||||
#include "mediapipe/framework/port/status_builder.h"
|
||||
#include "mediapipe/framework/port/status_macros.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tool {
|
||||
#define MEDIAPIPE_NAME_REGEX "[a-z_][a-z0-9_]*"
|
||||
#define MEDIAPIPE_NUMBER_REGEX "(0|[1-9][0-9]*)"
|
||||
#define MEDIAPIPE_TAG_REGEX "[A-Z_][A-Z0-9_]*"
|
||||
#define MEDIAPIPE_TAG_AND_NAME_REGEX \
|
||||
"(" MEDIAPIPE_TAG_REGEX ":)?" MEDIAPIPE_NAME_REGEX
|
||||
#define MEDIAPIPE_TAG_INDEX_NAME_REGEX \
|
||||
"(" MEDIAPIPE_TAG_REGEX ":(" MEDIAPIPE_NUMBER_REGEX \
|
||||
":)?)?" MEDIAPIPE_NAME_REGEX
|
||||
#define MEDIAPIPE_TAG_INDEX_REGEX \
|
||||
"(" MEDIAPIPE_TAG_REGEX ")?(:" MEDIAPIPE_NUMBER_REGEX ")?"
|
||||
|
||||
::mediapipe::Status GetTagAndNameInfo(
|
||||
const proto_ns::RepeatedPtrField<ProtoString>& tags_and_names,
|
||||
TagAndNameInfo* info) {
|
||||
RET_CHECK(info);
|
||||
info->tags.clear();
|
||||
info->names.clear();
|
||||
for (const auto& tag_and_name : tags_and_names) {
|
||||
std::string tag;
|
||||
std::string name;
|
||||
RETURN_IF_ERROR(ParseTagAndName(tag_and_name, &tag, &name));
|
||||
if (!tag.empty()) {
|
||||
info->tags.push_back(tag);
|
||||
}
|
||||
info->names.push_back(name);
|
||||
}
|
||||
if (info->tags.size() > 0 && info->names.size() != info->tags.size()) {
|
||||
info->tags.clear();
|
||||
info->names.clear();
|
||||
return ::mediapipe::InvalidArgumentError(absl::StrCat(
|
||||
"Each set of names must use exclusively either tags or indexes. "
|
||||
"Encountered: \"",
|
||||
absl::StrJoin(tags_and_names, "\", \""), "\""));
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status SetFromTagAndNameInfo(
|
||||
const TagAndNameInfo& info,
|
||||
proto_ns::RepeatedPtrField<ProtoString>* tags_and_names) {
|
||||
tags_and_names->Clear();
|
||||
if (info.tags.empty()) {
|
||||
for (const auto& name : info.names) {
|
||||
RETURN_IF_ERROR(ValidateName(name));
|
||||
*tags_and_names->Add() = name;
|
||||
}
|
||||
} else {
|
||||
if (info.names.size() != info.tags.size()) {
|
||||
return ::mediapipe::InvalidArgumentErrorBuilder(MEDIAPIPE_LOC)
|
||||
<< "Number of tags " << info.names.size()
|
||||
<< " does not match the number of tags " << info.tags.size();
|
||||
}
|
||||
for (int i = 0; i < info.tags.size(); ++i) {
|
||||
RETURN_IF_ERROR(ValidateTag(info.tags[i]));
|
||||
RETURN_IF_ERROR(ValidateName(info.names[i]));
|
||||
*tags_and_names->Add() = absl::StrCat(info.tags[i], ":", info.names[i]);
|
||||
}
|
||||
}
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status ValidateName(const std::string& name) {
|
||||
return name.length() > 0 && (name[0] == '_' || islower(name[0])) &&
|
||||
std::all_of(name.begin() + 1, name.end(),
|
||||
[](char c) {
|
||||
return c == '_' || isdigit(c) || islower(c);
|
||||
})
|
||||
? ::mediapipe::OkStatus()
|
||||
: ::mediapipe::InvalidArgumentError(absl::StrCat(
|
||||
"Name \"", absl::CEscape(name),
|
||||
"\" does not match \"" MEDIAPIPE_NAME_REGEX "\"."));
|
||||
}
|
||||
|
||||
::mediapipe::Status ValidateNumber(const std::string& number) {
|
||||
return (number.length() == 1 && isdigit(number[0])) ||
|
||||
(number.length() > 1 && isdigit(number[0]) &&
|
||||
number[0] != '0' &&
|
||||
std::all_of(number.begin() + 1, number.end(),
|
||||
[](char c) { return isdigit(c); }))
|
||||
? ::mediapipe::OkStatus()
|
||||
: ::mediapipe::InvalidArgumentError(absl::StrCat(
|
||||
"Number \"", absl::CEscape(number),
|
||||
"\" does not match \"" MEDIAPIPE_NUMBER_REGEX "\"."));
|
||||
}
|
||||
|
||||
::mediapipe::Status ValidateTag(const std::string& tag) {
|
||||
return tag.length() > 0 && (tag[0] == '_' || isupper(tag[0])) &&
|
||||
std::all_of(tag.begin() + 1, tag.end(),
|
||||
[](char c) {
|
||||
return c == '_' || isdigit(c) || isupper(c);
|
||||
})
|
||||
? ::mediapipe::OkStatus()
|
||||
: ::mediapipe::InvalidArgumentError(absl::StrCat(
|
||||
"Tag \"", absl::CEscape(tag),
|
||||
"\" does not match \"" MEDIAPIPE_TAG_REGEX "\"."));
|
||||
}
|
||||
|
||||
::mediapipe::Status ParseTagAndName(const std::string& tag_and_name,
|
||||
std::string* tag, std::string* name) {
|
||||
// An optional tag and colon, followed by a name.
|
||||
RET_CHECK(tag);
|
||||
RET_CHECK(name);
|
||||
::mediapipe::Status tag_status = ::mediapipe::OkStatus();
|
||||
::mediapipe::Status name_status = ::mediapipe::UnknownError("");
|
||||
int name_index = 0;
|
||||
std::vector<std::string> v = absl::StrSplit(tag_and_name, ':');
|
||||
if (v.size() == 1) {
|
||||
name_status = ValidateName(v[0]);
|
||||
name_index = 0;
|
||||
} else if (v.size() == 2) {
|
||||
tag_status = ValidateTag(v[0]);
|
||||
name_status = ValidateName(v[1]);
|
||||
name_index = 1;
|
||||
}
|
||||
if (name_index == -1 || tag_status != ::mediapipe::OkStatus() ||
|
||||
name_status != ::mediapipe::OkStatus()) {
|
||||
tag->clear();
|
||||
name->clear();
|
||||
return ::mediapipe::InvalidArgumentError(
|
||||
absl::StrCat("\"tag and name\" is invalid, \"", tag_and_name,
|
||||
"\" does not match "
|
||||
"\"" MEDIAPIPE_TAG_AND_NAME_REGEX
|
||||
"\" (examples: \"TAG:name\", \"longer_name\")."));
|
||||
}
|
||||
*tag = name_index == 1 ? v[0] : "";
|
||||
*name = v[name_index];
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status ParseTagIndexName(const std::string& tag_index_name,
|
||||
std::string* tag, int* index,
|
||||
std::string* name) {
|
||||
// An optional tag and colon, an optional index and color, followed by a name.
|
||||
RET_CHECK(tag);
|
||||
RET_CHECK(index);
|
||||
RET_CHECK(name);
|
||||
|
||||
::mediapipe::Status tag_status = ::mediapipe::OkStatus();
|
||||
::mediapipe::Status number_status = ::mediapipe::OkStatus();
|
||||
::mediapipe::Status name_status = ::mediapipe::UnknownError("");
|
||||
int name_index = -1;
|
||||
int the_index = 0;
|
||||
std::vector<std::string> v = absl::StrSplit(tag_index_name, ':');
|
||||
if (v.size() == 1) {
|
||||
name_status = ValidateName(v[0]);
|
||||
the_index = -1;
|
||||
name_index = 0;
|
||||
} else if (v.size() == 2) {
|
||||
tag_status = ValidateTag(v[0]);
|
||||
name_status = ValidateName(v[1]);
|
||||
name_index = 1;
|
||||
} else if (v.size() == 3) {
|
||||
tag_status = ValidateTag(v[0]);
|
||||
number_status = ValidateNumber(v[1]);
|
||||
if (number_status.ok()) {
|
||||
int64 index64;
|
||||
RET_CHECK(absl::SimpleAtoi(v[1], &index64));
|
||||
RET_CHECK_LE(index64, internal::kMaxCollectionItemId);
|
||||
the_index = index64;
|
||||
}
|
||||
name_status = ValidateName(v[2]);
|
||||
name_index = 2;
|
||||
} // else omitted, name_index == -1, triggering error.
|
||||
if (name_index == -1 || !tag_status.ok() || !number_status.ok() ||
|
||||
!name_status.ok()) {
|
||||
return ::mediapipe::InvalidArgumentError(absl::StrCat(
|
||||
"TAG:index:name is invalid, \"", tag_index_name,
|
||||
"\" does not match "
|
||||
"\"" MEDIAPIPE_TAG_INDEX_NAME_REGEX
|
||||
"\" (examples: \"TAG:name\" \"VIDEO:2:name_b\", \"longer_name\")."));
|
||||
}
|
||||
*tag = name_index != 0 ? v[0] : "";
|
||||
*index = the_index;
|
||||
*name = v[name_index];
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
::mediapipe::Status ParseTagIndex(const std::string& tag_index,
|
||||
std::string* tag, int* index) {
|
||||
RET_CHECK(tag);
|
||||
RET_CHECK(index);
|
||||
|
||||
::mediapipe::Status tag_status = ::mediapipe::OkStatus();
|
||||
::mediapipe::Status number_status = ::mediapipe::OkStatus();
|
||||
int the_index = -1;
|
||||
std::vector<std::string> v = absl::StrSplit(tag_index, ':');
|
||||
if (v.size() == 1) {
|
||||
if (!v[0].empty()) {
|
||||
tag_status = ValidateTag(v[0]);
|
||||
}
|
||||
the_index = 0;
|
||||
} else if (v.size() == 2) {
|
||||
if (!v[0].empty()) {
|
||||
tag_status = ValidateTag(v[0]);
|
||||
}
|
||||
number_status = ValidateNumber(v[1]);
|
||||
if (number_status.ok()) {
|
||||
int64 index64;
|
||||
RET_CHECK(absl::SimpleAtoi(v[1], &index64));
|
||||
RET_CHECK_LE(index64, internal::kMaxCollectionItemId);
|
||||
the_index = index64;
|
||||
}
|
||||
} // else omitted, the_index == -1, triggering error.
|
||||
if (the_index == -1 || !tag_status.ok() || !number_status.ok()) {
|
||||
return ::mediapipe::InvalidArgumentError(absl::StrCat(
|
||||
"TAG:index is invalid, \"", tag_index,
|
||||
"\" does not match "
|
||||
"\"" MEDIAPIPE_TAG_INDEX_REGEX "\" (examples: \"TAG\" \"VIDEO:2\")."));
|
||||
}
|
||||
*tag = v[0];
|
||||
*index = the_index;
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
#undef MEDIAPIPE_NAME_REGEX
|
||||
#undef MEDIAPIPE_TAG_REGEX
|
||||
#undef MEDIAPIPE_TAG_AND_NAME_REGEX
|
||||
#undef MEDIAPIPE_TAG_INDEX_NAME_REGEX
|
||||
#undef MEDIAPIPE_TAG_INDEX_REGEX
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Defines functions for validating and parsing tags and stream names
|
||||
// (and side packet names).
|
||||
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_VALIDATE_NAME_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_VALIDATE_NAME_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/base/macros.h"
|
||||
#include "mediapipe/framework/port/proto_ns.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace internal {
|
||||
constexpr int kMaxCollectionItemId = 10000;
|
||||
} // namespace internal
|
||||
|
||||
namespace tool {
|
||||
|
||||
struct ABSL_DEPRECATED(
|
||||
"Prefer using mediapipe::tool::TagMap instead, since this structure does "
|
||||
"not "
|
||||
"consider the TAG:INDEX:name notation.") TagAndNameInfo {
|
||||
// The tag names. If this is empty then the collection will use
|
||||
// indexes.
|
||||
std::vector<std::string> tags;
|
||||
// Names of the streams or side packets. If tags are used then
|
||||
// tags.size() and names.size() must match.
|
||||
std::vector<std::string> names;
|
||||
};
|
||||
|
||||
// Create a TagAndNameInfo from a list of strings in the form TAG:name.
|
||||
// The TAG (including colon) is optional, but the entire list must either
|
||||
// always include tags or never include tags.
|
||||
ABSL_DEPRECATED(
|
||||
"Prefer using mediapipe::tool::TagMap instead, since this method does not "
|
||||
"support the TAG:INDEX:name notation. You can use Create() to create the "
|
||||
"tag map, and then Names(), Mapping(), and other methods to access the "
|
||||
"tag, index and name information.")
|
||||
::mediapipe::Status GetTagAndNameInfo(
|
||||
const proto_ns::RepeatedPtrField<ProtoString>& tags_and_names,
|
||||
TagAndNameInfo* info);
|
||||
|
||||
// Create the proto field names in the form TAG:name based on a
|
||||
// TagAndNameInfo.
|
||||
ABSL_DEPRECATED(
|
||||
"Prefer using mediapipe::tool::TagMap instead, since this method does not "
|
||||
"support the TAG:INDEX:name notation. You can use CanonicalEntries() to "
|
||||
"translate a tag map to a RepeatedPtrField of tag and names.")
|
||||
::mediapipe::Status SetFromTagAndNameInfo(
|
||||
const TagAndNameInfo& info,
|
||||
proto_ns::RepeatedPtrField<ProtoString>* tags_and_names);
|
||||
|
||||
// The std::string is a valid name for an input stream, output stream,
|
||||
// side packet, and input collection. Names use only lower case letters,
|
||||
// numbers, and underscores.
|
||||
//
|
||||
// The reason for this restriction is threefold.
|
||||
// (1) To enforce a consistent style in graph configs.
|
||||
// (2) To distinguish between "arguments" to calculators and
|
||||
// trainer/calculator names.
|
||||
// (3) Because input side packet names end up in model directory names,
|
||||
// where lower case naming is the norm.
|
||||
::mediapipe::Status ValidateName(const std::string& name);
|
||||
// The std::string is a valid tag name. Tags use only upper case letters,
|
||||
// numbers, and underscores.
|
||||
::mediapipe::Status ValidateTag(const std::string& tag);
|
||||
|
||||
// Parse a "Tag and Name" std::string into a tag and a name.
|
||||
// The format is an optional tag and colon, followed by a name.
|
||||
// Example 1: "VIDEO:frames2" -> tag: "VIDEO", name: "frames2"
|
||||
// Example 2: "video_frames_1" -> tag: "", name: "video_frames_1"
|
||||
::mediapipe::Status ParseTagAndName(const std::string& tag_and_name,
|
||||
std::string* tag, std::string* name);
|
||||
|
||||
// Parse a generic TAG:index:name std::string. The format is a tag, then an
|
||||
// index, then a name. The tag and index are optional. If the index
|
||||
// is included, then the tag must be included. If no tag is used then
|
||||
// index is set to -1 (and should be assigned by argument position).
|
||||
// Examples:
|
||||
// "VIDEO:frames2" -> tag: "VIDEO", index: 0, name: "frames2"
|
||||
// "VIDEO:1:frames" -> tag: "VIDEO", index: 1, name: "frames"
|
||||
// "raw_frames" -> tag: "", index: -1, name: "raw_frames"
|
||||
::mediapipe::Status ParseTagIndexName(const std::string& tag_and_name,
|
||||
std::string* tag, int* index,
|
||||
std::string* name);
|
||||
|
||||
// Parse a generic TAG:index std::string. The format is a tag, then an index
|
||||
// with both being optional. If the tag is missing it is assumed to be
|
||||
// "" and if the index is missing then it is assumed to be 0. If the
|
||||
// index is provided then a colon (':') must be used.
|
||||
// Examples:
|
||||
// "VIDEO" -> tag: "VIDEO", index: 0
|
||||
// "VIDEO:1" -> tag: "VIDEO", index: 1
|
||||
// ":2" -> tag: "", index: 2
|
||||
// "" -> tag: "", index: 0
|
||||
::mediapipe::Status ParseTagIndex(const std::string& tag_and_index,
|
||||
std::string* tag, int* index);
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_VALIDATE_NAME_H_
|
||||
@@ -0,0 +1,480 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mediapipe/framework/tool/validate_name.h"
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/substitute.h"
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
#include "mediapipe/framework/deps/message_matchers.h"
|
||||
#include "mediapipe/framework/port/gmock.h"
|
||||
#include "mediapipe/framework/port/gtest.h"
|
||||
#include "mediapipe/framework/port/status_matchers.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(ValidateNameTest, ValidateName) {
|
||||
MEDIAPIPE_EXPECT_OK(tool::ValidateName("humphrey"));
|
||||
MEDIAPIPE_EXPECT_OK(tool::ValidateName("humphrey_bogart"));
|
||||
MEDIAPIPE_EXPECT_OK(tool::ValidateName("humphrey_bogart_1899"));
|
||||
MEDIAPIPE_EXPECT_OK(tool::ValidateName("aa"));
|
||||
MEDIAPIPE_EXPECT_OK(tool::ValidateName("b1"));
|
||||
MEDIAPIPE_EXPECT_OK(tool::ValidateName("_1"));
|
||||
EXPECT_FALSE(tool::ValidateName("").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("humphrey bogart").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("humphreyBogart").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("humphrey-bogart").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("humphrey/bogart").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("humphrey.bogart").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("humphrey:bogart").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("1ST").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("7_ELEVEN").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("401K").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("0").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("1").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("11").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("92091").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("1st").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("7_eleven").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("401k").ok());
|
||||
EXPECT_FALSE(tool::ValidateName("\0ContainsEscapes\t").ok());
|
||||
}
|
||||
|
||||
TEST(ValidateNameTest, ValidateTag) {
|
||||
MEDIAPIPE_EXPECT_OK(tool::ValidateTag("MALE"));
|
||||
MEDIAPIPE_EXPECT_OK(tool::ValidateTag("MALE_ACTOR"));
|
||||
MEDIAPIPE_EXPECT_OK(tool::ValidateTag("ACTOR_1899"));
|
||||
MEDIAPIPE_EXPECT_OK(tool::ValidateTag("AA"));
|
||||
MEDIAPIPE_EXPECT_OK(tool::ValidateTag("B1"));
|
||||
MEDIAPIPE_EXPECT_OK(tool::ValidateTag("_1"));
|
||||
EXPECT_FALSE(tool::ValidateTag("").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("MALE ACTOR").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("MALEaCTOR").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("MALE-ACTOR").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("MALE/ACTOR").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("MALE.ACTOR").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("MALE:ACTOR").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("0").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("1").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("11").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("92091").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("1ST").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("7_ELEVEN").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("401K").ok());
|
||||
EXPECT_FALSE(tool::ValidateTag("\0ContainsEscapes\t").ok());
|
||||
}
|
||||
|
||||
TEST(ValidateNameTest, ParseTagAndName) {
|
||||
std::string tag;
|
||||
std::string name;
|
||||
tag = "blah";
|
||||
name = "blah";
|
||||
MEDIAPIPE_EXPECT_OK(tool::ParseTagAndName("MALE:humphrey", &tag, &name));
|
||||
EXPECT_EQ("MALE", tag);
|
||||
EXPECT_EQ("humphrey", name);
|
||||
tag = "blah";
|
||||
name = "blah";
|
||||
MEDIAPIPE_EXPECT_OK(
|
||||
tool::ParseTagAndName("ACTOR:humphrey_bogart", &tag, &name));
|
||||
EXPECT_EQ("ACTOR", tag);
|
||||
EXPECT_EQ("humphrey_bogart", name);
|
||||
tag = "blah";
|
||||
name = "blah";
|
||||
MEDIAPIPE_EXPECT_OK(
|
||||
tool::ParseTagAndName("ACTOR_1899:humphrey_1899", &tag, &name));
|
||||
EXPECT_EQ("ACTOR_1899", tag);
|
||||
EXPECT_EQ("humphrey_1899", name);
|
||||
tag = "blah";
|
||||
name = "blah";
|
||||
MEDIAPIPE_EXPECT_OK(tool::ParseTagAndName("humphrey_bogart", &tag, &name));
|
||||
EXPECT_EQ("", tag);
|
||||
EXPECT_EQ("humphrey_bogart", name);
|
||||
|
||||
tag = "blah";
|
||||
name = "blah";
|
||||
EXPECT_FALSE(tool::ParseTagAndName(":humphrey", &tag, &name).ok());
|
||||
EXPECT_EQ("", tag);
|
||||
EXPECT_EQ("", name);
|
||||
|
||||
tag = "blah";
|
||||
name = "blah";
|
||||
EXPECT_FALSE(tool::ParseTagAndName("humphrey bogart", &tag, &name).ok());
|
||||
EXPECT_EQ("", tag);
|
||||
EXPECT_EQ("", name);
|
||||
|
||||
tag = "blah";
|
||||
name = "blah";
|
||||
EXPECT_FALSE(tool::ParseTagAndName("actor:humphrey", &tag, &name).ok());
|
||||
EXPECT_EQ("", tag);
|
||||
EXPECT_EQ("", name);
|
||||
tag = "blah";
|
||||
name = "blah";
|
||||
MEDIAPIPE_EXPECT_OK(tool::ParseTagAndName("ACTOR:humphrey", &tag, &name));
|
||||
EXPECT_EQ("ACTOR", tag);
|
||||
EXPECT_EQ("humphrey", name);
|
||||
|
||||
tag = "blah";
|
||||
name = "blah";
|
||||
EXPECT_FALSE(tool::ParseTagAndName("ACTOR:HUMPHREY", &tag, &name).ok());
|
||||
EXPECT_EQ("", tag);
|
||||
EXPECT_EQ("", name);
|
||||
|
||||
tag = "blah";
|
||||
name = "blah";
|
||||
EXPECT_FALSE(tool::ParseTagAndName("MALE:ACTOR:humphrey", &tag, &name).ok());
|
||||
EXPECT_EQ("", tag);
|
||||
EXPECT_EQ("", name);
|
||||
|
||||
// Test various bad characters.
|
||||
for (std::string character : {" ", "-", "/", ".", ":"}) {
|
||||
tag = "blah";
|
||||
name = "blah";
|
||||
EXPECT_FALSE(
|
||||
tool::ParseTagAndName(absl::StrCat("MALE", character, "ACTOR:humphrey"),
|
||||
&tag, &name)
|
||||
.ok());
|
||||
EXPECT_EQ("", tag);
|
||||
EXPECT_EQ("", name);
|
||||
tag = "blah";
|
||||
name = "blah";
|
||||
EXPECT_FALSE(
|
||||
tool::ParseTagAndName(
|
||||
absl::StrCat("ACTOR:humphrey", character, "bogart"), &tag, &name)
|
||||
.ok());
|
||||
EXPECT_EQ("", tag);
|
||||
EXPECT_EQ("", name);
|
||||
}
|
||||
}
|
||||
|
||||
void TestPassParseTagIndexName(const std::string& tag_index_name,
|
||||
const std::string& expected_tag,
|
||||
const int expected_index,
|
||||
const std::string& expected_name) {
|
||||
std::string actual_tag = "UNTOUCHED";
|
||||
int actual_index = -100;
|
||||
std::string actual_name = "untouched";
|
||||
MEDIAPIPE_ASSERT_OK(tool::ParseTagIndexName(tag_index_name, &actual_tag,
|
||||
&actual_index, &actual_name))
|
||||
<< "With tag_index_name " << tag_index_name;
|
||||
EXPECT_EQ(expected_tag, actual_tag)
|
||||
<< "With tag_index_name " << tag_index_name;
|
||||
EXPECT_EQ(expected_index, actual_index)
|
||||
<< "With tag_index_name " << tag_index_name;
|
||||
EXPECT_EQ(expected_name, actual_name)
|
||||
<< "With tag_index_name " << tag_index_name;
|
||||
}
|
||||
|
||||
void TestFailParseTagIndexName(const std::string& tag_index_name) {
|
||||
std::string actual_tag = "UNTOUCHED";
|
||||
int actual_index = -100;
|
||||
std::string actual_name = "untouched";
|
||||
ASSERT_FALSE(tool::ParseTagIndexName(tag_index_name, &actual_tag,
|
||||
&actual_index, &actual_name)
|
||||
.ok())
|
||||
<< "With tag_index_name " << tag_index_name;
|
||||
EXPECT_EQ("UNTOUCHED", actual_tag)
|
||||
<< "With tag_index_name " << tag_index_name;
|
||||
EXPECT_EQ(-100, actual_index) << "With tag_index_name " << tag_index_name;
|
||||
EXPECT_EQ("untouched", actual_name)
|
||||
<< "With tag_index_name " << tag_index_name;
|
||||
}
|
||||
|
||||
TEST(ValidateNameTest, ParseTagIndexName) {
|
||||
// Success cases.
|
||||
// Test with tag.
|
||||
TestPassParseTagIndexName("MALE:humphrey", "MALE", 0, "humphrey");
|
||||
TestPassParseTagIndexName("ACTOR:humphrey_bogart", "ACTOR", 0,
|
||||
"humphrey_bogart");
|
||||
TestPassParseTagIndexName("ACTOR_1899:humphrey_1899", "ACTOR_1899", 0,
|
||||
"humphrey_1899");
|
||||
// Test without tag.
|
||||
TestPassParseTagIndexName("humphrey_bogart", "", -1, "humphrey_bogart");
|
||||
// Test with index.
|
||||
TestPassParseTagIndexName("ACTRESS:3:mieko_harada", "ACTRESS", 3,
|
||||
"mieko_harada");
|
||||
TestPassParseTagIndexName("ACTRESS:0:mieko_harada", "ACTRESS", 0,
|
||||
"mieko_harada");
|
||||
TestPassParseTagIndexName("A1:100:mieko1", "A1", 100, "mieko1");
|
||||
TestPassParseTagIndexName(
|
||||
absl::StrCat("A1:", ::mediapipe::internal::kMaxCollectionItemId,
|
||||
":mieko1"),
|
||||
"A1", ::mediapipe::internal::kMaxCollectionItemId, "mieko1");
|
||||
|
||||
// Failure cases.
|
||||
TestFailParseTagIndexName(""); // Empty name.
|
||||
TestFailParseTagIndexName("A"); // Upper case name.
|
||||
TestFailParseTagIndexName("Aa"); // Upper case name.
|
||||
TestFailParseTagIndexName("aA"); // Upper case name.
|
||||
TestFailParseTagIndexName("1a"); // Name starts with number.
|
||||
TestFailParseTagIndexName("1"); // Name is number.
|
||||
// With tag.
|
||||
TestFailParseTagIndexName(":name"); // Missing tag.
|
||||
TestFailParseTagIndexName("A:"); // Missing name.
|
||||
TestFailParseTagIndexName("a:name"); // Lower case tag.
|
||||
TestFailParseTagIndexName("Aa:name"); // Lower case tag.
|
||||
TestFailParseTagIndexName("aA:name"); // Lower case tag.
|
||||
TestFailParseTagIndexName("1A:name"); // Tag starts with number.
|
||||
TestFailParseTagIndexName("1:name"); // Tag is number.
|
||||
// With index.
|
||||
TestFailParseTagIndexName("1:name"); // Missing tag.
|
||||
TestFailParseTagIndexName(":1:name"); // Missing tag.
|
||||
TestFailParseTagIndexName("A:1:"); // Missing name.
|
||||
TestFailParseTagIndexName("A::name"); // Missing index.
|
||||
TestFailParseTagIndexName("a:1:name"); // Lower case tag.
|
||||
TestFailParseTagIndexName("Aa:1:name"); // Lower case tag.
|
||||
TestFailParseTagIndexName("aA:1:name"); // Lower case tag.
|
||||
TestFailParseTagIndexName("1A:1:name"); // Tag starts with number.
|
||||
TestFailParseTagIndexName("1:1:name"); // Tag is number.
|
||||
TestFailParseTagIndexName("A:1:N"); // Upper case name.
|
||||
TestFailParseTagIndexName("A:1:nN"); // Upper case name.
|
||||
TestFailParseTagIndexName("A:1:Nn"); // Upper case name.
|
||||
TestFailParseTagIndexName("A:1:1name"); // Name starts with number.
|
||||
TestFailParseTagIndexName("A:1:1"); // Name is number.
|
||||
TestFailParseTagIndexName("A:-0:name"); // Negative index.
|
||||
TestFailParseTagIndexName("A:-1:name"); // Negative index.
|
||||
TestFailParseTagIndexName("A:01:name"); // Leading zero.
|
||||
TestFailParseTagIndexName("A:00:name"); // Leading zero.
|
||||
TestFailParseTagIndexName(
|
||||
absl::StrCat("A:", ::mediapipe::internal::kMaxCollectionItemId + 1,
|
||||
":a")); // Too large an index.
|
||||
// Extra field
|
||||
TestFailParseTagIndexName("A:1:a:"); // extra field.
|
||||
TestFailParseTagIndexName(":A:1:a"); // extra field.
|
||||
TestFailParseTagIndexName("A:1:a:a"); // extra field.
|
||||
TestFailParseTagIndexName("A:1:a:A"); // extra field.
|
||||
TestFailParseTagIndexName("A:1:a:1"); // extra field.
|
||||
|
||||
// Test various bad characters.
|
||||
for (char character : {'!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
|
||||
'{', '}', '[', ']', '/', '=', '?', '+', '\\', '|',
|
||||
'-', ';', ':', '\'', '"', ',', '<', '.', '>'}) {
|
||||
TestFailParseTagIndexName(absl::Substitute("$0", character));
|
||||
TestFailParseTagIndexName(absl::Substitute("$0a", character));
|
||||
TestFailParseTagIndexName(absl::Substitute("a$0", character));
|
||||
TestFailParseTagIndexName(absl::Substitute("$0:a", character));
|
||||
TestFailParseTagIndexName(absl::Substitute("A$0:a", character));
|
||||
TestFailParseTagIndexName(absl::Substitute("$0A:a", character));
|
||||
TestFailParseTagIndexName(absl::Substitute("A:$0:a", character));
|
||||
TestFailParseTagIndexName(absl::Substitute("A:$01:a", character));
|
||||
TestFailParseTagIndexName(absl::Substitute("A:1$0:a", character));
|
||||
TestFailParseTagIndexName(absl::Substitute("A:1:a$0", character));
|
||||
TestFailParseTagIndexName(absl::Substitute("$0A:1:a", character));
|
||||
}
|
||||
}
|
||||
|
||||
void TestPassParseTagIndex(const std::string& tag_index,
|
||||
const std::string& expected_tag,
|
||||
const int expected_index) {
|
||||
std::string actual_tag = "UNTOUCHED";
|
||||
int actual_index = -100;
|
||||
MEDIAPIPE_ASSERT_OK(
|
||||
tool::ParseTagIndex(tag_index, &actual_tag, &actual_index))
|
||||
<< "With tag_index" << tag_index;
|
||||
EXPECT_EQ(expected_tag, actual_tag) << "With tag_index " << tag_index;
|
||||
EXPECT_EQ(expected_index, actual_index) << "With tag_index " << tag_index;
|
||||
}
|
||||
|
||||
void TestFailParseTagIndex(const std::string& tag_index) {
|
||||
std::string actual_tag = "UNTOUCHED";
|
||||
int actual_index = -100;
|
||||
ASSERT_FALSE(tool::ParseTagIndex(tag_index, &actual_tag, &actual_index).ok())
|
||||
<< "With tag_index " << tag_index;
|
||||
EXPECT_EQ("UNTOUCHED", actual_tag) << "With tag_index " << tag_index;
|
||||
EXPECT_EQ(-100, actual_index) << "With tag_index " << tag_index;
|
||||
}
|
||||
|
||||
TEST(ValidateNameTest, ParseTagIndex) {
|
||||
// Success cases.
|
||||
TestPassParseTagIndex("", "", 0);
|
||||
TestPassParseTagIndex("VIDEO:0", "VIDEO", 0);
|
||||
TestPassParseTagIndex("VIDEO:1", "VIDEO", 1);
|
||||
TestPassParseTagIndex("AUDIO:2", "AUDIO", 2);
|
||||
TestPassParseTagIndex(":0", "", 0);
|
||||
TestPassParseTagIndex(":1", "", 1);
|
||||
TestPassParseTagIndex(":100", "", 100);
|
||||
|
||||
// Failure cases.
|
||||
TestFailParseTagIndex("a"); // Lower case tag.
|
||||
TestFailParseTagIndex("Aa"); // Lower case tag.
|
||||
TestFailParseTagIndex("aA"); // Lower case tag.
|
||||
TestFailParseTagIndex("1A"); // tag starts with number.
|
||||
TestFailParseTagIndex("1"); // tag is number.
|
||||
// Two fields.
|
||||
TestFailParseTagIndex(":"); // Missing number.
|
||||
TestFailParseTagIndex(":a"); // lower case number.
|
||||
TestFailParseTagIndex(":A"); // upper case number.
|
||||
TestFailParseTagIndex(":-0"); // Negative index.
|
||||
TestFailParseTagIndex(":-1"); // Negative index.
|
||||
TestFailParseTagIndex(":01"); // Leading zero.
|
||||
TestFailParseTagIndex(":00"); // Leading zero.
|
||||
TestFailParseTagIndex("A:"); // Missing number.
|
||||
TestFailParseTagIndex("A:a"); // lower case number.
|
||||
TestFailParseTagIndex("A:A"); // upper case number.
|
||||
TestFailParseTagIndex("A:-0"); // Negative index.
|
||||
TestFailParseTagIndex("A:-1"); // Negative index.
|
||||
TestFailParseTagIndex("A:01"); // Leading zero.
|
||||
TestFailParseTagIndex("A:00"); // Leading zero.
|
||||
// Extra field
|
||||
TestFailParseTagIndex("A:1:"); // extra field.
|
||||
TestFailParseTagIndex(":A:1"); // extra field.
|
||||
TestFailParseTagIndex("A:1:2"); // extra field.
|
||||
TestFailParseTagIndex("A:A:1"); // extra field.
|
||||
|
||||
// Test various bad characters.
|
||||
for (char character : {'!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
|
||||
'{', '}', '[', ']', '/', '=', '?', '+', '\\', '|',
|
||||
'-', ';', ':', '\'', '"', ',', '<', '.', '>'}) {
|
||||
TestFailParseTagIndex(absl::Substitute("$0", character));
|
||||
TestFailParseTagIndex(absl::Substitute("$0A", character));
|
||||
TestFailParseTagIndex(absl::Substitute("A$0", character));
|
||||
TestFailParseTagIndex(absl::Substitute("$0:1", character));
|
||||
TestFailParseTagIndex(absl::Substitute("A$0:1", character));
|
||||
TestFailParseTagIndex(absl::Substitute("$0A:1", character));
|
||||
TestFailParseTagIndex(absl::Substitute("A:1$0", character));
|
||||
TestFailParseTagIndex(absl::Substitute("A:$01", character));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ValidateNameTest, GetTagAndNameInfo) {
|
||||
CalculatorGraphConfig::Node node_config1;
|
||||
CalculatorGraphConfig::Node node_config2;
|
||||
proto_ns::RepeatedPtrField<std::string>& fields =
|
||||
*node_config1.mutable_input_stream();
|
||||
proto_ns::RepeatedPtrField<std::string>& fields_copy =
|
||||
*node_config2.mutable_input_stream();
|
||||
|
||||
// Single input using indexes.
|
||||
fields.Clear();
|
||||
fields.Add()->assign("transcoded_input_file");
|
||||
tool::TagAndNameInfo info;
|
||||
MEDIAPIPE_ASSERT_OK(tool::GetTagAndNameInfo(fields, &info));
|
||||
ASSERT_EQ(0, info.tags.size());
|
||||
ASSERT_EQ(1, info.names.size());
|
||||
EXPECT_EQ(fields.Get(0), info.names[0]);
|
||||
MEDIAPIPE_ASSERT_OK(tool::SetFromTagAndNameInfo(info, &fields_copy));
|
||||
EXPECT_THAT(node_config2, EqualsProto(node_config1));
|
||||
|
||||
// Single input using tags.
|
||||
fields.Clear();
|
||||
fields.Add()->assign("FILE:transcoded_input_file");
|
||||
MEDIAPIPE_ASSERT_OK(tool::GetTagAndNameInfo(fields, &info));
|
||||
ASSERT_EQ(1, info.tags.size());
|
||||
ASSERT_EQ(1, info.names.size());
|
||||
EXPECT_EQ("FILE", info.tags[0]);
|
||||
EXPECT_EQ("transcoded_input_file", info.names[0]);
|
||||
MEDIAPIPE_ASSERT_OK(tool::SetFromTagAndNameInfo(info, &fields_copy));
|
||||
EXPECT_THAT(node_config2, EqualsProto(node_config1));
|
||||
|
||||
// Mixing indexes and tags.
|
||||
fields.Clear();
|
||||
fields.Add()->assign("transcoded_input_file");
|
||||
fields.Add()->assign("FILE:transcoded_input_file");
|
||||
ASSERT_FALSE(tool::GetTagAndNameInfo(fields, &info).ok());
|
||||
|
||||
// Valid configuration with more than one input using tags.
|
||||
fields.Clear();
|
||||
fields.Add()->assign("TAG1:input1");
|
||||
fields.Add()->assign("TAG2:input2");
|
||||
fields.Add()->assign("TAG3:input3");
|
||||
fields.Add()->assign("TAG4:input4");
|
||||
MEDIAPIPE_ASSERT_OK(tool::GetTagAndNameInfo(fields, &info));
|
||||
ASSERT_EQ(4, info.tags.size());
|
||||
ASSERT_EQ(4, info.names.size());
|
||||
EXPECT_EQ("TAG1", info.tags[0]);
|
||||
EXPECT_EQ("TAG2", info.tags[1]);
|
||||
EXPECT_EQ("TAG3", info.tags[2]);
|
||||
EXPECT_EQ("TAG4", info.tags[3]);
|
||||
EXPECT_EQ("input1", info.names[0]);
|
||||
EXPECT_EQ("input2", info.names[1]);
|
||||
EXPECT_EQ("input3", info.names[2]);
|
||||
EXPECT_EQ("input4", info.names[3]);
|
||||
MEDIAPIPE_ASSERT_OK(tool::SetFromTagAndNameInfo(info, &fields_copy));
|
||||
EXPECT_THAT(node_config2, EqualsProto(node_config1));
|
||||
|
||||
// Valid configuration with more than one input using indexes.
|
||||
fields.Clear();
|
||||
fields.Add()->assign("input1");
|
||||
fields.Add()->assign("input2");
|
||||
fields.Add()->assign("input3");
|
||||
fields.Add()->assign("input4");
|
||||
MEDIAPIPE_ASSERT_OK(tool::GetTagAndNameInfo(fields, &info));
|
||||
ASSERT_EQ(0, info.tags.size());
|
||||
ASSERT_EQ(4, info.names.size());
|
||||
EXPECT_EQ("input1", info.names[0]);
|
||||
EXPECT_EQ("input2", info.names[1]);
|
||||
EXPECT_EQ("input3", info.names[2]);
|
||||
EXPECT_EQ("input4", info.names[3]);
|
||||
MEDIAPIPE_ASSERT_OK(tool::SetFromTagAndNameInfo(info, &fields_copy));
|
||||
EXPECT_THAT(node_config2, EqualsProto(node_config1));
|
||||
|
||||
// Add an invalid character into the name.
|
||||
fields.Clear();
|
||||
fields.Add()->assign("TAG1:input1");
|
||||
fields.Add()->assign("TAG2:inv*alid");
|
||||
fields.Add()->assign("TAG3:input3");
|
||||
fields.Add()->assign("TAG4:input4");
|
||||
ASSERT_FALSE(tool::GetTagAndNameInfo(fields, &info).ok());
|
||||
|
||||
// Add an invalid character into the tag.
|
||||
fields.Clear();
|
||||
fields.Add()->assign("TAG1:input1");
|
||||
fields.Add()->assign("INVA*LID:input2");
|
||||
fields.Add()->assign("TAG3:input3");
|
||||
fields.Add()->assign("TAG4:input4");
|
||||
ASSERT_FALSE(tool::GetTagAndNameInfo(fields, &info).ok());
|
||||
|
||||
// Add an invalid character into the name and use indexes.
|
||||
fields.Clear();
|
||||
fields.Add()->assign("input1");
|
||||
fields.Add()->assign("inv*alid");
|
||||
fields.Add()->assign("input3");
|
||||
fields.Add()->assign("input4");
|
||||
ASSERT_FALSE(tool::GetTagAndNameInfo(fields, &info).ok());
|
||||
|
||||
info.tags.clear();
|
||||
info.names.clear();
|
||||
info.names.push_back("a");
|
||||
info.tags.push_back("A");
|
||||
info.tags.push_back("B");
|
||||
ASSERT_FALSE(tool::SetFromTagAndNameInfo(info, &fields_copy).ok());
|
||||
|
||||
info.names.push_back("b");
|
||||
info.names.push_back("c");
|
||||
ASSERT_FALSE(tool::SetFromTagAndNameInfo(info, &fields_copy).ok());
|
||||
|
||||
info.tags.clear();
|
||||
info.names.clear();
|
||||
info.names.push_back("input1");
|
||||
info.names.push_back("inv*alid");
|
||||
info.names.push_back("input3");
|
||||
info.names.push_back("input4");
|
||||
ASSERT_FALSE(tool::SetFromTagAndNameInfo(info, &fields_copy).ok());
|
||||
|
||||
info.tags.clear();
|
||||
info.names.clear();
|
||||
info.names.push_back("input1");
|
||||
info.names.push_back("input2");
|
||||
info.names.push_back("input3");
|
||||
info.names.push_back("input4");
|
||||
info.tags.push_back("INPUT1");
|
||||
info.tags.push_back("IN*VALID");
|
||||
info.tags.push_back("INPUT3");
|
||||
info.tags.push_back("INPUT4");
|
||||
ASSERT_FALSE(tool::SetFromTagAndNameInfo(info, &fields_copy).ok());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Defines Helper functions.
|
||||
#include "mediapipe/framework/tool/validate_type.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "mediapipe/framework/calculator_contract.h"
|
||||
#include "mediapipe/framework/legacy_calculator_support.h"
|
||||
#include "mediapipe/framework/packet_generator.h"
|
||||
#include "mediapipe/framework/packet_generator.pb.h"
|
||||
#include "mediapipe/framework/packet_type.h"
|
||||
#include "mediapipe/framework/port/map_util.h"
|
||||
#include "mediapipe/framework/port/ret_check.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
#include "mediapipe/framework/tool/status_util.h"
|
||||
|
||||
#if !defined(MEDIAPIPE_MOBILE) && !defined(MEDIAPIPE_LITE)
|
||||
#include "absl/synchronization/blocking_counter.h"
|
||||
#include "mediapipe/framework/port/threadpool.h"
|
||||
#include "mediapipe/util/cpu_util.h"
|
||||
#endif // !MEDIAPIPE_MOBILE && !MEDIAPIPE_LITE
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
::mediapipe::Status RunGeneratorFillExpectations(
|
||||
const PacketGeneratorConfig& input_config, const std::string& package) {
|
||||
// TODO Remove conversion after everyone uses input/output
|
||||
// side packet.
|
||||
PacketGeneratorConfig config = input_config;
|
||||
|
||||
ASSIGN_OR_RETURN(
|
||||
auto static_access,
|
||||
internal::StaticAccessToGeneratorRegistry::CreateByNameInNamespace(
|
||||
package, config.packet_generator()),
|
||||
_ << config.packet_generator()
|
||||
<< " is not a registered packet generator.");
|
||||
|
||||
CalculatorContract contract;
|
||||
RETURN_IF_ERROR(contract.Initialize(config));
|
||||
|
||||
{
|
||||
LegacyCalculatorSupport::Scoped<CalculatorContract> s(&contract);
|
||||
RETURN_IF_ERROR(static_access->FillExpectations(
|
||||
config.options(), &contract.InputSidePackets(),
|
||||
&contract.OutputSidePackets()))
|
||||
.SetPrepend()
|
||||
<< config.packet_generator() << "::FillExpectations failed: ";
|
||||
}
|
||||
|
||||
// Check that everything got initialized.
|
||||
std::vector<::mediapipe::Status> statuses;
|
||||
statuses.push_back(ValidatePacketTypeSet(contract.InputSidePackets()));
|
||||
statuses.push_back(ValidatePacketTypeSet(contract.OutputSidePackets()));
|
||||
return tool::CombinedStatus(
|
||||
absl::StrCat(config.packet_generator(), "::FillExpectations failed: "),
|
||||
statuses);
|
||||
}
|
||||
|
||||
::mediapipe::Status RunGenerateAndValidateTypes(
|
||||
const std::string& packet_generator_name,
|
||||
const PacketGeneratorOptions& extendable_options,
|
||||
const PacketSet& input_side_packets, PacketSet* output_side_packets,
|
||||
const std::string& package) {
|
||||
CHECK(output_side_packets);
|
||||
// Get static access to functions.
|
||||
ASSIGN_OR_RETURN(
|
||||
auto static_access,
|
||||
internal::StaticAccessToGeneratorRegistry::CreateByNameInNamespace(
|
||||
package, packet_generator_name),
|
||||
_ << packet_generator_name << " is not a registered packet generator.");
|
||||
// Create PacketTypeSets.
|
||||
PacketTypeSet input_side_packet_types(input_side_packets.TagMap());
|
||||
PacketTypeSet output_side_packet_types(output_side_packets->TagMap());
|
||||
|
||||
// Fill the PacketTypeSets with type information.
|
||||
RETURN_IF_ERROR(static_access->FillExpectations(extendable_options,
|
||||
&input_side_packet_types,
|
||||
&output_side_packet_types))
|
||||
.SetPrepend()
|
||||
<< packet_generator_name << "::FillExpectations failed: ";
|
||||
// Check that the types were filled well.
|
||||
std::vector<::mediapipe::Status> statuses;
|
||||
statuses.push_back(ValidatePacketTypeSet(input_side_packet_types));
|
||||
statuses.push_back(ValidatePacketTypeSet(output_side_packet_types));
|
||||
RETURN_IF_ERROR(tool::CombinedStatus(
|
||||
absl::StrCat(packet_generator_name, "::FillExpectations failed: "),
|
||||
statuses));
|
||||
|
||||
RETURN_IF_ERROR(
|
||||
ValidatePacketSet(input_side_packet_types, input_side_packets))
|
||||
.SetPrepend()
|
||||
<< packet_generator_name
|
||||
<< "::FillExpectations expected different input type than those given: ";
|
||||
RETURN_IF_ERROR(static_access->Generate(extendable_options,
|
||||
input_side_packets,
|
||||
output_side_packets))
|
||||
.SetPrepend()
|
||||
<< packet_generator_name << "::Generate failed: ";
|
||||
RETURN_IF_ERROR(
|
||||
ValidatePacketSet(output_side_packet_types, *output_side_packets))
|
||||
.SetPrepend()
|
||||
<< packet_generator_name
|
||||
<< "::FillExpectations expected different "
|
||||
"output type than those produced: ";
|
||||
return ::mediapipe::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2019 The MediaPipe Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Helper functions for doing type validation on CalculatorGraphConfig.
|
||||
#ifndef MEDIAPIPE_FRAMEWORK_TOOL_VALIDATE_TYPE_H_
|
||||
#define MEDIAPIPE_FRAMEWORK_TOOL_VALIDATE_TYPE_H_
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "mediapipe/framework/calculator.pb.h"
|
||||
#include "mediapipe/framework/packet_set.h"
|
||||
#include "mediapipe/framework/port/status.h"
|
||||
|
||||
namespace mediapipe {
|
||||
|
||||
namespace tool {
|
||||
// Equivalent functions for PacketGenerators.
|
||||
::mediapipe::Status RunGeneratorFillExpectations(
|
||||
const PacketGeneratorConfig& config,
|
||||
const std::string& package = "mediapipe");
|
||||
|
||||
// Run PacketGenerator::Generate() on the given generator, options,
|
||||
// and inputs to produce outputs. Validate the types of the inputs and
|
||||
// outputs using PacketGenerator::FillExpectations.
|
||||
::mediapipe::Status RunGenerateAndValidateTypes(
|
||||
const std::string& packet_generator_name,
|
||||
const PacketGeneratorOptions& extendable_options,
|
||||
const PacketSet& input_side_packets, PacketSet* output_side_packets,
|
||||
const std::string& package = "mediapipe");
|
||||
|
||||
} // namespace tool
|
||||
} // namespace mediapipe
|
||||
|
||||
#endif // MEDIAPIPE_FRAMEWORK_TOOL_VALIDATE_TYPE_H_
|
||||
Reference in New Issue
Block a user