Project import generated by Copybara.

PiperOrigin-RevId: 253489161
This commit is contained in:
MediaPipe Team
2019-06-16 16:06:57 -07:00
committed by jqtang
commit d68f5e4169
844 changed files with 134997 additions and 0 deletions
+485
View File
@@ -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;
}
+56
View File
@@ -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
+30
View File
@@ -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
)
+72
View File
@@ -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
+37
View File
@@ -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_
+147
View File
@@ -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_
+57
View File
@@ -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_
+393
View File
@@ -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
+361
View File
@@ -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
+205
View File
@@ -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_
+150
View File
@@ -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
+136
View File
@@ -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
+53
View File
@@ -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];
}
+70
View File
@@ -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
+61
View File
@@ -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_
+276
View File
@@ -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
+133
View File
@@ -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
+39
View File
@@ -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_
+446
View File
@@ -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
+163
View File
@@ -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_
+39
View File
@@ -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_
+48
View File
@@ -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
+37
View File
@@ -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_
+254
View File
@@ -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
+118
View File
@@ -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
+125
View File
@@ -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
+45
View File
@@ -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_