Compare commits

...
3 Commits
Author SHA1 Message Date
MediaPipe Teamandjqtang 4c68eb4a70 Project import generated by Copybara.
GitOrigin-RevId: da3cb82c964457b1c719c915a48e545b980af64b
2020-04-06 21:28:19 -07:00
MediaPipe Teamandjqtang a3d36eee32 Project import generated by Copybara.
GitOrigin-RevId: 53a42bf7ad836321123cb7b6c80b0f2e13fbf83e
2020-04-06 19:14:13 -07:00
MediaPipe Teamandjqtang 1722d4b8a2 Project import generated by Copybara.
GitOrigin-RevId: 43cd697ec87dcc5cab5051f27960bb77a057399d
2020-03-20 15:28:51 -07:00
172 changed files with 6817 additions and 2202 deletions
+7 -3
View File
@@ -12,9 +12,9 @@ build --copt='-Wno-comment'
build --copt='-Wno-return-type' build --copt='-Wno-return-type'
build --copt='-Wno-unused-local-typedefs' build --copt='-Wno-unused-local-typedefs'
build --copt='-Wno-ignored-attributes' build --copt='-Wno-ignored-attributes'
# Temporarily set the incompatibility flag for Bazel 0.27.0 and above
build --incompatible_disable_deprecated_attr_params=false # Tensorflow needs remote repo
build --incompatible_depset_is_not_iterable=false build --experimental_repo_remote_exec
# Sets the default Apple platform to macOS. # Sets the default Apple platform to macOS.
build --apple_platform_type=macos build --apple_platform_type=macos
@@ -23,6 +23,10 @@ build --apple_platform_type=macos
build --apple_generate_dsym build --apple_generate_dsym
# Android configs. # Android configs.
# Note: the documentation tells us to use @androidndk//:default_crosstool, but
# the automatic configuration transition uses //external:android/crosstool.
# Using it here works and spares us from having two different config_settings
# for Android.
build:android --crosstool_top=//external:android/crosstool build:android --crosstool_top=//external:android/crosstool
build:android --host_crosstool_top=@bazel_tools//tools/cpp:toolchain build:android --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
build:android --linkopt=-landroid build:android --linkopt=-landroid
+1 -1
View File
@@ -48,7 +48,7 @@ RUN pip install future
RUN pip3 install six RUN pip3 install six
# Install bazel # Install bazel
ARG BAZEL_VERSION=1.1.0 ARG BAZEL_VERSION=2.0.0
RUN mkdir /bazel && \ RUN mkdir /bazel && \
wget --no-check-certificate -O /bazel/installer.sh "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/b\ wget --no-check-certificate -O /bazel/installer.sh "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/b\
azel-${BAZEL_VERSION}-installer-linux-x86_64.sh" && \ azel-${BAZEL_VERSION}-installer-linux-x86_64.sh" && \
+69 -95
View File
@@ -2,16 +2,15 @@ workspace(name = "mediapipe")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
skylib_version = "0.8.0" skylib_version = "0.9.0"
http_archive( http_archive(
name = "bazel_skylib", name = "bazel_skylib",
type = "tar.gz", type = "tar.gz",
url = "https://github.com/bazelbuild/bazel-skylib/releases/download/{}/bazel-skylib.{}.tar.gz".format (skylib_version, skylib_version), url = "https://github.com/bazelbuild/bazel-skylib/releases/download/{}/bazel_skylib-{}.tar.gz".format (skylib_version, skylib_version),
sha256 = "2ef429f5d7ce7111263289644d233707dba35e39696377ebab8b0bc701f7818e", sha256 = "1dde365491125a3db70731e25658dfdd3bc5dbdfd11b840b3e987ecf043c7ca0",
) )
load("@bazel_skylib//lib:versions.bzl", "versions") load("@bazel_skylib//lib:versions.bzl", "versions")
versions.check(minimum_bazel_version = "1.0.0", versions.check(minimum_bazel_version = "2.0.0")
maximum_bazel_version = "1.2.1")
# ABSL cpp library lts_2020_02_25 # ABSL cpp library lts_2020_02_25
@@ -93,11 +92,13 @@ http_archive(
build_file = "@//third_party:libyuv.BUILD", build_file = "@//third_party:libyuv.BUILD",
) )
# Note: protobuf-javalite is no longer released as a separate download, it's included in the main Java download.
# ...but the Java download is currently broken, so we use the "source" download.
http_archive( http_archive(
name = "com_google_protobuf_javalite", name = "com_google_protobuf_javalite",
sha256 = "79d102c61e2a479a0b7e5fc167bcfaa4832a0c6aad4a75fa7da0480564931bcc", sha256 = "a79d19dcdf9139fa4b81206e318e33d245c4c9da1ffed21c87288ed4380426f9",
strip_prefix = "protobuf-384989534b2246d413dbcd750744faab2607b516", strip_prefix = "protobuf-3.11.4",
urls = ["https://github.com/google/protobuf/archive/384989534b2246d413dbcd750744faab2607b516.zip"], urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.11.4.tar.gz"],
) )
http_archive( http_archive(
@@ -117,19 +118,18 @@ http_archive(
], ],
) )
# 2020-02-12 # 2020-04-01
# The last commit before TensorFlow switched to Bazel 2.0 _TENSORFLOW_GIT_COMMIT = "805e47cea96c7e8c6fccf494d40a2392dc99fdd8"
_TENSORFLOW_GIT_COMMIT = "77e9ffb9b2bfb1a4f7056e62d84039626923e328" _TENSORFLOW_SHA256= "9ee3ae604c2e1345ac60345becee6d659364721513f9cb8652eb2e7138320ca5"
_TENSORFLOW_SHA256= "176ccd82f7dd17c5e117b50d353603b129c7a6ccbfebd522ca47cc2a40f33f13"
http_archive( http_archive(
name = "org_tensorflow", name = "org_tensorflow",
urls = [ urls = [
"https://mirror.bazel.build/github.com/tensorflow/tensorflow/archive/%s.tar.gz" % _TENSORFLOW_GIT_COMMIT, "https://mirror.bazel.build/github.com/tensorflow/tensorflow/archive/%s.tar.gz" % _TENSORFLOW_GIT_COMMIT,
"https://github.com/tensorflow/tensorflow/archive/%s.tar.gz" % _TENSORFLOW_GIT_COMMIT, "https://github.com/tensorflow/tensorflow/archive/%s.tar.gz" % _TENSORFLOW_GIT_COMMIT,
], ],
# A compatibility patch
patches = [ patches = [
"@//third_party:org_tensorflow_528e22eae8bf3206189a066032c66e9e5c9b4a61.diff" "@//third_party:org_tensorflow_compatibility_fixes.diff",
"@//third_party:org_tensorflow_protobuf_updates.diff",
], ],
patch_args = [ patch_args = [
"-p1", "-p1",
@@ -154,10 +154,6 @@ http_archive(
sha256 = "5ba6d0db4e784621fda44a50c58bb23b0892684692f0c623e2063f9c19f192f1" sha256 = "5ba6d0db4e784621fda44a50c58bb23b0892684692f0c623e2063f9c19f192f1"
) )
# Please run
# $ sudo apt-get install libopencv-core-dev libopencv-highgui-dev \
# libopencv-calib3d-dev libopencv-features2d-dev \
# libopencv-imgproc-dev libopencv-video-dev
new_local_repository( new_local_repository(
name = "linux_opencv", name = "linux_opencv",
build_file = "@//third_party:opencv_linux.BUILD", build_file = "@//third_party:opencv_linux.BUILD",
@@ -170,7 +166,6 @@ new_local_repository(
path = "/usr" path = "/usr"
) )
# Please run $ brew install opencv@3
new_local_repository( new_local_repository(
name = "macos_opencv", name = "macos_opencv",
build_file = "@//third_party:opencv_macos.BUILD", build_file = "@//third_party:opencv_macos.BUILD",
@@ -203,79 +198,6 @@ http_archive(
url = "https://github.com/opencv/opencv/releases/download/3.2.0/opencv-3.2.0-ios-framework.zip", url = "https://github.com/opencv/opencv/releases/download/3.2.0/opencv-3.2.0-ios-framework.zip",
) )
RULES_JVM_EXTERNAL_TAG = "2.2"
RULES_JVM_EXTERNAL_SHA = "f1203ce04e232ab6fdd81897cf0ff76f2c04c0741424d192f28e65ae752ce2d6"
http_archive(
name = "rules_jvm_external",
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
sha256 = RULES_JVM_EXTERNAL_SHA,
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)
load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_install(
artifacts = [
"androidx.annotation:annotation:aar:1.1.0",
"androidx.appcompat:appcompat:aar:1.1.0-rc01",
"androidx.camera:camera-core:aar:1.0.0-alpha06",
"androidx.camera:camera-camera2:aar:1.0.0-alpha06",
"androidx.constraintlayout:constraintlayout:aar:1.1.3",
"androidx.core:core:aar:1.1.0-rc03",
"androidx.legacy:legacy-support-v4:aar:1.0.0",
"androidx.recyclerview:recyclerview:aar:1.1.0-beta02",
"com.google.android.material:material:aar:1.0.0-rc01",
],
repositories = [
"https://dl.google.com/dl/android/maven2",
"https://repo1.maven.org/maven2",
],
)
maven_server(
name = "google_server",
url = "https://dl.google.com/dl/android/maven2",
)
maven_jar(
name = "androidx_lifecycle",
artifact = "androidx.lifecycle:lifecycle-common:2.0.0",
sha1 = "e070ffae07452331bc5684734fce6831d531785c",
server = "google_server",
)
maven_jar(
name = "androidx_concurrent_futures",
artifact = "androidx.concurrent:concurrent-futures:1.0.0-alpha03",
sha1 = "b528df95c7e2fefa2210c0c742bf3e491c1818ae",
server = "google_server",
)
maven_jar(
name = "com_google_guava_android",
artifact = "com.google.guava:guava:27.0.1-android",
sha1 = "b7e1c37f66ef193796ccd7ea6e80c2b05426182d",
)
maven_jar(
name = "com_google_common_flogger",
artifact = "com.google.flogger:flogger:0.3.1",
sha1 = "585030fe1ec709760cbef997a459729fb965df0e",
)
maven_jar(
name = "com_google_common_flogger_system_backend",
artifact = "com.google.flogger:flogger-system-backend:0.3.1",
sha1 = "287b569d76abcd82f9de87fe41829fbc7ebd8ac9",
)
maven_jar(
name = "com_google_code_findbugs",
artifact = "com.google.code.findbugs:jsr305:3.0.2",
sha1 = "25ea2e8b0c338a877313bd4672d3fe056ea78f0d",
)
# You may run setup_android.sh to install Android SDK and NDK. # You may run setup_android.sh to install Android SDK and NDK.
android_ndk_repository( android_ndk_repository(
name = "androidndk", name = "androidndk",
@@ -289,9 +211,15 @@ android_sdk_repository(
http_archive( http_archive(
name = "build_bazel_rules_apple", name = "build_bazel_rules_apple",
sha256 = "bdc8e66e70b8a75da23b79f1f8c6207356df07d041d96d2189add7ee0780cf4e", sha256 = "7a7afdd4869bb201c9352eed2daf37294d42b093579b70423490c1b4d4f6ce42",
strip_prefix = "rules_apple-b869b0d3868d78a1d4ffd866ccb304fb68aa12c3", url = "https://github.com/bazelbuild/rules_apple/releases/download/0.19.0/rules_apple.0.19.0.tar.gz",
url = "https://github.com/bazelbuild/rules_apple/archive/b869b0d3868d78a1d4ffd866ccb304fb68aa12c3.tar.gz", patches = [
# Bypass checking ios unit test runner when building MP ios applications.
"@//third_party:build_bazel_rules_apple_bypass_test_runner_check.diff"
],
patch_args = [
"-p1",
],
) )
load( load(
@@ -325,3 +253,49 @@ http_archive(
build_file = "@//third_party:google_toolbox_for_mac.BUILD", build_file = "@//third_party:google_toolbox_for_mac.BUILD",
) )
# Maven dependencies.
RULES_JVM_EXTERNAL_TAG = "3.2"
RULES_JVM_EXTERNAL_SHA = "82262ff4223c5fda6fb7ff8bd63db8131b51b413d26eb49e3131037e79e324af"
http_archive(
name = "rules_jvm_external",
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
sha256 = RULES_JVM_EXTERNAL_SHA,
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)
load("@rules_jvm_external//:defs.bzl", "maven_install")
# Important: there can only be one maven_install rule. Add new maven deps here.
maven_install(
name = "maven",
artifacts = [
"junit:junit:4.12",
"androidx.test.espresso:espresso-core:3.1.1",
"org.hamcrest:hamcrest-library:1.3",
"androidx.concurrent:concurrent-futures:1.0.0-alpha03",
"androidx.lifecycle:lifecycle-common:2.2.0",
"androidx.annotation:annotation:aar:1.1.0",
"androidx.appcompat:appcompat:aar:1.1.0-rc01",
"androidx.camera:camera-core:aar:1.0.0-alpha06",
"androidx.camera:camera-camera2:aar:1.0.0-alpha06",
"androidx.constraintlayout:constraintlayout:aar:1.1.3",
"androidx.core:core:aar:1.1.0-rc03",
"androidx.legacy:legacy-support-v4:aar:1.0.0",
"androidx.recyclerview:recyclerview:aar:1.1.0-beta02",
"com.google.android.material:material:aar:1.0.0-rc01",
"com.google.code.findbugs:jsr305:3.0.2",
"com.google.flogger:flogger-system-backend:0.3.1",
"com.google.flogger:flogger:0.3.1",
"com.google.guava:guava:27.0.1-android",
],
repositories = [
"https://jcenter.bintray.com",
"https://maven.google.com",
"https://dl.google.com/dl/android/maven2",
"https://repo1.maven.org/maven2",
],
fetch_sources = True,
version_conflict_policy = "pinned",
)
+3
View File
@@ -14,6 +14,9 @@
licenses(["notice"]) # Apache 2.0 licenses(["notice"]) # Apache 2.0
# Note: yes, these need to use "//external:android/crosstool", not
# @androidndk//:default_crosstool.
config_setting( config_setting(
name = "android", name = "android",
values = {"crosstool_top": "//external:android/crosstool"}, values = {"crosstool_top": "//external:android/crosstool"},
+13 -3
View File
@@ -228,7 +228,9 @@ cc_library(
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework:collection_item_id", "//mediapipe/framework:collection_item_id",
"//mediapipe/framework:packet", "//mediapipe/framework:packet",
"//mediapipe/framework/formats:detection_cc_proto",
"//mediapipe/framework/formats:landmark_cc_proto", "//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/formats:matrix",
"//mediapipe/framework/formats:rect_cc_proto", "//mediapipe/framework/formats:rect_cc_proto",
"//mediapipe/framework/port:integral_types", "//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
@@ -249,12 +251,14 @@ cc_library(
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework:collection_item_id", "//mediapipe/framework:collection_item_id",
"//mediapipe/framework:packet", "//mediapipe/framework:packet",
"//mediapipe/framework/formats:classification_cc_proto",
"//mediapipe/framework/formats:landmark_cc_proto", "//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/formats:rect_cc_proto", "//mediapipe/framework/formats:rect_cc_proto",
"//mediapipe/framework/port:integral_types", "//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
"//mediapipe/util:render_data_cc_proto", "//mediapipe/util:render_data_cc_proto",
"@org_tensorflow//tensorflow/lite:framework",
], ],
alwayslink = 1, alwayslink = 1,
) )
@@ -265,10 +269,11 @@ cc_test(
deps = [ deps = [
":begin_loop_calculator", ":begin_loop_calculator",
":end_loop_calculator", ":end_loop_calculator",
"//mediapipe/calculators/core:packet_cloner_calculator", ":gate_calculator",
"//mediapipe/framework:calculator_context", "//mediapipe/framework:calculator_context",
"//mediapipe/framework:calculator_contract", "//mediapipe/framework:calculator_contract",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework:packet",
"//mediapipe/framework/port:gtest_main", "//mediapipe/framework/port:gtest_main",
"//mediapipe/framework/port:integral_types", "//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:parse_text_proto", "//mediapipe/framework/port:parse_text_proto",
@@ -334,6 +339,7 @@ cc_library(
deps = [ deps = [
":clip_vector_size_calculator_cc_proto", ":clip_vector_size_calculator_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:detection_cc_proto",
"//mediapipe/framework/formats:rect_cc_proto", "//mediapipe/framework/formats:rect_cc_proto",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
@@ -693,15 +699,17 @@ cc_test(
name = "previous_loopback_calculator_test", name = "previous_loopback_calculator_test",
srcs = ["previous_loopback_calculator_test.cc"], srcs = ["previous_loopback_calculator_test.cc"],
deps = [ deps = [
":gate_calculator",
":make_pair_calculator",
":pass_through_calculator",
":previous_loopback_calculator", ":previous_loopback_calculator",
"//mediapipe/calculators/core:make_pair_calculator",
"//mediapipe/calculators/core:pass_through_calculator",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework:calculator_runner", "//mediapipe/framework:calculator_runner",
"//mediapipe/framework:timestamp", "//mediapipe/framework:timestamp",
"//mediapipe/framework/port:gtest_main", "//mediapipe/framework/port:gtest_main",
"//mediapipe/framework/port:integral_types", "//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:parse_text_proto", "//mediapipe/framework/port:parse_text_proto",
"//mediapipe/framework/port:status",
"//mediapipe/framework/stream_handler:immediate_input_stream_handler", "//mediapipe/framework/stream_handler:immediate_input_stream_handler",
"//mediapipe/framework/tool:sink", "//mediapipe/framework/tool:sink",
"@com_google_absl//absl/time", "@com_google_absl//absl/time",
@@ -769,9 +777,11 @@ cc_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
":split_vector_calculator_cc_proto", ":split_vector_calculator_cc_proto",
"//mediapipe/framework/formats:detection_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:landmark_cc_proto", "//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/formats:rect_cc_proto", "//mediapipe/framework/formats:rect_cc_proto",
"//mediapipe/framework/formats:matrix",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
"//mediapipe/util:resource_util", "//mediapipe/util:resource_util",
@@ -20,6 +20,8 @@
#include "mediapipe/calculators/core/end_loop_calculator.h" #include "mediapipe/calculators/core/end_loop_calculator.h"
#include "mediapipe/framework/calculator_contract.h" #include "mediapipe/framework/calculator_contract.h"
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/packet.h"
#include "mediapipe/framework/port/gmock.h"
#include "mediapipe/framework/port/gtest.h" #include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/parse_text_proto.h" #include "mediapipe/framework/port/parse_text_proto.h"
@@ -28,6 +30,13 @@
namespace mediapipe { namespace mediapipe {
namespace { namespace {
MATCHER_P2(PacketOfIntsEq, timestamp, value, "") {
Timestamp actual_timestamp = arg.Timestamp();
const auto& actual_value = arg.template Get<std::vector<int>>();
return testing::Value(actual_timestamp, testing::Eq(timestamp)) &&
testing::Value(actual_value, testing::ElementsAreArray(value));
}
typedef BeginLoopCalculator<std::vector<int>> BeginLoopIntegerCalculator; typedef BeginLoopCalculator<std::vector<int>> BeginLoopIntegerCalculator;
REGISTER_CALCULATOR(BeginLoopIntegerCalculator); REGISTER_CALCULATOR(BeginLoopIntegerCalculator);
@@ -59,8 +68,8 @@ REGISTER_CALCULATOR(EndLoopIntegersCalculator);
class BeginEndLoopCalculatorGraphTest : public ::testing::Test { class BeginEndLoopCalculatorGraphTest : public ::testing::Test {
protected: protected:
BeginEndLoopCalculatorGraphTest() { void SetUp() override {
graph_config_ = ParseTextProtoOrDie<CalculatorGraphConfig>( auto graph_config = ParseTextProtoOrDie<CalculatorGraphConfig>(
R"( R"(
num_threads: 4 num_threads: 4
input_stream: "ints" input_stream: "ints"
@@ -82,94 +91,222 @@ class BeginEndLoopCalculatorGraphTest : public ::testing::Test {
output_stream: "ITERABLE:ints_plus_one" output_stream: "ITERABLE:ints_plus_one"
} }
)"); )");
tool::AddVectorSink("ints_plus_one", &graph_config_, &output_packets_); tool::AddVectorSink("ints_plus_one", &graph_config, &output_packets_);
MP_ASSERT_OK(graph_.Initialize(graph_config));
MP_ASSERT_OK(graph_.StartRun({}));
} }
CalculatorGraphConfig graph_config_; void SendPacketOfInts(Timestamp timestamp, std::vector<int> ints) {
MP_ASSERT_OK(graph_.AddPacketToInputStream(
"ints", MakePacket<std::vector<int>>(std::move(ints)).At(timestamp)));
}
CalculatorGraph graph_;
std::vector<Packet> output_packets_; std::vector<Packet> output_packets_;
}; };
TEST_F(BeginEndLoopCalculatorGraphTest, InputStreamForIterableIsEmpty) {
MP_ASSERT_OK(graph_.WaitUntilIdle());
// EndLoopCalc will forward the timestamp bound because there are no packets
// to process.
ASSERT_EQ(0, output_packets_.size());
MP_ASSERT_OK(graph_.CloseAllPacketSources());
MP_ASSERT_OK(graph_.WaitUntilDone());
}
TEST_F(BeginEndLoopCalculatorGraphTest, SingleEmptyVector) { TEST_F(BeginEndLoopCalculatorGraphTest, SingleEmptyVector) {
CalculatorGraph graph; SendPacketOfInts(Timestamp(0), {});
MP_EXPECT_OK(graph.Initialize(graph_config_)); MP_ASSERT_OK(graph_.WaitUntilIdle());
MP_EXPECT_OK(graph.StartRun({}));
auto input_vector = absl::make_unique<std::vector<int>>();
Timestamp input_timestamp = Timestamp(0);
MP_ASSERT_OK(graph.AddPacketToInputStream(
"ints", Adopt(input_vector.release()).At(input_timestamp)));
MP_ASSERT_OK(graph.WaitUntilIdle());
// EndLoopCalc will forward the timestamp bound because there are no elements // EndLoopCalc will forward the timestamp bound because there are no elements
// in collection to output. // in collection to output.
ASSERT_EQ(0, output_packets_.size()); EXPECT_TRUE(output_packets_.empty());
MP_ASSERT_OK(graph.CloseAllPacketSources()); MP_ASSERT_OK(graph_.CloseAllPacketSources());
MP_ASSERT_OK(graph.WaitUntilDone()); MP_ASSERT_OK(graph_.WaitUntilDone());
} }
TEST_F(BeginEndLoopCalculatorGraphTest, SingleNonEmptyVector) { TEST_F(BeginEndLoopCalculatorGraphTest, SingleNonEmptyVector) {
CalculatorGraph graph;
MP_EXPECT_OK(graph.Initialize(graph_config_));
MP_EXPECT_OK(graph.StartRun({}));
auto input_vector = absl::make_unique<std::vector<int>>();
input_vector->emplace_back(0);
input_vector->emplace_back(1);
input_vector->emplace_back(2);
Timestamp input_timestamp = Timestamp(0); Timestamp input_timestamp = Timestamp(0);
MP_ASSERT_OK(graph.AddPacketToInputStream( SendPacketOfInts(input_timestamp, {0, 1, 2});
"ints", Adopt(input_vector.release()).At(input_timestamp))); MP_ASSERT_OK(graph_.WaitUntilIdle());
MP_ASSERT_OK(graph.WaitUntilIdle());
ASSERT_EQ(1, output_packets_.size()); EXPECT_THAT(output_packets_,
EXPECT_EQ(input_timestamp, output_packets_[0].Timestamp()); testing::ElementsAre(
std::vector<int> expected_output_vector = {1, 2, 3}; PacketOfIntsEq(input_timestamp, std::vector<int>{1, 2, 3})));
EXPECT_EQ(expected_output_vector, output_packets_[0].Get<std::vector<int>>());
MP_ASSERT_OK(graph.CloseAllPacketSources()); MP_ASSERT_OK(graph_.CloseAllPacketSources());
MP_ASSERT_OK(graph.WaitUntilDone()); MP_ASSERT_OK(graph_.WaitUntilDone());
} }
TEST_F(BeginEndLoopCalculatorGraphTest, MultipleVectors) { TEST_F(BeginEndLoopCalculatorGraphTest, MultipleVectors) {
CalculatorGraph graph;
MP_EXPECT_OK(graph.Initialize(graph_config_));
MP_EXPECT_OK(graph.StartRun({}));
auto input_vector0 = absl::make_unique<std::vector<int>>();
input_vector0->emplace_back(0);
input_vector0->emplace_back(1);
Timestamp input_timestamp0 = Timestamp(0); Timestamp input_timestamp0 = Timestamp(0);
MP_ASSERT_OK(graph.AddPacketToInputStream( SendPacketOfInts(input_timestamp0, {0, 1});
"ints", Adopt(input_vector0.release()).At(input_timestamp0)));
auto input_vector1 = absl::make_unique<std::vector<int>>();
Timestamp input_timestamp1 = Timestamp(1); Timestamp input_timestamp1 = Timestamp(1);
MP_ASSERT_OK(graph.AddPacketToInputStream( SendPacketOfInts(input_timestamp1, {});
"ints", Adopt(input_vector1.release()).At(input_timestamp1)));
auto input_vector2 = absl::make_unique<std::vector<int>>();
input_vector2->emplace_back(2);
input_vector2->emplace_back(3);
Timestamp input_timestamp2 = Timestamp(2); Timestamp input_timestamp2 = Timestamp(2);
MP_ASSERT_OK(graph.AddPacketToInputStream( SendPacketOfInts(input_timestamp2, {2, 3});
"ints", Adopt(input_vector2.release()).At(input_timestamp2)));
MP_ASSERT_OK(graph.CloseAllPacketSources()); MP_ASSERT_OK(graph_.CloseAllPacketSources());
MP_ASSERT_OK(graph.WaitUntilDone()); MP_ASSERT_OK(graph_.WaitUntilDone());
ASSERT_EQ(2, output_packets_.size());
EXPECT_EQ(input_timestamp0, output_packets_[0].Timestamp());
std::vector<int> expected_output_vector0 = {1, 2};
EXPECT_EQ(expected_output_vector0,
output_packets_[0].Get<std::vector<int>>());
// At input_timestamp1, EndLoopCalc will forward timestamp bound as there are // At input_timestamp1, EndLoopCalc will forward timestamp bound as there are
// no elements in vector to process. // no elements in vector to process.
EXPECT_THAT(output_packets_,
testing::ElementsAre(
PacketOfIntsEq(input_timestamp0, std::vector<int>{1, 2}),
PacketOfIntsEq(input_timestamp2, std::vector<int>{3, 4})));
}
EXPECT_EQ(input_timestamp2, output_packets_[1].Timestamp()); // Passes non empty vector through or outputs empty vector in case of timestamp
std::vector<int> expected_output_vector2 = {3, 4}; // bound update.
EXPECT_EQ(expected_output_vector2, class PassThroughOrEmptyVectorCalculator : public CalculatorBase {
output_packets_[1].Get<std::vector<int>>()); public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->SetProcessTimestampBounds(true);
cc->Inputs().Index(0).Set<std::vector<int>>();
cc->Outputs().Index(0).Set<std::vector<int>>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
cc->SetOffset(TimestampDiff(0));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
if (!cc->Inputs().Index(0).IsEmpty()) {
cc->Outputs().Index(0).AddPacket(cc->Inputs().Index(0).Value());
} else {
cc->Outputs().Index(0).AddPacket(
MakePacket<std::vector<int>>(std::vector<int>())
.At(cc->InputTimestamp()));
}
return ::mediapipe::OkStatus();
}
};
REGISTER_CALCULATOR(PassThroughOrEmptyVectorCalculator);
class BeginEndLoopCalculatorGraphProcessingEmptyPacketsTest
: public ::testing::Test {
protected:
void SetUp() override {
auto graph_config = ParseTextProtoOrDie<CalculatorGraphConfig>(
R"(
num_threads: 4
input_stream: "ints"
input_stream: "force_ints_to_be_timestamp_bound_update"
node {
calculator: "GateCalculator"
input_stream: "ints"
input_stream: "DISALLOW:force_ints_to_be_timestamp_bound_update"
output_stream: "ints_passed_through"
}
node {
calculator: "BeginLoopIntegerCalculator"
input_stream: "ITERABLE:ints_passed_through"
output_stream: "ITEM:int"
output_stream: "BATCH_END:timestamp"
}
node {
calculator: "IncrementCalculator"
input_stream: "int"
output_stream: "int_plus_one"
}
node {
calculator: "EndLoopIntegersCalculator"
input_stream: "ITEM:int_plus_one"
input_stream: "BATCH_END:timestamp"
output_stream: "ITERABLE:ints_plus_one"
}
node {
calculator: "PassThroughOrEmptyVectorCalculator"
input_stream: "ints_plus_one"
output_stream: "ints_plus_one_passed_through"
}
)");
tool::AddVectorSink("ints_plus_one_passed_through", &graph_config,
&output_packets_);
MP_ASSERT_OK(graph_.Initialize(graph_config));
MP_ASSERT_OK(graph_.StartRun({}));
}
void SendPacketOfIntsOrBound(Timestamp timestamp, std::vector<int> ints) {
// All "ints" packets which are empty are forced to be just timestamp
// bound updates for begin loop calculator.
bool force_ints_to_be_timestamp_bound_update = ints.empty();
MP_ASSERT_OK(graph_.AddPacketToInputStream(
"force_ints_to_be_timestamp_bound_update",
MakePacket<bool>(force_ints_to_be_timestamp_bound_update)
.At(timestamp)));
MP_ASSERT_OK(graph_.AddPacketToInputStream(
"ints", MakePacket<std::vector<int>>(std::move(ints)).At(timestamp)));
}
CalculatorGraph graph_;
std::vector<Packet> output_packets_;
};
TEST_F(BeginEndLoopCalculatorGraphProcessingEmptyPacketsTest,
SingleEmptyVector) {
SendPacketOfIntsOrBound(Timestamp(0), {});
MP_ASSERT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(output_packets_, testing::ElementsAre(PacketOfIntsEq(
Timestamp(0), std::vector<int>{})));
MP_ASSERT_OK(graph_.CloseAllPacketSources());
MP_ASSERT_OK(graph_.WaitUntilDone());
}
TEST_F(BeginEndLoopCalculatorGraphProcessingEmptyPacketsTest,
SingleNonEmptyVector) {
SendPacketOfIntsOrBound(Timestamp(0), {0, 1, 2});
MP_ASSERT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(output_packets_, testing::ElementsAre(PacketOfIntsEq(
Timestamp(0), std::vector<int>{1, 2, 3})));
MP_ASSERT_OK(graph_.CloseAllPacketSources());
MP_ASSERT_OK(graph_.WaitUntilDone());
}
TEST_F(BeginEndLoopCalculatorGraphProcessingEmptyPacketsTest, MultipleVectors) {
SendPacketOfIntsOrBound(Timestamp(0), {});
// Waiting until idle to guarantee all timestamp bound updates are processed
// individually. (Timestamp bounds updates occur in the provide config only
// if input is an empty vector.)
MP_ASSERT_OK(graph_.WaitUntilIdle());
SendPacketOfIntsOrBound(Timestamp(1), {0, 1});
SendPacketOfIntsOrBound(Timestamp(2), {});
// Waiting until idle to guarantee all timestamp bound updates are processed
// individually. (Timestamp bounds updates occur in the provide config only
// if input is an empty vector.)
MP_ASSERT_OK(graph_.WaitUntilIdle());
SendPacketOfIntsOrBound(Timestamp(3), {2, 3});
SendPacketOfIntsOrBound(Timestamp(4), {});
// Waiting until idle to guarantee all timestamp bound updates are processed
// individually. (Timestamp bounds updates occur in the provide config only
// if input is an empty vector.)
MP_ASSERT_OK(graph_.WaitUntilIdle());
MP_ASSERT_OK(graph_.CloseAllPacketSources());
MP_ASSERT_OK(graph_.WaitUntilDone());
EXPECT_THAT(
output_packets_,
testing::ElementsAre(PacketOfIntsEq(Timestamp(0), std::vector<int>{}),
PacketOfIntsEq(Timestamp(1), std::vector<int>{1, 2}),
PacketOfIntsEq(Timestamp(2), std::vector<int>{}),
PacketOfIntsEq(Timestamp(3), std::vector<int>{3, 4}),
PacketOfIntsEq(Timestamp(4), std::vector<int>{})));
} }
class MultiplierCalculator : public CalculatorBase { class MultiplierCalculator : public CalculatorBase {
@@ -199,8 +336,8 @@ REGISTER_CALCULATOR(MultiplierCalculator);
class BeginEndLoopCalculatorGraphWithClonedInputsTest : public ::testing::Test { class BeginEndLoopCalculatorGraphWithClonedInputsTest : public ::testing::Test {
protected: protected:
BeginEndLoopCalculatorGraphWithClonedInputsTest() { void SetUp() override {
graph_config_ = ParseTextProtoOrDie<CalculatorGraphConfig>( auto graph_config = ParseTextProtoOrDie<CalculatorGraphConfig>(
R"( R"(
num_threads: 4 num_threads: 4
input_stream: "ints" input_stream: "ints"
@@ -226,109 +363,85 @@ class BeginEndLoopCalculatorGraphWithClonedInputsTest : public ::testing::Test {
output_stream: "ITERABLE:multiplied_ints" output_stream: "ITERABLE:multiplied_ints"
} }
)"); )");
tool::AddVectorSink("multiplied_ints", &graph_config_, &output_packets_); tool::AddVectorSink("multiplied_ints", &graph_config, &output_packets_);
MP_ASSERT_OK(graph_.Initialize(graph_config));
MP_ASSERT_OK(graph_.StartRun({}));
} }
CalculatorGraphConfig graph_config_; void SendPackets(Timestamp timestamp, int multiplier, std::vector<int> ints) {
MP_ASSERT_OK(graph_.AddPacketToInputStream(
"ints", MakePacket<std::vector<int>>(std::move(ints)).At(timestamp)));
MP_ASSERT_OK(graph_.AddPacketToInputStream(
"multiplier", MakePacket<int>(multiplier).At(timestamp)));
}
void SendMultiplier(Timestamp timestamp, int multiplier) {
MP_ASSERT_OK(graph_.AddPacketToInputStream(
"multiplier", MakePacket<int>(multiplier).At(timestamp)));
}
CalculatorGraph graph_;
std::vector<Packet> output_packets_; std::vector<Packet> output_packets_;
}; };
TEST_F(BeginEndLoopCalculatorGraphWithClonedInputsTest, SingleEmptyVector) { TEST_F(BeginEndLoopCalculatorGraphWithClonedInputsTest,
CalculatorGraph graph; InputStreamForIterableIsEmpty) {
MP_EXPECT_OK(graph.Initialize(graph_config_));
MP_EXPECT_OK(graph.StartRun({}));
auto input_vector = absl::make_unique<std::vector<int>>();
Timestamp input_timestamp = Timestamp(42); Timestamp input_timestamp = Timestamp(42);
MP_ASSERT_OK(graph.AddPacketToInputStream( SendMultiplier(input_timestamp, /*multiplier=*/2);
"ints", Adopt(input_vector.release()).At(input_timestamp))); MP_ASSERT_OK(graph_.WaitUntilIdle());
auto multiplier = absl::make_unique<int>(2);
MP_ASSERT_OK(graph.AddPacketToInputStream( // EndLoopCalc will forward the timestamp bound because there are no packets
"multiplier", Adopt(multiplier.release()).At(input_timestamp))); // to process.
MP_ASSERT_OK(graph.WaitUntilIdle()); ASSERT_EQ(0, output_packets_.size());
MP_ASSERT_OK(graph_.CloseAllPacketSources());
MP_ASSERT_OK(graph_.WaitUntilDone());
}
TEST_F(BeginEndLoopCalculatorGraphWithClonedInputsTest, SingleEmptyVector) {
SendPackets(Timestamp(0), /*multiplier=*/2, /*ints=*/{});
MP_ASSERT_OK(graph_.WaitUntilIdle());
// EndLoopCalc will forward the timestamp bound because there are no elements // EndLoopCalc will forward the timestamp bound because there are no elements
// in collection to output. // in collection to output.
ASSERT_EQ(0, output_packets_.size()); EXPECT_TRUE(output_packets_.empty());
MP_ASSERT_OK(graph.CloseAllPacketSources()); MP_ASSERT_OK(graph_.CloseAllPacketSources());
MP_ASSERT_OK(graph.WaitUntilDone()); MP_ASSERT_OK(graph_.WaitUntilDone());
} }
TEST_F(BeginEndLoopCalculatorGraphWithClonedInputsTest, SingleNonEmptyVector) { TEST_F(BeginEndLoopCalculatorGraphWithClonedInputsTest, SingleNonEmptyVector) {
CalculatorGraph graph;
MP_EXPECT_OK(graph.Initialize(graph_config_));
MP_EXPECT_OK(graph.StartRun({}));
auto input_vector = absl::make_unique<std::vector<int>>();
input_vector->emplace_back(0);
input_vector->emplace_back(1);
input_vector->emplace_back(2);
Timestamp input_timestamp = Timestamp(42); Timestamp input_timestamp = Timestamp(42);
MP_ASSERT_OK(graph.AddPacketToInputStream( SendPackets(input_timestamp, /*multiplier=*/2, /*ints=*/{0, 1, 2});
"ints", Adopt(input_vector.release()).At(input_timestamp))); MP_ASSERT_OK(graph_.WaitUntilIdle());
auto multiplier = absl::make_unique<int>(2);
MP_ASSERT_OK(graph.AddPacketToInputStream(
"multiplier", Adopt(multiplier.release()).At(input_timestamp)));
MP_ASSERT_OK(graph.WaitUntilIdle());
ASSERT_EQ(1, output_packets_.size()); EXPECT_THAT(output_packets_,
EXPECT_EQ(input_timestamp, output_packets_[0].Timestamp()); testing::ElementsAre(
std::vector<int> expected_output_vector = {0, 2, 4}; PacketOfIntsEq(input_timestamp, std::vector<int>{0, 2, 4})));
EXPECT_EQ(expected_output_vector, output_packets_[0].Get<std::vector<int>>());
MP_ASSERT_OK(graph.CloseAllPacketSources()); MP_ASSERT_OK(graph_.CloseAllPacketSources());
MP_ASSERT_OK(graph.WaitUntilDone()); MP_ASSERT_OK(graph_.WaitUntilDone());
} }
TEST_F(BeginEndLoopCalculatorGraphWithClonedInputsTest, MultipleVectors) { TEST_F(BeginEndLoopCalculatorGraphWithClonedInputsTest, MultipleVectors) {
CalculatorGraph graph;
MP_EXPECT_OK(graph.Initialize(graph_config_));
MP_EXPECT_OK(graph.StartRun({}));
auto input_vector0 = absl::make_unique<std::vector<int>>();
input_vector0->emplace_back(0);
input_vector0->emplace_back(1);
Timestamp input_timestamp0 = Timestamp(42); Timestamp input_timestamp0 = Timestamp(42);
MP_ASSERT_OK(graph.AddPacketToInputStream( SendPackets(input_timestamp0, /*multiplier=*/2, /*ints=*/{0, 1});
"ints", Adopt(input_vector0.release()).At(input_timestamp0)));
auto multiplier0 = absl::make_unique<int>(2);
MP_ASSERT_OK(graph.AddPacketToInputStream(
"multiplier", Adopt(multiplier0.release()).At(input_timestamp0)));
auto input_vector1 = absl::make_unique<std::vector<int>>();
Timestamp input_timestamp1 = Timestamp(43); Timestamp input_timestamp1 = Timestamp(43);
MP_ASSERT_OK(graph.AddPacketToInputStream( SendPackets(input_timestamp1, /*multiplier=*/2, /*ints=*/{});
"ints", Adopt(input_vector1.release()).At(input_timestamp1)));
auto multiplier1 = absl::make_unique<int>(2);
MP_ASSERT_OK(graph.AddPacketToInputStream(
"multiplier", Adopt(multiplier1.release()).At(input_timestamp1)));
auto input_vector2 = absl::make_unique<std::vector<int>>();
input_vector2->emplace_back(2);
input_vector2->emplace_back(3);
Timestamp input_timestamp2 = Timestamp(44); Timestamp input_timestamp2 = Timestamp(44);
MP_ASSERT_OK(graph.AddPacketToInputStream( SendPackets(input_timestamp2, /*multiplier=*/3, /*ints=*/{2, 3});
"ints", Adopt(input_vector2.release()).At(input_timestamp2)));
auto multiplier2 = absl::make_unique<int>(3);
MP_ASSERT_OK(graph.AddPacketToInputStream(
"multiplier", Adopt(multiplier2.release()).At(input_timestamp2)));
MP_ASSERT_OK(graph.CloseAllPacketSources()); MP_ASSERT_OK(graph_.CloseAllPacketSources());
MP_ASSERT_OK(graph.WaitUntilDone()); MP_ASSERT_OK(graph_.WaitUntilDone());
ASSERT_EQ(2, output_packets_.size());
EXPECT_EQ(input_timestamp0, output_packets_[0].Timestamp());
std::vector<int> expected_output_vector0 = {0, 2};
EXPECT_EQ(expected_output_vector0,
output_packets_[0].Get<std::vector<int>>());
// At input_timestamp1, EndLoopCalc will forward timestamp bound as there are // At input_timestamp1, EndLoopCalc will forward timestamp bound as there are
// no elements in vector to process. // no elements in vector to process.
EXPECT_THAT(output_packets_,
EXPECT_EQ(input_timestamp2, output_packets_[1].Timestamp()); testing::ElementsAre(
std::vector<int> expected_output_vector2 = {6, 9}; PacketOfIntsEq(input_timestamp0, std::vector<int>{0, 2}),
EXPECT_EQ(expected_output_vector2, PacketOfIntsEq(input_timestamp2, std::vector<int>{6, 9})));
output_packets_[1].Get<std::vector<int>>());
} }
} // namespace } // namespace
@@ -16,7 +16,9 @@
#include <vector> #include <vector>
#include "mediapipe/framework/formats/detection.pb.h"
#include "mediapipe/framework/formats/landmark.pb.h" #include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/formats/matrix.h"
#include "mediapipe/framework/formats/rect.pb.h" #include "mediapipe/framework/formats/rect.pb.h"
namespace mediapipe { namespace mediapipe {
@@ -31,4 +33,13 @@ typedef BeginLoopCalculator<std::vector<::mediapipe::NormalizedRect>>
BeginLoopNormalizedRectCalculator; BeginLoopNormalizedRectCalculator;
REGISTER_CALCULATOR(BeginLoopNormalizedRectCalculator); REGISTER_CALCULATOR(BeginLoopNormalizedRectCalculator);
// A calculator to process std::vector<Detection>.
typedef BeginLoopCalculator<std::vector<::mediapipe::Detection>>
BeginLoopDetectionCalculator;
REGISTER_CALCULATOR(BeginLoopDetectionCalculator);
// A calculator to process std::vector<Matrix>.
typedef BeginLoopCalculator<std::vector<Matrix>> BeginLoopMatrixCalculator;
REGISTER_CALCULATOR(BeginLoopMatrixCalculator);
} // namespace mediapipe } // namespace mediapipe
@@ -52,20 +52,28 @@ namespace mediapipe {
// output_stream: "OUTPUT:aggregated_result" # IterableU @ext_ts // output_stream: "OUTPUT:aggregated_result" # IterableU @ext_ts
// } // }
// //
// BeginLoopCalculator accepts an optional input stream tagged with "TICK" // Input streams tagged with "CLONE" are cloned to the corresponding output
// which if non-empty, wakes up the calculator and calls // streams at loop timestamps. This ensures that a MediaPipe graph or sub-graph
// BeginLoopCalculator::Process(). Input streams tagged with "CLONE" are cloned // can run multiple times, once per element in the "ITERABLE" for each pakcet
// to the corresponding output streams at loop timestamps. This ensures that a // clone of the packets in the "CLONE" input streams.
// MediaPipe graph or sub-graph can run multiple times, once per element in the
// "ITERABLE" for each pakcet clone of the packets in the "CLONE" input streams.
template <typename IterableT> template <typename IterableT>
class BeginLoopCalculator : public CalculatorBase { class BeginLoopCalculator : public CalculatorBase {
using ItemT = typename IterableT::value_type; using ItemT = typename IterableT::value_type;
public: public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) { static ::mediapipe::Status GetContract(CalculatorContract* cc) {
// The below enables processing of timestamp bound updates, and that enables
// correct timestamp propagation by the companion EndLoopCalculator.
//
// For instance, Process() function will be still invoked even if upstream
// calculator has updated timestamp bound for ITERABLE input instead of
// providing actual value.
cc->SetProcessTimestampBounds(true);
// A non-empty packet in the optional "TICK" input stream wakes up the // A non-empty packet in the optional "TICK" input stream wakes up the
// calculator. // calculator.
// DEPRECATED as timestamp bound updates are processed by default in this
// calculator.
if (cc->Inputs().HasTag("TICK")) { if (cc->Inputs().HasTag("TICK")) {
cc->Inputs().Tag("TICK").SetAny(); cc->Inputs().Tag("TICK").SetAny();
} }
@@ -17,6 +17,7 @@
#include <vector> #include <vector>
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/detection.pb.h"
#include "mediapipe/framework/formats/rect.pb.h" #include "mediapipe/framework/formats/rect.pb.h"
namespace mediapipe { namespace mediapipe {
@@ -25,4 +26,8 @@ typedef ClipVectorSizeCalculator<::mediapipe::NormalizedRect>
ClipNormalizedRectVectorSizeCalculator; ClipNormalizedRectVectorSizeCalculator;
REGISTER_CALCULATOR(ClipNormalizedRectVectorSizeCalculator); REGISTER_CALCULATOR(ClipNormalizedRectVectorSizeCalculator);
typedef ClipVectorSizeCalculator<::mediapipe::Detection>
ClipDetectionVectorSizeCalculator;
REGISTER_CALCULATOR(ClipDetectionVectorSizeCalculator);
} // namespace mediapipe } // namespace mediapipe
@@ -16,9 +16,11 @@
#include <vector> #include <vector>
#include "mediapipe/framework/formats/classification.pb.h"
#include "mediapipe/framework/formats/landmark.pb.h" #include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/formats/rect.pb.h" #include "mediapipe/framework/formats/rect.pb.h"
#include "mediapipe/util/render_data.pb.h" #include "mediapipe/util/render_data.pb.h"
#include "tensorflow/lite/interpreter.h"
namespace mediapipe { namespace mediapipe {
@@ -37,4 +39,11 @@ typedef EndLoopCalculator<std::vector<::mediapipe::RenderData>>
EndLoopRenderDataCalculator; EndLoopRenderDataCalculator;
REGISTER_CALCULATOR(EndLoopRenderDataCalculator); REGISTER_CALCULATOR(EndLoopRenderDataCalculator);
typedef EndLoopCalculator<std::vector<::mediapipe::ClassificationList>>
EndLoopClassificationListCalculator;
REGISTER_CALCULATOR(EndLoopClassificationListCalculator);
typedef EndLoopCalculator<std::vector<TfLiteTensor>> EndLoopTensorCalculator;
REGISTER_CALCULATOR(EndLoopTensorCalculator);
} // namespace mediapipe } // namespace mediapipe
@@ -25,13 +25,17 @@ namespace mediapipe {
// together with some previous output. // together with some previous output.
// //
// For the first packet that arrives on the MAIN input, the timestamp bound is // For the first packet that arrives on the MAIN input, the timestamp bound is
// advanced on the output. Downstream calculators will see this as an empty // advanced on the PREV_LOOP. Downstream calculators will see this as an empty
// packet. This way they are not kept waiting for the previous output, which // packet. This way they are not kept waiting for the previous output, which
// for the first iteration does not exist. // for the first iteration does not exist.
// //
// Thereafter, each packet received on MAIN is matched with a packet received // Thereafter,
// on LOOP; the LOOP packet's timestamp is changed to that of the MAIN packet, // - Each non-empty MAIN packet results in:
// and it is output on PREV_LOOP. // a) a PREV_LOOP packet with contents of the LOOP packet received at the
// timestamp of the previous non-empty MAIN packet
// b) or in a PREV_LOOP timestamp bound update if the LOOP packet was empty.
// - Each empty MAIN packet indicating timestamp bound update results in a
// PREV_LOOP timestamp bound update.
// //
// Example config: // Example config:
// node { // node {
@@ -56,83 +60,115 @@ class PreviousLoopbackCalculator : public CalculatorBase {
// TODO: an optional PREV_TIMESTAMP output could be added to // TODO: an optional PREV_TIMESTAMP output could be added to
// carry the original timestamp of the packet on PREV_LOOP. // carry the original timestamp of the packet on PREV_LOOP.
cc->SetInputStreamHandler("ImmediateInputStreamHandler"); cc->SetInputStreamHandler("ImmediateInputStreamHandler");
// Process() function is invoked in response to MAIN/LOOP stream timestamp
// bound updates.
cc->SetProcessTimestampBounds(true);
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
::mediapipe::Status Open(CalculatorContext* cc) final { ::mediapipe::Status Open(CalculatorContext* cc) final {
main_id_ = cc->Inputs().GetId("MAIN", 0); main_id_ = cc->Inputs().GetId("MAIN", 0);
loop_id_ = cc->Inputs().GetId("LOOP", 0); loop_id_ = cc->Inputs().GetId("LOOP", 0);
loop_out_id_ = cc->Outputs().GetId("PREV_LOOP", 0); prev_loop_id_ = cc->Outputs().GetId("PREV_LOOP", 0);
cc->Outputs() cc->Outputs()
.Get(loop_out_id_) .Get(prev_loop_id_)
.SetHeader(cc->Inputs().Get(loop_id_).Header()); .SetHeader(cc->Inputs().Get(loop_id_).Header());
// Use an empty packet for the first round, since there is no previous
// output.
loopback_packets_.push_back({});
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
::mediapipe::Status Process(CalculatorContext* cc) final { ::mediapipe::Status Process(CalculatorContext* cc) final {
Packet& main_packet = cc->Inputs().Get(main_id_).Value(); // Non-empty packets and empty packets indicating timestamp bound updates
if (!main_packet.IsEmpty()) { // are guaranteed to have timestamps greater than timestamps of previous
main_ts_.push_back(main_packet.Timestamp()); // packets within the same stream. Calculator tracks and operates on such
} // packets.
Packet& loopback_packet = cc->Inputs().Get(loop_id_).Value();
if (!loopback_packet.IsEmpty()) {
loopback_packets_.push_back(loopback_packet);
while (!main_ts_.empty() &&
main_ts_.front() <= loopback_packets_.front().Timestamp()) {
main_ts_.pop_front();
}
}
auto& loop_out = cc->Outputs().Get(loop_out_id_);
while (!main_ts_.empty() && !loopback_packets_.empty()) { const Packet& main_packet = cc->Inputs().Get(main_id_).Value();
Timestamp main_timestamp = main_ts_.front(); if (prev_main_ts_ < main_packet.Timestamp()) {
main_ts_.pop_front(); Timestamp loop_timestamp;
Packet previous_loopback = loopback_packets_.front().At(main_timestamp); if (!main_packet.IsEmpty()) {
loopback_packets_.pop_front(); loop_timestamp = prev_non_empty_main_ts_;
prev_non_empty_main_ts_ = main_packet.Timestamp();
if (previous_loopback.IsEmpty()) {
// TODO: SetCompleteTimestampBound would be more useful.
loop_out.SetNextTimestampBound(main_timestamp + 1);
} else { } else {
loop_out.AddPacket(std::move(previous_loopback)); // Calculator advances PREV_LOOP timestamp bound in response to empty
// MAIN packet, hence not caring about corresponding loop packet.
loop_timestamp = Timestamp::Unset();
}
main_packet_specs_.push_back({.timestamp = main_packet.Timestamp(),
.loop_timestamp = loop_timestamp});
prev_main_ts_ = main_packet.Timestamp();
}
const Packet& loop_packet = cc->Inputs().Get(loop_id_).Value();
if (prev_loop_ts_ < loop_packet.Timestamp()) {
loop_packets_.push_back(loop_packet);
prev_loop_ts_ = loop_packet.Timestamp();
}
auto& prev_loop = cc->Outputs().Get(prev_loop_id_);
while (!main_packet_specs_.empty() && !loop_packets_.empty()) {
// The earliest MAIN packet.
const MainPacketSpec& main_spec = main_packet_specs_.front();
// The earliest LOOP packet.
const Packet& loop_candidate = loop_packets_.front();
// Match LOOP and MAIN packets.
if (main_spec.loop_timestamp < loop_candidate.Timestamp()) {
// No LOOP packet can match the MAIN packet under review.
prev_loop.SetNextTimestampBound(main_spec.timestamp + 1);
main_packet_specs_.pop_front();
} else if (main_spec.loop_timestamp > loop_candidate.Timestamp()) {
// No MAIN packet can match the LOOP packet under review.
loop_packets_.pop_front();
} else {
// Exact match found.
if (loop_candidate.IsEmpty()) {
// However, LOOP packet is empty.
prev_loop.SetNextTimestampBound(main_spec.timestamp + 1);
} else {
prev_loop.AddPacket(loop_candidate.At(main_spec.timestamp));
}
loop_packets_.pop_front();
main_packet_specs_.pop_front();
} }
} }
// In case of an empty loopback input, the next timestamp bound for if (main_packet_specs_.empty() && cc->Inputs().Get(main_id_).IsDone()) {
// loopback input is the loopback timestamp + 1. The next timestamp bound prev_loop.Close();
// for output is set and the main_ts_ vector is truncated accordingly.
if (loopback_packet.IsEmpty() &&
loopback_packet.Timestamp() != Timestamp::Unstarted()) {
Timestamp loopback_bound =
loopback_packet.Timestamp().NextAllowedInStream();
while (!main_ts_.empty() && main_ts_.front() <= loopback_bound) {
main_ts_.pop_front();
}
if (main_ts_.empty()) {
loop_out.SetNextTimestampBound(loopback_bound.NextAllowedInStream());
}
}
if (!main_ts_.empty()) {
loop_out.SetNextTimestampBound(main_ts_.front());
}
if (cc->Inputs().Get(main_id_).IsDone() && main_ts_.empty()) {
loop_out.Close();
} }
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
private: private:
struct MainPacketSpec {
Timestamp timestamp;
// Expected timestamp of the packet from LOOP stream that corresponds to the
// packet from MAIN stream descirbed by this spec.
Timestamp loop_timestamp;
};
CollectionItemId main_id_; CollectionItemId main_id_;
CollectionItemId loop_id_; CollectionItemId loop_id_;
CollectionItemId loop_out_id_; CollectionItemId prev_loop_id_;
std::deque<Timestamp> main_ts_; // Contains specs for MAIN packets which only can be:
std::deque<Packet> loopback_packets_; // - non-empty packets
// - empty packets indicating timestamp bound updates
//
// Sorted according to packet timestamps.
std::deque<MainPacketSpec> main_packet_specs_;
Timestamp prev_main_ts_ = Timestamp::Unstarted();
Timestamp prev_non_empty_main_ts_ = Timestamp::Unstarted();
// Contains LOOP packets which only can be:
// - the very first empty packet
// - non empty packets
// - empty packets indicating timestamp bound updates
//
// Sorted according to packet timestamps.
std::deque<Packet> loop_packets_;
// Using "Timestamp::Unset" instead of "Timestamp::Unstarted" in order to
// allow addition of the very first empty packet (which doesn't indicate
// timestamp bound change necessarily).
Timestamp prev_loop_ts_ = Timestamp::Unset();
}; };
REGISTER_CALCULATOR(PreviousLoopbackCalculator); REGISTER_CALCULATOR(PreviousLoopbackCalculator);
@@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
#include <algorithm> #include <algorithm>
#include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -25,12 +26,17 @@
#include "mediapipe/framework/port/gtest.h" #include "mediapipe/framework/port/gtest.h"
#include "mediapipe/framework/port/integral_types.h" #include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/parse_text_proto.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/port/status_matchers.h"
#include "mediapipe/framework/timestamp.h" #include "mediapipe/framework/timestamp.h"
#include "mediapipe/framework/tool/sink.h" #include "mediapipe/framework/tool/sink.h"
namespace mediapipe { namespace mediapipe {
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Pair;
using ::testing::Value;
namespace { namespace {
// Returns the timestamp values for a vector of Packets. // Returns the timestamp values for a vector of Packets.
@@ -43,6 +49,23 @@ std::vector<int64> TimestampValues(const std::vector<Packet>& packets) {
return result; return result;
} }
MATCHER(EmptyPacket, negation ? "isn't empty" : "is empty") {
if (arg.IsEmpty()) {
return true;
}
return false;
}
MATCHER_P(IntPacket, value, "") {
return Value(arg.template Get<int>(), Eq(value));
}
MATCHER_P2(PairPacket, timestamp, pair, "") {
Timestamp actual_timestamp = arg.Timestamp();
const auto& actual_pair = arg.template Get<std::pair<Packet, Packet>>();
return Value(actual_timestamp, Eq(timestamp)) && Value(actual_pair, pair);
}
TEST(PreviousLoopbackCalculator, CorrectTimestamps) { TEST(PreviousLoopbackCalculator, CorrectTimestamps) {
std::vector<Packet> in_prev; std::vector<Packet> in_prev;
CalculatorGraphConfig graph_config_ = CalculatorGraphConfig graph_config_ =
@@ -81,32 +104,30 @@ TEST(PreviousLoopbackCalculator, CorrectTimestamps) {
MP_EXPECT_OK(graph_.AddPacketToInputStream( MP_EXPECT_OK(graph_.AddPacketToInputStream(
input_name, MakePacket<int>(n).At(Timestamp(n)))); input_name, MakePacket<int>(n).At(Timestamp(n))));
}; };
auto pair_values = [](const Packet& packet) {
auto pair = packet.Get<std::pair<Packet, Packet>>();
int first = pair.first.IsEmpty() ? -1 : pair.first.Get<int>();
int second = pair.second.IsEmpty() ? -1 : pair.second.Get<int>();
return std::make_pair(first, second);
};
send_packet("in", 1); send_packet("in", 1);
MP_EXPECT_OK(graph_.WaitUntilIdle()); MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_EQ(TimestampValues(in_prev), (std::vector<int64>{1})); EXPECT_THAT(TimestampValues(in_prev), ElementsAre(1));
EXPECT_EQ(pair_values(in_prev.back()), std::make_pair(1, -1)); EXPECT_THAT(in_prev.back(),
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())));
send_packet("in", 2); send_packet("in", 2);
MP_EXPECT_OK(graph_.WaitUntilIdle()); MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_EQ(TimestampValues(in_prev), (std::vector<int64>{1, 2})); EXPECT_THAT(TimestampValues(in_prev), ElementsAre(1, 2));
EXPECT_EQ(pair_values(in_prev.back()), std::make_pair(2, 1)); EXPECT_THAT(in_prev.back(),
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1))));
send_packet("in", 5); send_packet("in", 5);
MP_EXPECT_OK(graph_.WaitUntilIdle()); MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_EQ(TimestampValues(in_prev), (std::vector<int64>{1, 2, 5})); EXPECT_THAT(TimestampValues(in_prev), ElementsAre(1, 2, 5));
EXPECT_EQ(pair_values(in_prev.back()), std::make_pair(5, 2)); EXPECT_THAT(in_prev.back(),
PairPacket(Timestamp(5), Pair(IntPacket(5), IntPacket(2))));
send_packet("in", 15); send_packet("in", 15);
MP_EXPECT_OK(graph_.WaitUntilIdle()); MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_EQ(TimestampValues(in_prev), (std::vector<int64>{1, 2, 5, 15})); EXPECT_THAT(TimestampValues(in_prev), ElementsAre(1, 2, 5, 15));
EXPECT_EQ(pair_values(in_prev.back()), std::make_pair(15, 5)); EXPECT_THAT(in_prev.back(),
PairPacket(Timestamp(15), Pair(IntPacket(15), IntPacket(5))));
MP_EXPECT_OK(graph_.CloseAllInputStreams()); MP_EXPECT_OK(graph_.CloseAllInputStreams());
MP_EXPECT_OK(graph_.WaitUntilDone()); MP_EXPECT_OK(graph_.WaitUntilDone());
@@ -185,24 +206,24 @@ TEST(PreviousLoopbackCalculator, ClosesCorrectly) {
send_packet("in", 1); send_packet("in", 1);
MP_EXPECT_OK(graph_.WaitUntilIdle()); MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_EQ(TimestampValues(outputs), (std::vector<int64>{1})); EXPECT_THAT(TimestampValues(outputs), ElementsAre(1));
send_packet("in", 2); send_packet("in", 2);
MP_EXPECT_OK(graph_.WaitUntilIdle()); MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_EQ(TimestampValues(outputs), (std::vector<int64>{1, 2})); EXPECT_THAT(TimestampValues(outputs), ElementsAre(1, 2));
send_packet("in", 5); send_packet("in", 5);
MP_EXPECT_OK(graph_.WaitUntilIdle()); MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_EQ(TimestampValues(outputs), (std::vector<int64>{1, 2, 5})); EXPECT_THAT(TimestampValues(outputs), ElementsAre(1, 2, 5));
send_packet("in", 15); send_packet("in", 15);
MP_EXPECT_OK(graph_.WaitUntilIdle()); MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_EQ(TimestampValues(outputs), (std::vector<int64>{1, 2, 5, 15})); EXPECT_THAT(TimestampValues(outputs), ElementsAre(1, 2, 5, 15));
MP_EXPECT_OK(graph_.CloseAllInputStreams()); MP_EXPECT_OK(graph_.CloseAllInputStreams());
MP_EXPECT_OK(graph_.WaitUntilIdle()); MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_EQ(TimestampValues(outputs), EXPECT_THAT(TimestampValues(outputs),
(std::vector<int64>{1, 2, 5, 15, Timestamp::Max().Value()})); ElementsAre(1, 2, 5, 15, Timestamp::Max().Value()));
MP_EXPECT_OK(graph_.WaitUntilDone()); MP_EXPECT_OK(graph_.WaitUntilDone());
} }
@@ -247,16 +268,12 @@ TEST(PreviousLoopbackCalculator, EmptyLoopForever) {
input_name, MakePacket<int>(n).At(Timestamp(n)))); input_name, MakePacket<int>(n).At(Timestamp(n))));
}; };
send_packet("in", 0); for (int main_ts = 0; main_ts < 50; ++main_ts) {
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_EQ(TimestampValues(outputs), (std::vector<int64>{0}));
for (int main_ts = 1; main_ts < 50; ++main_ts) {
send_packet("in", main_ts); send_packet("in", main_ts);
MP_EXPECT_OK(graph_.WaitUntilIdle()); MP_EXPECT_OK(graph_.WaitUntilIdle());
std::vector<int64> ts_values = TimestampValues(outputs); std::vector<int64> ts_values = TimestampValues(outputs);
EXPECT_EQ(ts_values.size(), main_ts + 1); EXPECT_EQ(ts_values.size(), main_ts + 1);
for (int j = 0; j < main_ts; ++j) { for (int j = 0; j < main_ts + 1; ++j) {
EXPECT_EQ(ts_values[j], j); EXPECT_EQ(ts_values[j], j);
} }
} }
@@ -266,5 +283,487 @@ TEST(PreviousLoopbackCalculator, EmptyLoopForever) {
MP_EXPECT_OK(graph_.WaitUntilDone()); MP_EXPECT_OK(graph_.WaitUntilDone());
} }
class PreviousLoopbackCalculatorProcessingTimestampsTest
: public testing::Test {
protected:
void SetUp() override {
CalculatorGraphConfig graph_config =
ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
input_stream: 'input'
input_stream: 'force_main_empty'
input_stream: 'force_loop_empty'
# Used to indicate "main" timestamp bound updates.
node {
calculator: 'GateCalculator'
input_stream: 'input'
input_stream: 'DISALLOW:force_main_empty'
output_stream: 'main'
}
node {
calculator: 'PreviousLoopbackCalculator'
input_stream: 'MAIN:main'
input_stream: 'LOOP:loop'
input_stream_info: { tag_index: 'LOOP' back_edge: true }
output_stream: 'PREV_LOOP:prev_loop'
}
node {
calculator: 'PassThroughCalculator'
input_stream: 'input'
input_stream: 'prev_loop'
output_stream: 'passed_through_input'
output_stream: 'passed_through_prev_loop'
}
# Used to indicate "loop" timestamp bound updates.
node {
calculator: 'GateCalculator'
input_stream: 'input'
input_stream: 'DISALLOW:force_loop_empty'
output_stream: 'loop'
}
node {
calculator: 'MakePairCalculator'
input_stream: 'passed_through_input'
input_stream: 'passed_through_prev_loop'
output_stream: 'passed_through_input_and_prev_loop'
}
)");
tool::AddVectorSink("passed_through_input_and_prev_loop", &graph_config,
&output_packets_);
MP_ASSERT_OK(graph_.Initialize(graph_config, {}));
MP_ASSERT_OK(graph_.StartRun({}));
}
void SendPackets(int timestamp, int input, bool force_main_empty,
bool force_loop_empty) {
MP_ASSERT_OK(graph_.AddPacketToInputStream(
"input", MakePacket<int>(input).At(Timestamp(timestamp))));
MP_ASSERT_OK(graph_.AddPacketToInputStream(
"force_main_empty",
MakePacket<bool>(force_main_empty).At(Timestamp(timestamp))));
MP_ASSERT_OK(graph_.AddPacketToInputStream(
"force_loop_empty",
MakePacket<bool>(force_loop_empty).At(Timestamp(timestamp))));
}
CalculatorGraph graph_;
std::vector<Packet> output_packets_;
};
TEST_F(PreviousLoopbackCalculatorProcessingTimestampsTest,
MultiplePacketsEmptyMainNonEmptyLoop) {
SendPackets(/*timestamp=*/1, /*input=*/1, /*force_main_empty=*/true,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
SendPackets(/*timestamp=*/2, /*input=*/2, /*force_main_empty=*/true,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket()))));
SendPackets(/*timestamp=*/3, /*input=*/3, /*force_main_empty=*/true,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket()))));
SendPackets(/*timestamp=*/5, /*input=*/5, /*force_main_empty=*/true,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket()))));
SendPackets(/*timestamp=*/15, /*input=*/15,
/*force_main_empty=*/true,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket())),
PairPacket(Timestamp(15), Pair(IntPacket(15), EmptyPacket()))));
MP_EXPECT_OK(graph_.CloseAllInputStreams());
MP_EXPECT_OK(graph_.WaitUntilDone());
}
TEST_F(PreviousLoopbackCalculatorProcessingTimestampsTest,
MultiplePacketsNonEmptyMainEmptyLoop) {
SendPackets(/*timestamp=*/1, /*input=*/1,
/*force_main_empty=*/false,
/*force_loop_empty=*/true);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
SendPackets(/*timestamp=*/2, /*input=*/2,
/*force_main_empty=*/false,
/*force_loop_empty=*/true);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket()))));
SendPackets(/*timestamp=*/3, /*input=*/3,
/*force_main_empty=*/false,
/*force_loop_empty=*/true);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket()))));
SendPackets(/*timestamp=*/5, /*input=*/5,
/*force_main_empty=*/false,
/*force_loop_empty=*/true);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket()))));
SendPackets(/*timestamp=*/15, /*input=*/15,
/*force_main_empty=*/false,
/*force_loop_empty=*/true);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket())),
PairPacket(Timestamp(15), Pair(IntPacket(15), EmptyPacket()))));
MP_EXPECT_OK(graph_.CloseAllInputStreams());
MP_EXPECT_OK(graph_.WaitUntilDone());
}
TEST_F(PreviousLoopbackCalculatorProcessingTimestampsTest,
MultiplePacketsAlteringMainNonEmptyLoop) {
SendPackets(/*timestamp=*/1, /*input=*/1,
/*force_main_empty=*/false,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
SendPackets(/*timestamp=*/2, /*input=*/2, /*force_main_empty=*/true,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket()))));
SendPackets(/*timestamp=*/3, /*input=*/3,
/*force_main_empty=*/false,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
PairPacket(Timestamp(3), Pair(IntPacket(3), IntPacket(1)))));
SendPackets(/*timestamp=*/5, /*input=*/5,
/*force_main_empty=*/false,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
PairPacket(Timestamp(3), Pair(IntPacket(3), IntPacket(1))),
PairPacket(Timestamp(5), Pair(IntPacket(5), IntPacket(3)))));
SendPackets(/*timestamp=*/15, /*input=*/15,
/*force_main_empty=*/true,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
PairPacket(Timestamp(3), Pair(IntPacket(3), IntPacket(1))),
PairPacket(Timestamp(5), Pair(IntPacket(5), IntPacket(3))),
PairPacket(Timestamp(15), Pair(IntPacket(15), EmptyPacket()))));
MP_EXPECT_OK(graph_.CloseAllInputStreams());
MP_EXPECT_OK(graph_.WaitUntilDone());
}
TEST_F(PreviousLoopbackCalculatorProcessingTimestampsTest,
MultiplePacketsNonEmptyMainAlteringLoop) {
SendPackets(/*timestamp=*/1, /*input=*/1,
/*force_main_empty=*/false,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
SendPackets(/*timestamp=*/2, /*input=*/2,
/*force_main_empty=*/false,
/*force_loop_empty=*/true);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1)))));
SendPackets(/*timestamp=*/3, /*input=*/3,
/*force_main_empty=*/false,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1))),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket()))));
SendPackets(/*timestamp=*/5, /*input=*/5,
/*force_main_empty=*/false,
/*force_loop_empty=*/true);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1))),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
PairPacket(Timestamp(5), Pair(IntPacket(5), IntPacket(3)))));
SendPackets(/*timestamp=*/15, /*input=*/15,
/*force_main_empty=*/false,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1))),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
PairPacket(Timestamp(5), Pair(IntPacket(5), IntPacket(3))),
PairPacket(Timestamp(15), Pair(IntPacket(15), EmptyPacket()))));
MP_EXPECT_OK(graph_.CloseAllInputStreams());
MP_EXPECT_OK(graph_.WaitUntilDone());
}
TEST_F(PreviousLoopbackCalculatorProcessingTimestampsTest,
MultiplePacketsCheckIfLastCorrectAlteringMainAlteringLoop) {
int num_packets = 1000;
for (int i = 0; i < num_packets; ++i) {
bool force_main_empty = i % 3 == 0 ? true : false;
bool force_loop_empty = i % 2 == 0 ? true : false;
SendPackets(/*timestamp=*/i + 1, /*input=*/i + 1, force_main_empty,
force_loop_empty);
}
SendPackets(/*timestamp=*/num_packets + 1,
/*input=*/num_packets + 1, /*force_main_empty=*/false,
/*force_loop_empty=*/false);
SendPackets(/*timestamp=*/num_packets + 2,
/*input=*/num_packets + 2, /*force_main_empty=*/false,
/*force_loop_empty=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
ASSERT_FALSE(output_packets_.empty());
EXPECT_THAT(
output_packets_.back(),
PairPacket(Timestamp(num_packets + 2),
Pair(IntPacket(num_packets + 2), IntPacket(num_packets + 1))));
MP_EXPECT_OK(graph_.CloseAllInputStreams());
MP_EXPECT_OK(graph_.WaitUntilDone());
}
// Similar to GateCalculator, but it doesn't propagate timestamp bound updates.
class DroppingGateCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Index(0).SetAny();
cc->Inputs().Tag("DISALLOW").Set<bool>();
cc->Outputs().Index(0).SetSameAs(&cc->Inputs().Index(0));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) final {
if (!cc->Inputs().Index(0).IsEmpty() &&
!cc->Inputs().Tag("DISALLOW").Get<bool>()) {
cc->Outputs().Index(0).AddPacket(cc->Inputs().Index(0).Value());
}
return ::mediapipe::OkStatus();
}
};
REGISTER_CALCULATOR(DroppingGateCalculator);
// Tests PreviousLoopbackCalculator in cases when there are no "LOOP" timestamp
// bound updates and non-empty packets for a while and the aforementioned start
// to arrive at some point. So, "PREV_LOOP" is delayed for a couple of inputs.
class PreviousLoopbackCalculatorDelayBehaviorTest : public testing::Test {
protected:
void SetUp() override {
CalculatorGraphConfig graph_config =
ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
input_stream: 'input'
# Drops "loop" when set to "true", delaying output of prev_loop, hence
# delaying output of the graph.
input_stream: 'delay_next_output'
node {
calculator: 'PreviousLoopbackCalculator'
input_stream: 'MAIN:input'
input_stream: 'LOOP:loop'
input_stream_info: { tag_index: 'LOOP' back_edge: true }
output_stream: 'PREV_LOOP:prev_loop'
}
node {
calculator: 'PassThroughCalculator'
input_stream: 'input'
input_stream: 'prev_loop'
output_stream: 'passed_through_input'
output_stream: 'passed_through_prev_loop'
}
node {
calculator: 'DroppingGateCalculator'
input_stream: 'input'
input_stream: 'DISALLOW:delay_next_output'
output_stream: 'loop'
}
node {
calculator: 'MakePairCalculator'
input_stream: 'passed_through_input'
input_stream: 'passed_through_prev_loop'
output_stream: 'passed_through_input_and_prev_loop'
}
)");
tool::AddVectorSink("passed_through_input_and_prev_loop", &graph_config,
&output_packets_);
MP_ASSERT_OK(graph_.Initialize(graph_config, {}));
MP_ASSERT_OK(graph_.StartRun({}));
}
void SendPackets(int timestamp, int input, bool delay_next_output) {
MP_ASSERT_OK(graph_.AddPacketToInputStream(
"input", MakePacket<int>(input).At(Timestamp(timestamp))));
MP_ASSERT_OK(graph_.AddPacketToInputStream(
"delay_next_output",
MakePacket<bool>(delay_next_output).At(Timestamp(timestamp))));
}
CalculatorGraph graph_;
std::vector<Packet> output_packets_;
};
TEST_F(PreviousLoopbackCalculatorDelayBehaviorTest, MultipleDelayedOutputs) {
SendPackets(/*timestamp=*/1, /*input=*/1, /*delay_next_output=*/true);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
SendPackets(/*timestamp=*/2, /*input=*/2, /*delay_next_output=*/true);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
SendPackets(/*timestamp=*/3, /*input=*/3, /*delay_next_output=*/true);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
SendPackets(/*timestamp=*/5, /*input=*/5, /*delay_next_output=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket()))));
SendPackets(/*timestamp=*/15, /*input=*/15, /*delay_next_output=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), EmptyPacket())),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket())),
PairPacket(Timestamp(15), Pair(IntPacket(15), IntPacket(5)))));
MP_EXPECT_OK(graph_.CloseAllInputStreams());
MP_EXPECT_OK(graph_.WaitUntilDone());
}
TEST_F(PreviousLoopbackCalculatorDelayBehaviorTest,
NonDelayedOutputFollowedByMultipleDelayedOutputs) {
SendPackets(/*timestamp=*/1, /*input=*/1, /*delay_next_output=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket()))));
SendPackets(/*timestamp=*/2, /*input=*/2, /*delay_next_output=*/true);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1)))));
SendPackets(/*timestamp=*/3, /*input=*/3, /*delay_next_output=*/true);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1)))));
SendPackets(/*timestamp=*/5, /*input=*/5, /*delay_next_output=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1))),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket()))));
SendPackets(/*timestamp=*/15, /*input=*/15, /*delay_next_output=*/false);
MP_EXPECT_OK(graph_.WaitUntilIdle());
EXPECT_THAT(
output_packets_,
ElementsAre(
PairPacket(Timestamp(1), Pair(IntPacket(1), EmptyPacket())),
PairPacket(Timestamp(2), Pair(IntPacket(2), IntPacket(1))),
PairPacket(Timestamp(3), Pair(IntPacket(3), EmptyPacket())),
PairPacket(Timestamp(5), Pair(IntPacket(5), EmptyPacket())),
PairPacket(Timestamp(15), Pair(IntPacket(15), IntPacket(5)))));
MP_EXPECT_OK(graph_.CloseAllInputStreams());
MP_EXPECT_OK(graph_.WaitUntilDone());
}
} // anonymous namespace } // anonymous namespace
} // namespace mediapipe } // namespace mediapipe
@@ -16,7 +16,9 @@
#include <vector> #include <vector>
#include "mediapipe/framework/formats/detection.pb.h"
#include "mediapipe/framework/formats/landmark.pb.h" #include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/formats/matrix.h"
#include "mediapipe/framework/formats/rect.pb.h" #include "mediapipe/framework/formats/rect.pb.h"
#include "tensorflow/lite/interpreter.h" #include "tensorflow/lite/interpreter.h"
@@ -48,13 +50,25 @@ typedef SplitVectorCalculator<::mediapipe::NormalizedLandmark, false>
SplitLandmarkVectorCalculator; SplitLandmarkVectorCalculator;
REGISTER_CALCULATOR(SplitLandmarkVectorCalculator); REGISTER_CALCULATOR(SplitLandmarkVectorCalculator);
typedef SplitVectorCalculator<::mediapipe::NormalizedLandmarkList, false>
SplitNormalizedLandmarkListVectorCalculator;
REGISTER_CALCULATOR(SplitNormalizedLandmarkListVectorCalculator);
typedef SplitVectorCalculator<::mediapipe::NormalizedRect, false> typedef SplitVectorCalculator<::mediapipe::NormalizedRect, false>
SplitNormalizedRectVectorCalculator; SplitNormalizedRectVectorCalculator;
REGISTER_CALCULATOR(SplitNormalizedRectVectorCalculator); REGISTER_CALCULATOR(SplitNormalizedRectVectorCalculator);
typedef SplitVectorCalculator<Matrix, false> SplitMatrixVectorCalculator;
REGISTER_CALCULATOR(SplitMatrixVectorCalculator);
#if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE) #if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE)
typedef SplitVectorCalculator<::tflite::gpu::gl::GlBuffer, true> typedef SplitVectorCalculator<::tflite::gpu::gl::GlBuffer, true>
MovableSplitGlBufferVectorCalculator; MovableSplitGlBufferVectorCalculator;
REGISTER_CALCULATOR(MovableSplitGlBufferVectorCalculator); REGISTER_CALCULATOR(MovableSplitGlBufferVectorCalculator);
#endif #endif
typedef SplitVectorCalculator<::mediapipe::Detection, false>
SplitDetectionVectorCalculator;
REGISTER_CALCULATOR(SplitDetectionVectorCalculator);
} // namespace mediapipe } // namespace mediapipe
+60
View File
@@ -86,6 +86,14 @@ mediapipe_cc_proto_library(
deps = [":opencv_image_encoder_calculator_proto"], deps = [":opencv_image_encoder_calculator_proto"],
) )
mediapipe_cc_proto_library(
name = "opencv_encoded_image_to_image_frame_calculator_cc_proto",
srcs = ["opencv_encoded_image_to_image_frame_calculator.proto"],
cc_deps = ["//mediapipe/framework:calculator_cc_proto"],
visibility = ["//visibility:public"],
deps = [":opencv_encoded_image_to_image_frame_calculator_proto"],
)
mediapipe_cc_proto_library( mediapipe_cc_proto_library(
name = "mask_overlay_calculator_cc_proto", name = "mask_overlay_calculator_cc_proto",
srcs = ["mask_overlay_calculator.proto"], srcs = ["mask_overlay_calculator.proto"],
@@ -172,6 +180,7 @@ cc_library(
srcs = ["opencv_encoded_image_to_image_frame_calculator.cc"], srcs = ["opencv_encoded_image_to_image_frame_calculator.cc"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
":opencv_encoded_image_to_image_frame_calculator_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:image_frame_opencv", "//mediapipe/framework/formats:image_frame_opencv",
"//mediapipe/framework/port:opencv_imgcodecs", "//mediapipe/framework/port:opencv_imgcodecs",
@@ -422,9 +431,12 @@ cc_library(
":recolor_calculator_cc_proto", ":recolor_calculator_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:image_frame", "//mediapipe/framework/formats:image_frame",
"//mediapipe/framework/formats:image_frame_opencv",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/util:color_cc_proto", "//mediapipe/util:color_cc_proto",
"//mediapipe/framework/port:opencv_core",
"//mediapipe/framework/port:opencv_imgproc",
] + select({ ] + select({
"//mediapipe/gpu:disable_gpu": [], "//mediapipe/gpu:disable_gpu": [],
"//conditions:default": [ "//conditions:default": [
@@ -554,6 +566,27 @@ proto_library(
deps = ["//mediapipe/framework:calculator_proto"], deps = ["//mediapipe/framework:calculator_proto"],
) )
proto_library(
name = "opencv_encoded_image_to_image_frame_calculator_proto",
srcs = ["opencv_encoded_image_to_image_frame_calculator.proto"],
visibility = ["//visibility:public"],
deps = ["//mediapipe/framework:calculator_proto"],
)
proto_library(
name = "feature_detector_calculator_proto",
srcs = ["feature_detector_calculator.proto"],
deps = ["//mediapipe/framework:calculator_proto"],
)
mediapipe_cc_proto_library(
name = "feature_detector_calculator_cc_proto",
srcs = ["feature_detector_calculator.proto"],
cc_deps = ["//mediapipe/framework:calculator_cc_proto"],
visibility = ["//visibility:public"],
deps = [":feature_detector_calculator_proto"],
)
cc_library( cc_library(
name = "mask_overlay_calculator", name = "mask_overlay_calculator",
srcs = ["mask_overlay_calculator.cc"], srcs = ["mask_overlay_calculator.cc"],
@@ -569,3 +602,30 @@ cc_library(
], ],
alwayslink = 1, alwayslink = 1,
) )
cc_library(
name = "feature_detector_calculator",
srcs = ["feature_detector_calculator.cc"],
visibility = ["//mediapipe:__subpackages__"],
deps = [
":feature_detector_calculator_cc_proto",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:image_frame",
"//mediapipe/framework/formats:image_frame_opencv",
"//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/formats:video_stream_header",
"//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:logging",
"//mediapipe/framework/port:opencv_core",
"//mediapipe/framework/port:opencv_features2d",
"//mediapipe/framework/port:opencv_imgproc",
"//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status",
"//mediapipe/framework/port:threadpool",
"//mediapipe/framework/tool:options_util",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/synchronization",
"@org_tensorflow//tensorflow/lite:framework",
],
alwayslink = 1,
)
@@ -0,0 +1,210 @@
// Copyright 2020 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/memory/memory.h"
#include "absl/synchronization/blocking_counter.h"
#include "mediapipe/calculators/image/feature_detector_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/image_frame.h"
#include "mediapipe/framework/formats/image_frame_opencv.h"
#include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/formats/video_stream_header.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/opencv_core_inc.h"
#include "mediapipe/framework/port/opencv_features2d_inc.h"
#include "mediapipe/framework/port/opencv_imgproc_inc.h"
#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/framework/port/threadpool.h"
#include "mediapipe/framework/tool/options_util.h"
#include "tensorflow/lite/interpreter.h"
namespace mediapipe {
const char kOptionsTag[] = "OPTIONS";
const int kPatchSize = 32;
const int kNumThreads = 16;
// A calculator to apply local feature detection.
// Input stream:
// IMAGE: Input image frame of type ImageFrame from video stream.
// Output streams:
// FEATURES: The detected keypoints from input image as vector<cv::KeyPoint>.
// PATCHES: Optional output the extracted patches as vector<cv::Mat>
class FeatureDetectorCalculator : public CalculatorBase {
public:
~FeatureDetectorCalculator() override = default;
static ::mediapipe::Status GetContract(CalculatorContract* cc);
::mediapipe::Status Open(CalculatorContext* cc) override;
::mediapipe::Status Process(CalculatorContext* cc) override;
private:
FeatureDetectorCalculatorOptions options_;
cv::Ptr<cv::Feature2D> feature_detector_;
std::unique_ptr<::mediapipe::ThreadPool> pool_;
// Create image pyramid based on input image.
void ComputeImagePyramid(const cv::Mat& input_image,
std::vector<cv::Mat>* image_pyramid);
// Extract the patch for single feature with image pyramid.
cv::Mat ExtractPatch(const cv::KeyPoint& feature,
const std::vector<cv::Mat>& image_pyramid);
};
REGISTER_CALCULATOR(FeatureDetectorCalculator);
::mediapipe::Status FeatureDetectorCalculator::GetContract(
CalculatorContract* cc) {
if (cc->Inputs().HasTag("IMAGE")) {
cc->Inputs().Tag("IMAGE").Set<ImageFrame>();
}
if (cc->Outputs().HasTag("FEATURES")) {
cc->Outputs().Tag("FEATURES").Set<std::vector<cv::KeyPoint>>();
}
if (cc->Outputs().HasTag("LANDMARKS")) {
cc->Outputs().Tag("LANDMARKS").Set<NormalizedLandmarkList>();
}
if (cc->Outputs().HasTag("PATCHES")) {
cc->Outputs().Tag("PATCHES").Set<std::vector<TfLiteTensor>>();
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status FeatureDetectorCalculator::Open(CalculatorContext* cc) {
options_ =
tool::RetrieveOptions(cc->Options(), cc->InputSidePackets(), kOptionsTag)
.GetExtension(FeatureDetectorCalculatorOptions::ext);
feature_detector_ = cv::ORB::create(
options_.max_features(), options_.scale_factor(),
options_.pyramid_level(), kPatchSize - 1, 0, 2, cv::ORB::FAST_SCORE);
pool_ = absl::make_unique<::mediapipe::ThreadPool>("ThreadPool", kNumThreads);
pool_->StartWorkers();
return ::mediapipe::OkStatus();
}
::mediapipe::Status FeatureDetectorCalculator::Process(CalculatorContext* cc) {
const Timestamp& timestamp = cc->InputTimestamp();
if (timestamp == Timestamp::PreStream()) {
// Indicator packet.
return ::mediapipe::OkStatus();
}
InputStream* input_frame = &(cc->Inputs().Tag("IMAGE"));
cv::Mat input_view = formats::MatView(&input_frame->Get<ImageFrame>());
cv::Mat grayscale_view;
cv::cvtColor(input_view, grayscale_view, cv::COLOR_RGB2GRAY);
std::vector<cv::KeyPoint> keypoints;
feature_detector_->detect(grayscale_view, keypoints);
if (keypoints.size() > options_.max_features()) {
keypoints.resize(options_.max_features());
}
if (cc->Outputs().HasTag("FEATURES")) {
auto features_ptr = absl::make_unique<std::vector<cv::KeyPoint>>(keypoints);
cc->Outputs().Tag("FEATURES").Add(features_ptr.release(), timestamp);
}
if (cc->Outputs().HasTag("LANDMARKS")) {
auto landmarks_ptr = absl::make_unique<NormalizedLandmarkList>();
for (int j = 0; j < keypoints.size(); ++j) {
auto feature_landmark = landmarks_ptr->add_landmark();
feature_landmark->set_x(keypoints[j].pt.x / grayscale_view.cols);
feature_landmark->set_y(keypoints[j].pt.y / grayscale_view.rows);
}
cc->Outputs().Tag("LANDMARKS").Add(landmarks_ptr.release(), timestamp);
}
if (cc->Outputs().HasTag("PATCHES")) {
std::vector<cv::Mat> image_pyramid;
ComputeImagePyramid(grayscale_view, &image_pyramid);
std::vector<cv::Mat> patch_mat;
patch_mat.resize(keypoints.size());
absl::BlockingCounter counter(keypoints.size());
for (int i = 0; i < keypoints.size(); i++) {
pool_->Schedule(
[this, &image_pyramid, &keypoints, &patch_mat, i, &counter] {
patch_mat[i] = ExtractPatch(keypoints[i], image_pyramid);
counter.DecrementCount();
});
}
counter.Wait();
const int batch_size = options_.max_features();
auto patches = absl::make_unique<std::vector<TfLiteTensor>>();
TfLiteTensor tensor;
tensor.type = kTfLiteFloat32;
tensor.dims = TfLiteIntArrayCreate(4);
tensor.dims->data[0] = batch_size;
tensor.dims->data[1] = kPatchSize;
tensor.dims->data[2] = kPatchSize;
tensor.dims->data[3] = 1;
int num_bytes = batch_size * kPatchSize * kPatchSize * sizeof(float);
tensor.data.data = malloc(num_bytes);
tensor.bytes = num_bytes;
tensor.allocation_type = kTfLiteArenaRw;
float* tensor_buffer = tensor.data.f;
for (int i = 0; i < keypoints.size(); i++) {
for (int j = 0; j < patch_mat[i].rows; ++j) {
for (int k = 0; k < patch_mat[i].cols; ++k) {
*tensor_buffer++ = patch_mat[i].at<uchar>(j, k) / 128.0f - 1.0f;
}
}
}
for (int i = keypoints.size() * kPatchSize * kPatchSize; i < num_bytes / 4;
i++) {
*tensor_buffer++ = 0;
}
patches->emplace_back(tensor);
cc->Outputs().Tag("PATCHES").Add(patches.release(), timestamp);
}
return ::mediapipe::OkStatus();
}
void FeatureDetectorCalculator::ComputeImagePyramid(
const cv::Mat& input_image, std::vector<cv::Mat>* image_pyramid) {
cv::Mat tmp_image = input_image;
cv::Mat src_image = input_image;
for (int i = 0; i < options_.pyramid_level(); ++i) {
image_pyramid->push_back(src_image);
cv::resize(src_image, tmp_image, cv::Size(), 1.0f / options_.scale_factor(),
1.0f / options_.scale_factor());
src_image = tmp_image;
}
}
cv::Mat FeatureDetectorCalculator::ExtractPatch(
const cv::KeyPoint& feature, const std::vector<cv::Mat>& image_pyramid) {
cv::Mat img = image_pyramid[feature.octave];
float scale_factor = 1 / pow(options_.scale_factor(), feature.octave);
cv::Point2f center =
cv::Point2f(feature.pt.x * scale_factor, feature.pt.y * scale_factor);
cv::Mat rot = cv::getRotationMatrix2D(center, feature.angle, 1.0);
rot.at<double>(0, 2) += kPatchSize / 2 - center.x;
rot.at<double>(1, 2) += kPatchSize / 2 - center.y;
cv::Mat cropped_img;
// perform the affine transformation
cv::warpAffine(img, cropped_img, rot, cv::Size(kPatchSize, kPatchSize),
cv::INTER_LINEAR);
return cropped_img;
}
} // namespace mediapipe
@@ -0,0 +1,24 @@
// Options for FeatureDetectorCalculator
syntax = "proto2";
package mediapipe;
import "mediapipe/framework/calculator.proto";
message FeatureDetectorCalculatorOptions {
extend CalculatorOptions {
optional FeatureDetectorCalculatorOptions ext = 278741680;
}
// Set to true if output patches, otherwise only output cv::KeyPoint
optional bool output_patch = 1;
// The max number of detected features.
optional int32 max_features = 2 [default = 200];
// The number of pyramid levels.
optional int32 pyramid_level = 3 [default = 4];
// Pyramid decimation ratio.
optional float scale_factor = 4 [default = 1.2];
}
@@ -219,8 +219,10 @@ REGISTER_CALCULATOR(ImageCroppingCalculator);
const auto& input_img = cc->Inputs().Tag(kImageTag).Get<ImageFrame>(); const auto& input_img = cc->Inputs().Tag(kImageTag).Get<ImageFrame>();
cv::Mat input_mat = formats::MatView(&input_img); cv::Mat input_mat = formats::MatView(&input_img);
auto [target_width, target_height, rect_center_x, rect_center_y, rotation] = RectSpec specs = GetCropSpecs(cc, input_img.Width(), input_img.Height());
GetCropSpecs(cc, input_img.Width(), input_img.Height()); int target_width = specs.width, target_height = specs.height,
rect_center_x = specs.center_x, rect_center_y = specs.center_y;
float rotation = specs.rotation;
// Get border mode and value for OpenCV. // Get border mode and value for OpenCV.
int border_mode; int border_mode;
@@ -403,8 +405,10 @@ void ImageCroppingCalculator::GetOutputDimensions(CalculatorContext* cc,
int src_width, int src_height, int src_width, int src_height,
int* dst_width, int* dst_width,
int* dst_height) { int* dst_height) {
auto [crop_width, crop_height, x_center, y_center, rotation] = RectSpec specs = GetCropSpecs(cc, src_width, src_height);
GetCropSpecs(cc, src_width, src_height); int crop_width = specs.width, crop_height = specs.height,
x_center = specs.center_x, y_center = specs.center_y;
float rotation = specs.rotation;
const float half_width = crop_width / 2.0f; const float half_width = crop_width / 2.0f;
const float half_height = crop_height / 2.0f; const float half_height = crop_height / 2.0f;
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "mediapipe/calculators/image/opencv_encoded_image_to_image_frame_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/image_frame_opencv.h" #include "mediapipe/framework/formats/image_frame_opencv.h"
#include "mediapipe/framework/port/opencv_imgcodecs_inc.h" #include "mediapipe/framework/port/opencv_imgcodecs_inc.h"
@@ -34,7 +35,11 @@ namespace mediapipe {
class OpenCvEncodedImageToImageFrameCalculator : public CalculatorBase { class OpenCvEncodedImageToImageFrameCalculator : public CalculatorBase {
public: public:
static ::mediapipe::Status GetContract(CalculatorContract* cc); static ::mediapipe::Status GetContract(CalculatorContract* cc);
::mediapipe::Status Open(CalculatorContext* cc) override;
::mediapipe::Status Process(CalculatorContext* cc) override; ::mediapipe::Status Process(CalculatorContext* cc) override;
private:
mediapipe::OpenCvEncodedImageToImageFrameCalculatorOptions options_;
}; };
::mediapipe::Status OpenCvEncodedImageToImageFrameCalculator::GetContract( ::mediapipe::Status OpenCvEncodedImageToImageFrameCalculator::GetContract(
@@ -44,13 +49,29 @@ class OpenCvEncodedImageToImageFrameCalculator : public CalculatorBase {
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
::mediapipe::Status OpenCvEncodedImageToImageFrameCalculator::Open(
CalculatorContext* cc) {
options_ =
cc->Options<mediapipe::OpenCvEncodedImageToImageFrameCalculatorOptions>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status OpenCvEncodedImageToImageFrameCalculator::Process( ::mediapipe::Status OpenCvEncodedImageToImageFrameCalculator::Process(
CalculatorContext* cc) { CalculatorContext* cc) {
const std::string& contents = cc->Inputs().Index(0).Get<std::string>(); const std::string& contents = cc->Inputs().Index(0).Get<std::string>();
const std::vector<char> contents_vector(contents.begin(), contents.end()); const std::vector<char> contents_vector(contents.begin(), contents.end());
cv::Mat decoded_mat = cv::Mat decoded_mat;
cv::imdecode(contents_vector, -1 /* return the loaded image as-is */); if (options_.apply_orientation_from_exif_data()) {
// We want to respect the orientation from the EXIF data, which
// IMREAD_UNCHANGED ignores, but otherwise we want to be as permissive as
// possible with our reading flags. Therefore, we use IMREAD_ANYCOLOR and
// IMREAD_ANYDEPTH.
decoded_mat = cv::imdecode(contents_vector,
cv::IMREAD_ANYCOLOR | cv::IMREAD_ANYDEPTH);
} else {
// Return the loaded image as-is
decoded_mat = cv::imdecode(contents_vector, cv::IMREAD_UNCHANGED);
}
ImageFormat::Format image_format = ImageFormat::UNKNOWN; ImageFormat::Format image_format = ImageFormat::UNKNOWN;
cv::Mat output_mat; cv::Mat output_mat;
switch (decoded_mat.channels()) { switch (decoded_mat.channels()) {
@@ -70,7 +91,8 @@ class OpenCvEncodedImageToImageFrameCalculator : public CalculatorBase {
<< "Unsupported number of channels: " << decoded_mat.channels(); << "Unsupported number of channels: " << decoded_mat.channels();
} }
std::unique_ptr<ImageFrame> output_frame = absl::make_unique<ImageFrame>( std::unique_ptr<ImageFrame> output_frame = absl::make_unique<ImageFrame>(
image_format, decoded_mat.size().width, decoded_mat.size().height); image_format, decoded_mat.size().width, decoded_mat.size().height,
ImageFrame::kGlDefaultAlignmentBoundary);
output_mat.copyTo(formats::MatView(output_frame.get())); output_mat.copyTo(formats::MatView(output_frame.get()));
cc->Outputs().Index(0).Add(output_frame.release(), cc->InputTimestamp()); cc->Outputs().Index(0).Add(output_frame.release(), cc->InputTimestamp());
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
@@ -0,0 +1,30 @@
// Copyright 2020 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.
syntax = "proto2";
package mediapipe;
import "mediapipe/framework/calculator.proto";
message OpenCvEncodedImageToImageFrameCalculatorOptions {
extend CalculatorOptions {
optional OpenCvEncodedImageToImageFrameCalculatorOptions ext = 303447308;
}
// If set, we will attempt to automatically apply the orientation specified by
// the image's EXIF data when loading the image. Otherwise, the image data
// will be loaded as-is.
optional bool apply_orientation_from_exif_data = 1 [default = false];
}
@@ -17,6 +17,9 @@
#include "mediapipe/calculators/image/recolor_calculator.pb.h" #include "mediapipe/calculators/image/recolor_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/image_frame.h" #include "mediapipe/framework/formats/image_frame.h"
#include "mediapipe/framework/formats/image_frame_opencv.h"
#include "mediapipe/framework/port/opencv_core_inc.h"
#include "mediapipe/framework/port/opencv_imgproc_inc.h"
#include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/port/status.h" #include "mediapipe/framework/port/status.h"
#include "mediapipe/util/color.pb.h" #include "mediapipe/util/color.pb.h"
@@ -39,8 +42,6 @@ namespace mediapipe {
// The luminance of the input image is used to adjust the blending weight, // The luminance of the input image is used to adjust the blending weight,
// to help preserve image textures. // to help preserve image textures.
// //
// TODO implement cpu support.
//
// Inputs: // Inputs:
// One of the following IMAGE tags: // One of the following IMAGE tags:
// IMAGE: An ImageFrame input image, RGB or RGBA. // IMAGE: An ImageFrame input image, RGB or RGBA.
@@ -71,6 +72,8 @@ namespace mediapipe {
// } // }
// } // }
// //
// Note: Cannot mix-match CPU & GPU inputs/outputs.
// CPU-in & CPU-out <or> GPU-in & GPU-out
class RecolorCalculator : public CalculatorBase { class RecolorCalculator : public CalculatorBase {
public: public:
RecolorCalculator() = default; RecolorCalculator() = default;
@@ -138,6 +141,11 @@ REGISTER_CALCULATOR(RecolorCalculator);
cc->Outputs().Tag("IMAGE").Set<ImageFrame>(); cc->Outputs().Tag("IMAGE").Set<ImageFrame>();
} }
// Confirm only one of the input streams is present.
RET_CHECK(cc->Inputs().HasTag("IMAGE") ^ cc->Inputs().HasTag("IMAGE_GPU"));
// Confirm only one of the output streams is present.
RET_CHECK(cc->Outputs().HasTag("IMAGE") ^ cc->Outputs().HasTag("IMAGE_GPU"));
if (use_gpu) { if (use_gpu) {
#if !defined(MEDIAPIPE_DISABLE_GPU) #if !defined(MEDIAPIPE_DISABLE_GPU)
MP_RETURN_IF_ERROR(mediapipe::GlCalculatorHelper::UpdateContract(cc)); MP_RETURN_IF_ERROR(mediapipe::GlCalculatorHelper::UpdateContract(cc));
@@ -193,7 +201,62 @@ REGISTER_CALCULATOR(RecolorCalculator);
} }
::mediapipe::Status RecolorCalculator::RenderCpu(CalculatorContext* cc) { ::mediapipe::Status RecolorCalculator::RenderCpu(CalculatorContext* cc) {
return ::mediapipe::UnimplementedError("CPU support is not implemented yet."); if (cc->Inputs().Tag("MASK").IsEmpty()) {
return ::mediapipe::OkStatus();
}
// Get inputs and setup output.
const auto& input_img = cc->Inputs().Tag("IMAGE").Get<ImageFrame>();
const auto& mask_img = cc->Inputs().Tag("MASK").Get<ImageFrame>();
cv::Mat input_mat = formats::MatView(&input_img);
cv::Mat mask_mat = formats::MatView(&mask_img);
RET_CHECK(input_mat.channels() == 3); // RGB only.
if (mask_mat.channels() > 1) {
std::vector<cv::Mat> channels;
cv::split(mask_mat, channels);
if (mask_channel_ == mediapipe::RecolorCalculatorOptions_MaskChannel_ALPHA)
mask_mat = channels[3];
else
mask_mat = channels[0];
}
cv::Mat mask_full;
cv::resize(mask_mat, mask_full, input_mat.size());
auto output_img = absl::make_unique<ImageFrame>(
input_img.Format(), input_mat.cols, input_mat.rows);
cv::Mat output_mat = mediapipe::formats::MatView(output_img.get());
// From GPU shader:
/*
vec4 weight = texture2D(mask, sample_coordinate);
vec4 color1 = texture2D(frame, sample_coordinate);
vec4 color2 = vec4(recolor, 1.0);
float luminance = dot(color1.rgb, vec3(0.299, 0.587, 0.114));
float mix_value = weight.MASK_COMPONENT * luminance;
fragColor = mix(color1, color2, mix_value);
*/
for (int i = 0; i < output_mat.rows; ++i) {
for (int j = 0; j < output_mat.cols; ++j) {
float weight = mask_full.at<uchar>(i, j) * (1.0 / 255.0);
cv::Vec3f color1 = input_mat.at<cv::Vec3b>(i, j);
cv::Vec3f color2 = {color_[0], color_[1], color_[2]};
float luminance =
(color1[0] * 0.299 + color1[1] * 0.587 + color1[2] * 0.114) / 255;
float mix_value = weight * luminance;
cv::Vec3b mix_color = color1 * (1.0 - mix_value) + color2 * mix_value;
output_mat.at<cv::Vec3b>(i, j) = mix_color;
}
}
cc->Outputs().Tag("IMAGE").Add(output_img.release(), cc->InputTimestamp());
return ::mediapipe::OkStatus();
} }
::mediapipe::Status RecolorCalculator::RenderGpu(CalculatorContext* cc) { ::mediapipe::Status RecolorCalculator::RenderGpu(CalculatorContext* cc) {
@@ -303,9 +366,9 @@ void RecolorCalculator::GlRender() {
if (!options.has_color()) RET_CHECK_FAIL() << "Missing color option."; if (!options.has_color()) RET_CHECK_FAIL() << "Missing color option.";
color_.push_back(options.color().r() / 255.0); color_.push_back(options.color().r());
color_.push_back(options.color().g() / 255.0); color_.push_back(options.color().g());
color_.push_back(options.color().b() / 255.0); color_.push_back(options.color().b());
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
@@ -378,8 +441,8 @@ void RecolorCalculator::GlRender() {
glUseProgram(program_); glUseProgram(program_);
glUniform1i(glGetUniformLocation(program_, "frame"), 1); glUniform1i(glGetUniformLocation(program_, "frame"), 1);
glUniform1i(glGetUniformLocation(program_, "mask"), 2); glUniform1i(glGetUniformLocation(program_, "mask"), 2);
glUniform3f(glGetUniformLocation(program_, "recolor"), color_[0], color_[1], glUniform3f(glGetUniformLocation(program_, "recolor"), color_[0] / 255.0,
color_[2]); color_[1] / 255.0, color_[2] / 255.0);
#endif // !MEDIAPIPE_DISABLE_GPU #endif // !MEDIAPIPE_DISABLE_GPU
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
+1
View File
@@ -1110,6 +1110,7 @@ cc_test(
], ],
"//mediapipe:android": [ "//mediapipe:android": [
"@org_tensorflow//tensorflow/core:android_tensorflow_lib_with_ops_lite_proto_no_rtti_lib", "@org_tensorflow//tensorflow/core:android_tensorflow_lib_with_ops_lite_proto_no_rtti_lib",
"@org_tensorflow//tensorflow/core:android_tensorflow_test_lib",
], ],
"//mediapipe:ios": [ "//mediapipe:ios": [
"@org_tensorflow//tensorflow/core:ios_tensorflow_test_lib", "@org_tensorflow//tensorflow/core:ios_tensorflow_test_lib",
@@ -51,10 +51,11 @@ static constexpr char kStringSavedModelPath[] = "STRING_SAVED_MODEL_PATH";
#endif #endif
} }
// If options.convert_signature_to_tags() will convert letters to uppercase // If options.convert_signature_to_tags() is set, will convert letters to
// and replace /'s with _'s. If set, this enables the standard SavedModel // uppercase and replace /'s and -'s with _'s. This enables the standard
// classification, regression, and prediction signatures to be used as // SavedModel classification, regression, and prediction signatures to be used
// uppercase INPUTS and OUTPUTS tags for streams. // as uppercase INPUTS and OUTPUTS tags for streams and supports other common
// patterns.
const std::string MaybeConvertSignatureToTag( const std::string MaybeConvertSignatureToTag(
const std::string& name, const std::string& name,
const TensorFlowSessionFromSavedModelCalculatorOptions& options) { const TensorFlowSessionFromSavedModelCalculatorOptions& options) {
@@ -64,6 +65,7 @@ const std::string MaybeConvertSignatureToTag(
std::transform(name.begin(), name.end(), output.begin(), std::transform(name.begin(), name.end(), output.begin(),
[](unsigned char c) { return std::toupper(c); }); [](unsigned char c) { return std::toupper(c); });
output = absl::StrReplaceAll(output, {{"/", "_"}}); output = absl::StrReplaceAll(output, {{"/", "_"}});
output = absl::StrReplaceAll(output, {{"-", "_"}});
return output; return output;
} else { } else {
return name; return name;
@@ -32,8 +32,8 @@ message TensorFlowSessionFromSavedModelCalculatorOptions {
// The name of the generic signature to load into the mapping from tags to // The name of the generic signature to load into the mapping from tags to
// tensor names. // tensor names.
optional string signature_name = 2 [default = "serving_default"]; optional string signature_name = 2 [default = "serving_default"];
// Whether to convert the signature keys to uppercase and switch /'s to // Whether to convert the signature keys to uppercase as well as switch /'s
// _'s, which enables standard signatures to be used as Tags. // and -'s to _'s, which enables common signatures to be used as Tags.
optional bool convert_signature_to_tags = 3 [default = true]; optional bool convert_signature_to_tags = 3 [default = true];
// If true, saved_model_path can have multiple exported models in // If true, saved_model_path can have multiple exported models in
// subdirectories saved_model_path/%08d and the alphabetically last (i.e., // subdirectories saved_model_path/%08d and the alphabetically last (i.e.,
@@ -53,10 +53,11 @@ static constexpr char kStringSavedModelPath[] = "STRING_SAVED_MODEL_PATH";
#endif #endif
} }
// If options.convert_signature_to_tags() will convert letters to uppercase // If options.convert_signature_to_tags() is set, will convert letters to
// and replace /'s with _'s. If set, this enables the standard SavedModel // uppercase and replace /'s and -'s with _'s. This enables the standard
// classification, regression, and prediction signatures to be used as // SavedModel classification, regression, and prediction signatures to be used
// uppercase INPUTS and OUTPUTS tags for streams. // as uppercase INPUTS and OUTPUTS tags for streams and supports other common
// patterns.
const std::string MaybeConvertSignatureToTag( const std::string MaybeConvertSignatureToTag(
const std::string& name, const std::string& name,
const TensorFlowSessionFromSavedModelGeneratorOptions& options) { const TensorFlowSessionFromSavedModelGeneratorOptions& options) {
@@ -66,6 +67,7 @@ const std::string MaybeConvertSignatureToTag(
std::transform(name.begin(), name.end(), output.begin(), std::transform(name.begin(), name.end(), output.begin(),
[](unsigned char c) { return std::toupper(c); }); [](unsigned char c) { return std::toupper(c); });
output = absl::StrReplaceAll(output, {{"/", "_"}}); output = absl::StrReplaceAll(output, {{"/", "_"}});
output = absl::StrReplaceAll(output, {{"-", "_"}});
return output; return output;
} else { } else {
return name; return name;
@@ -32,8 +32,8 @@ message TensorFlowSessionFromSavedModelGeneratorOptions {
// The name of the generic signature to load into the mapping from tags to // The name of the generic signature to load into the mapping from tags to
// tensor names. // tensor names.
optional string signature_name = 2 [default = "serving_default"]; optional string signature_name = 2 [default = "serving_default"];
// Whether to convert the signature keys to uppercase and switch /'s to // Whether to convert the signature keys to uppercase as well as switch /'s
// _'s, which enables standard signatures to be used as Tags. // and -'s to _'s, which enables common signatures to be used as Tags.
optional bool convert_signature_to_tags = 3 [default = true]; optional bool convert_signature_to_tags = 3 [default = true];
// If true, saved_model_path can have multiple exported models in // If true, saved_model_path can have multiple exported models in
// subdirectories saved_model_path/%08d and the alphabetically last (i.e., // subdirectories saved_model_path/%08d and the alphabetically last (i.e.,
+42 -1
View File
@@ -222,9 +222,11 @@ cc_library(
deps = [ deps = [
":util", ":util",
":tflite_inference_calculator_cc_proto", ":tflite_inference_calculator_cc_proto",
"@com_google_absl//absl/memory",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/util:resource_util", "//mediapipe/util:resource_util",
"@org_tensorflow//tensorflow/lite:framework", "@org_tensorflow//tensorflow/lite:framework",
"@org_tensorflow//tensorflow/lite/delegates/xnnpack:xnnpack_delegate",
"@org_tensorflow//tensorflow/lite/kernels:builtin_ops", "@org_tensorflow//tensorflow/lite/kernels:builtin_ops",
"//mediapipe/framework/stream_handler:fixed_size_input_stream_handler", "//mediapipe/framework/stream_handler:fixed_size_input_stream_handler",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
@@ -254,6 +256,10 @@ cc_library(
"//mediapipe:android": [ "//mediapipe:android": [
"@org_tensorflow//tensorflow/lite/delegates/nnapi:nnapi_delegate", "@org_tensorflow//tensorflow/lite/delegates/nnapi:nnapi_delegate",
], ],
}) + select({
"//conditions:default": [
"//mediapipe/util:cpu_util",
],
}), }),
alwayslink = 1, alwayslink = 1,
) )
@@ -308,6 +314,20 @@ cc_library(
alwayslink = 1, alwayslink = 1,
) )
cc_library(
name = "tflite_model_calculator",
srcs = ["tflite_model_calculator.cc"],
visibility = ["//visibility:public"],
deps = [
":util",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework:packet",
"//mediapipe/framework/port:ret_check",
"@org_tensorflow//tensorflow/lite:framework",
],
alwayslink = 1,
)
cc_library( cc_library(
name = "tflite_tensors_to_segmentation_calculator", name = "tflite_tensors_to_segmentation_calculator",
srcs = ["tflite_tensors_to_segmentation_calculator.cc"], srcs = ["tflite_tensors_to_segmentation_calculator.cc"],
@@ -431,7 +451,7 @@ cc_library(
"//mediapipe:android": [ "//mediapipe:android": [
"//mediapipe/util/android/file/base", "//mediapipe/util/android/file/base",
], ],
"//mediapipe:apple": [ "//mediapipe:ios": [
"//mediapipe/util/android/file/base", "//mediapipe/util/android/file/base",
], ],
"//mediapipe:macos": [ "//mediapipe:macos": [
@@ -478,6 +498,9 @@ cc_test(
deps = [ deps = [
":tflite_inference_calculator", ":tflite_inference_calculator",
":tflite_inference_calculator_cc_proto", ":tflite_inference_calculator_cc_proto",
":tflite_model_calculator",
"//mediapipe/calculators/core:constant_side_packet_calculator",
"//mediapipe/calculators/util:local_file_contents_calculator",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework:calculator_runner", "//mediapipe/framework:calculator_runner",
"//mediapipe/framework/deps:file_path", "//mediapipe/framework/deps:file_path",
@@ -485,7 +508,9 @@ cc_test(
"//mediapipe/framework/port:integral_types", "//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:parse_text_proto", "//mediapipe/framework/port:parse_text_proto",
"//mediapipe/framework/tool:validate_type", "//mediapipe/framework/tool:validate_type",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings", "@com_google_absl//absl/strings",
"@com_google_absl//absl/types:optional",
"@org_tensorflow//tensorflow/lite:framework", "@org_tensorflow//tensorflow/lite:framework",
"@org_tensorflow//tensorflow/lite/kernels:builtin_ops", "@org_tensorflow//tensorflow/lite/kernels:builtin_ops",
], ],
@@ -511,3 +536,19 @@ cc_test(
"@org_tensorflow//tensorflow/lite/kernels:builtin_ops", "@org_tensorflow//tensorflow/lite/kernels:builtin_ops",
], ],
) )
cc_test(
name = "tflite_model_calculator_test",
srcs = ["tflite_model_calculator_test.cc"],
data = ["testdata/add.bin"],
deps = [
":tflite_model_calculator",
"//mediapipe/calculators/core:constant_side_packet_calculator",
"//mediapipe/calculators/util:local_file_contents_calculator",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework:calculator_runner",
"//mediapipe/framework/port:gtest_main",
"//mediapipe/framework/port:parse_text_proto",
"@org_tensorflow//tensorflow/lite:framework",
],
)
@@ -17,10 +17,16 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "absl/memory/memory.h"
#include "mediapipe/calculators/tflite/tflite_inference_calculator.pb.h" #include "mediapipe/calculators/tflite/tflite_inference_calculator.pb.h"
#include "mediapipe/calculators/tflite/util.h" #include "mediapipe/calculators/tflite/util.h"
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/port/ret_check.h" #include "mediapipe/framework/port/ret_check.h"
#if !defined(__EMSCRIPTEN__)
#include "mediapipe/util/cpu_util.h"
#endif // !__EMSCRIPTEN__
#include "mediapipe/util/resource_util.h" #include "mediapipe/util/resource_util.h"
#include "tensorflow/lite/error_reporter.h" #include "tensorflow/lite/error_reporter.h"
#include "tensorflow/lite/interpreter.h" #include "tensorflow/lite/interpreter.h"
@@ -50,7 +56,7 @@
#include "tensorflow/lite/delegates/gpu/metal_delegate.h" #include "tensorflow/lite/delegates/gpu/metal_delegate.h"
#include "tensorflow/lite/delegates/gpu/metal_delegate_internal.h" #include "tensorflow/lite/delegates/gpu/metal_delegate_internal.h"
#endif // iOS #endif // iOS
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#if defined(MEDIAPIPE_ANDROID) #if defined(MEDIAPIPE_ANDROID)
#include "tensorflow/lite/delegates/nnapi/nnapi_delegate.h" #include "tensorflow/lite/delegates/nnapi/nnapi_delegate.h"
#endif // ANDROID #endif // ANDROID
@@ -113,6 +119,23 @@ struct GPUData {
}; };
#endif #endif
// Returns number of threads to configure XNNPACK delegate with.
// (Equal to user provided value if specified. Otherwise, it returns number of
// high cores (hard-coded to 1 for __EMSCRIPTEN__))
int GetXnnpackNumThreads(
const mediapipe::TfLiteInferenceCalculatorOptions& opts) {
static constexpr int kDefaultNumThreads = -1;
if (opts.has_delegate() && opts.delegate().has_xnnpack() &&
opts.delegate().xnnpack().num_threads() != kDefaultNumThreads) {
return opts.delegate().xnnpack().num_threads();
}
#if !defined(__EMSCRIPTEN__)
return InferHigherCoreIds().size();
#else
return 1;
#endif // !__EMSCRIPTEN__
}
// Calculator Header Section // Calculator Header Section
// Runs inference on the provided input TFLite tensors and TFLite model. // Runs inference on the provided input TFLite tensors and TFLite model.
@@ -139,6 +162,9 @@ struct GPUData {
// Input side packet: // Input side packet:
// CUSTOM_OP_RESOLVER (optional) - Use a custom op resolver, // CUSTOM_OP_RESOLVER (optional) - Use a custom op resolver,
// instead of the builtin one. // instead of the builtin one.
// MODEL (optional) - Use to specify TfLite model
// (std::unique_ptr<tflite::FlatBufferModel,
// std::function<void(tflite::FlatBufferModel*)>>)
// //
// Example use: // Example use:
// node { // node {
@@ -153,6 +179,20 @@ struct GPUData {
// } // }
// } // }
// //
// or
//
// node {
// calculator: "TfLiteInferenceCalculator"
// input_stream: "TENSORS:tensor_image"
// input_side_packet: "MODEL:model"
// output_stream: "TENSORS:tensors"
// options: {
// [mediapipe.TfLiteInferenceCalculatorOptions.ext] {
// delegate { gpu {} }
// }
// }
// }
//
// IMPORTANT Notes: // IMPORTANT Notes:
// Tensors are assumed to be ordered correctly (sequentially added to model). // Tensors are assumed to be ordered correctly (sequentially added to model).
// Input tensors are assumed to be of the correct size and already normalized. // Input tensors are assumed to be of the correct size and already normalized.
@@ -165,6 +205,9 @@ class TfLiteInferenceCalculator : public CalculatorBase {
public: public:
using TfLiteDelegatePtr = using TfLiteDelegatePtr =
std::unique_ptr<TfLiteDelegate, std::function<void(TfLiteDelegate*)>>; std::unique_ptr<TfLiteDelegate, std::function<void(TfLiteDelegate*)>>;
using TfLiteModelPtr =
std::unique_ptr<tflite::FlatBufferModel,
std::function<void(tflite::FlatBufferModel*)>>;
static ::mediapipe::Status GetContract(CalculatorContract* cc); static ::mediapipe::Status GetContract(CalculatorContract* cc);
@@ -173,12 +216,12 @@ class TfLiteInferenceCalculator : public CalculatorBase {
::mediapipe::Status Close(CalculatorContext* cc) override; ::mediapipe::Status Close(CalculatorContext* cc) override;
private: private:
::mediapipe::Status LoadOptions(CalculatorContext* cc);
::mediapipe::Status LoadModel(CalculatorContext* cc); ::mediapipe::Status LoadModel(CalculatorContext* cc);
::mediapipe::StatusOr<Packet> GetModelAsPacket(const CalculatorContext& cc);
::mediapipe::Status LoadDelegate(CalculatorContext* cc); ::mediapipe::Status LoadDelegate(CalculatorContext* cc);
Packet model_packet_;
std::unique_ptr<tflite::Interpreter> interpreter_; std::unique_ptr<tflite::Interpreter> interpreter_;
std::unique_ptr<tflite::FlatBufferModel> model_;
TfLiteDelegatePtr delegate_; TfLiteDelegatePtr delegate_;
#if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE) #if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE)
@@ -198,7 +241,6 @@ class TfLiteInferenceCalculator : public CalculatorBase {
edgetpu::EdgeTpuManager::GetSingleton()->OpenDevice(); edgetpu::EdgeTpuManager::GetSingleton()->OpenDevice();
#endif #endif
std::string model_path_ = "";
bool gpu_inference_ = false; bool gpu_inference_ = false;
bool gpu_input_ = false; bool gpu_input_ = false;
bool gpu_output_ = false; bool gpu_output_ = false;
@@ -217,6 +259,10 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
const auto& options = const auto& options =
cc->Options<::mediapipe::TfLiteInferenceCalculatorOptions>(); cc->Options<::mediapipe::TfLiteInferenceCalculatorOptions>();
RET_CHECK(!options.model_path().empty() ^
cc->InputSidePackets().HasTag("MODEL"))
<< "Either model as side packet or model path in options is required.";
bool use_gpu = bool use_gpu =
options.has_delegate() ? options.delegate().has_gpu() : options.use_gpu(); options.has_delegate() ? options.delegate().has_gpu() : options.use_gpu();
@@ -249,6 +295,9 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
.Tag("CUSTOM_OP_RESOLVER") .Tag("CUSTOM_OP_RESOLVER")
.Set<tflite::ops::builtin::BuiltinOpResolver>(); .Set<tflite::ops::builtin::BuiltinOpResolver>();
} }
if (cc->InputSidePackets().HasTag("MODEL")) {
cc->InputSidePackets().Tag("MODEL").Set<TfLiteModelPtr>();
}
if (use_gpu) { if (use_gpu) {
#if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE) #if !defined(MEDIAPIPE_DISABLE_GL_COMPUTE)
@@ -267,7 +316,9 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
::mediapipe::Status TfLiteInferenceCalculator::Open(CalculatorContext* cc) { ::mediapipe::Status TfLiteInferenceCalculator::Open(CalculatorContext* cc) {
cc->SetOffset(TimestampDiff(0)); cc->SetOffset(TimestampDiff(0));
MP_RETURN_IF_ERROR(LoadOptions(cc)); const auto& options =
cc->Options<::mediapipe::TfLiteInferenceCalculatorOptions>();
gpu_inference_ = options.use_gpu();
if (cc->Inputs().HasTag("TENSORS_GPU")) { if (cc->Inputs().HasTag("TENSORS_GPU")) {
#if !defined(MEDIAPIPE_DISABLE_GPU) && !defined(__EMSCRIPTEN__) #if !defined(MEDIAPIPE_DISABLE_GPU) && !defined(__EMSCRIPTEN__)
@@ -492,34 +543,10 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
// Calculator Auxiliary Section // Calculator Auxiliary Section
::mediapipe::Status TfLiteInferenceCalculator::LoadOptions(
CalculatorContext* cc) {
// Get calculator options specified in the graph.
const auto& options =
cc->Options<::mediapipe::TfLiteInferenceCalculatorOptions>();
// Get model name.
if (!options.model_path().empty()) {
std::string model_path = options.model_path();
ASSIGN_OR_RETURN(model_path_, mediapipe::PathToResourceAsFile(model_path));
} else {
LOG(ERROR) << "Must specify path to TFLite model.";
return ::mediapipe::Status(::mediapipe::StatusCode::kNotFound,
"Must specify path to TFLite model.");
}
// Get execution modes.
gpu_inference_ =
options.has_delegate() ? options.delegate().has_gpu() : options.use_gpu();
return ::mediapipe::OkStatus();
}
::mediapipe::Status TfLiteInferenceCalculator::LoadModel( ::mediapipe::Status TfLiteInferenceCalculator::LoadModel(
CalculatorContext* cc) { CalculatorContext* cc) {
model_ = tflite::FlatBufferModel::BuildFromFile(model_path_.c_str()); ASSIGN_OR_RETURN(model_packet_, GetModelAsPacket(*cc));
RET_CHECK(model_); const auto& model = *model_packet_.Get<TfLiteModelPtr>();
tflite::ops::builtin::BuiltinOpResolver op_resolver; tflite::ops::builtin::BuiltinOpResolver op_resolver;
if (cc->InputSidePackets().HasTag("CUSTOM_OP_RESOLVER")) { if (cc->InputSidePackets().HasTag("CUSTOM_OP_RESOLVER")) {
@@ -529,9 +556,9 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
} }
#if defined(MEDIAPIPE_EDGE_TPU) #if defined(MEDIAPIPE_EDGE_TPU)
interpreter_ = interpreter_ =
BuildEdgeTpuInterpreter(*model_, &op_resolver, edgetpu_context_.get()); BuildEdgeTpuInterpreter(model, &op_resolver, edgetpu_context_.get());
#else #else
tflite::InterpreterBuilder(*model_, op_resolver)(&interpreter_); tflite::InterpreterBuilder(model, op_resolver)(&interpreter_);
#endif // MEDIAPIPE_EDGE_TPU #endif // MEDIAPIPE_EDGE_TPU
RET_CHECK(interpreter_); RET_CHECK(interpreter_);
@@ -557,6 +584,28 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
::mediapipe::StatusOr<Packet> TfLiteInferenceCalculator::GetModelAsPacket(
const CalculatorContext& cc) {
const auto& options =
cc.Options<mediapipe::TfLiteInferenceCalculatorOptions>();
if (!options.model_path().empty()) {
std::string model_path = options.model_path();
ASSIGN_OR_RETURN(model_path, mediapipe::PathToResourceAsFile(model_path));
auto model = tflite::FlatBufferModel::BuildFromFile(model_path.c_str());
RET_CHECK(model) << "Failed to load model from path.";
return MakePacket<TfLiteModelPtr>(TfLiteModelPtr(
model.release(), [](tflite::FlatBufferModel* model) { delete model; }));
}
if (cc.InputSidePackets().HasTag("MODEL")) {
return cc.InputSidePackets().Tag("MODEL");
}
return ::mediapipe::Status(
::mediapipe::StatusCode::kNotFound,
"Must specify TFLite model as path or loaded model.");
}
::mediapipe::Status TfLiteInferenceCalculator::LoadDelegate( ::mediapipe::Status TfLiteInferenceCalculator::LoadDelegate(
CalculatorContext* cc) { CalculatorContext* cc) {
const auto& calculator_opts = const auto& calculator_opts =
@@ -587,6 +636,22 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
} }
#endif // MEDIAPIPE_ANDROID #endif // MEDIAPIPE_ANDROID
#if defined(__EMSCRIPTEN__)
const bool xnnpack_requested = true;
#else
const bool xnnpack_requested = calculator_opts.has_delegate() &&
calculator_opts.delegate().has_xnnpack();
#endif // __EMSCRIPTEN__
if (xnnpack_requested) {
TfLiteXNNPackDelegateOptions xnnpack_opts{};
xnnpack_opts.num_threads = GetXnnpackNumThreads(calculator_opts);
delegate_ = TfLiteDelegatePtr(TfLiteXNNPackDelegateCreate(&xnnpack_opts),
&TfLiteXNNPackDelegateDelete);
RET_CHECK_EQ(interpreter_->ModifyGraphWithDelegate(delegate_.get()),
kTfLiteOk);
}
// Return, no need for GPU delegate below. // Return, no need for GPU delegate below.
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
@@ -608,7 +673,7 @@ REGISTER_CALCULATOR(TfLiteInferenceCalculator);
const auto& input_indices = interpreter_->inputs(); const auto& input_indices = interpreter_->inputs();
gpu_data_in_.resize(input_indices.size()); gpu_data_in_.resize(input_indices.size());
for (int i = 0; i < input_indices.size(); ++i) { for (int i = 0; i < input_indices.size(); ++i) {
const TfLiteTensor* tensor = interpreter_->tensor(input_indices[0]); const TfLiteTensor* tensor = interpreter_->tensor(input_indices[i]);
gpu_data_in_[i] = absl::make_unique<GPUData>(); gpu_data_in_[i] = absl::make_unique<GPUData>();
gpu_data_in_[i]->elements = 1; gpu_data_in_[i]->elements = 1;
for (int d = 0; d < tensor->dims->size; ++d) { for (int d = 0; d < tensor->dims->size; ++d) {
@@ -45,11 +45,17 @@ message TfLiteInferenceCalculatorOptions {
message Gpu {} message Gpu {}
// Android only. // Android only.
message Nnapi {} message Nnapi {}
message Xnnpack {
// Number of threads for XNNPACK delegate. (By default, calculator tries
// to choose optimal number of threads depending on the device.)
optional int32 num_threads = 1 [default = -1];
}
oneof delegate { oneof delegate {
TfLite tflite = 1; TfLite tflite = 1;
Gpu gpu = 2; Gpu gpu = 2;
Nnapi nnapi = 3; Nnapi nnapi = 3;
Xnnpack xnnpack = 4;
} }
} }
@@ -41,7 +41,7 @@ namespace mediapipe {
using ::tflite::Interpreter; using ::tflite::Interpreter;
void DoSmokeTest(absl::string_view delegate) { void DoSmokeTest(const std::string& graph_proto) {
const int width = 8; const int width = 8;
const int height = 8; const int height = 8;
const int channels = 3; const int channels = 3;
@@ -69,24 +69,9 @@ void DoSmokeTest(absl::string_view delegate) {
auto input_vec = absl::make_unique<std::vector<TfLiteTensor>>(); auto input_vec = absl::make_unique<std::vector<TfLiteTensor>>();
input_vec->emplace_back(*tensor); input_vec->emplace_back(*tensor);
std::string graph_proto = R"(
input_stream: "tensor_in"
node {
calculator: "TfLiteInferenceCalculator"
input_stream: "TENSORS:tensor_in"
output_stream: "TENSORS:tensor_out"
options {
[mediapipe.TfLiteInferenceCalculatorOptions.ext] {
model_path: "mediapipe/calculators/tflite/testdata/add.bin"
$delegate
}
}
}
)";
ASSERT_EQ(absl::StrReplaceAll({{"$delegate", delegate}}, &graph_proto), 1);
// Prepare single calculator graph to and wait for packets. // Prepare single calculator graph to and wait for packets.
CalculatorGraphConfig graph_config = CalculatorGraphConfig graph_config =
::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(graph_proto); ParseTextProtoOrDie<CalculatorGraphConfig>(graph_proto);
std::vector<Packet> output_packets; std::vector<Packet> output_packets;
tool::AddVectorSink("tensor_out", &graph_config, &output_packets); tool::AddVectorSink("tensor_out", &graph_config, &output_packets);
CalculatorGraph graph(graph_config); CalculatorGraph graph(graph_config);
@@ -119,8 +104,70 @@ void DoSmokeTest(absl::string_view delegate) {
// Tests a simple add model that adds an input tensor to itself. // Tests a simple add model that adds an input tensor to itself.
TEST(TfLiteInferenceCalculatorTest, SmokeTest) { TEST(TfLiteInferenceCalculatorTest, SmokeTest) {
DoSmokeTest(/*delegate=*/""); std::string graph_proto = R"(
DoSmokeTest(/*delegate=*/"delegate { tflite {} }"); input_stream: "tensor_in"
node {
calculator: "TfLiteInferenceCalculator"
input_stream: "TENSORS:tensor_in"
output_stream: "TENSORS:tensor_out"
options {
[mediapipe.TfLiteInferenceCalculatorOptions.ext] {
model_path: "mediapipe/calculators/tflite/testdata/add.bin"
$delegate
}
}
}
)";
DoSmokeTest(
/*graph_proto=*/absl::StrReplaceAll(graph_proto, {{"$delegate", ""}}));
DoSmokeTest(/*graph_proto=*/absl::StrReplaceAll(
graph_proto, {{"$delegate", "delegate { tflite {} }"}}));
DoSmokeTest(/*graph_proto=*/absl::StrReplaceAll(
graph_proto, {{"$delegate", "delegate { xnnpack {} }"}}));
DoSmokeTest(/*graph_proto=*/absl::StrReplaceAll(
graph_proto,
{{"$delegate", "delegate { xnnpack { num_threads: 10 } }"}}));
}
TEST(TfLiteInferenceCalculatorTest, SmokeTest_ModelAsInputSidePacket) {
std::string graph_proto = R"(
input_stream: "tensor_in"
node {
calculator: "ConstantSidePacketCalculator"
output_side_packet: "PACKET:model_path"
options: {
[mediapipe.ConstantSidePacketCalculatorOptions.ext]: {
packet { string_value: "mediapipe/calculators/tflite/testdata/add.bin" }
}
}
}
node {
calculator: "LocalFileContentsCalculator"
input_side_packet: "FILE_PATH:model_path"
output_side_packet: "CONTENTS:model_blob"
}
node {
calculator: "TfLiteModelCalculator"
input_side_packet: "MODEL_BLOB:model_blob"
output_side_packet: "MODEL:model"
}
node {
calculator: "TfLiteInferenceCalculator"
input_stream: "TENSORS:tensor_in"
output_stream: "TENSORS:tensor_out"
input_side_packet: "MODEL:model"
options {
[mediapipe.TfLiteInferenceCalculatorOptions.ext] {
use_gpu: false
}
}
}
)";
DoSmokeTest(graph_proto);
} }
} // namespace mediapipe } // namespace mediapipe
@@ -0,0 +1,86 @@
// Copyright 2020 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 <functional>
#include <memory>
#include <string>
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/packet.h"
#include "mediapipe/framework/port/ret_check.h"
#include "tensorflow/lite/model.h"
namespace mediapipe {
// Loads TfLite model from model blob specified as input side packet and outputs
// corresponding side packet.
//
// Input side packets:
// MODEL_BLOB - TfLite model blob/file-contents (std::string). You can read
// model blob from file (using whatever APIs you have) and pass
// it to the graph as input side packet or you can use some of
// calculators like LocalFileContentsCalculator to get model
// blob and use it as input here.
//
// Output side packets:
// MODEL - TfLite model. (std::unique_ptr<tflite::FlatBufferModel,
// std::function<void(tflite::FlatBufferModel*)>>)
//
// Example use:
//
// node {
// calculator: "TfLiteModelCalculator"
// input_side_packet: "MODEL_BLOB:model_blob"
// output_side_packet: "MODEL:model"
// }
//
class TfLiteModelCalculator : public CalculatorBase {
public:
using TfLiteModelPtr =
std::unique_ptr<tflite::FlatBufferModel,
std::function<void(tflite::FlatBufferModel*)>>;
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->InputSidePackets().Tag("MODEL_BLOB").Set<std::string>();
cc->OutputSidePackets().Tag("MODEL").Set<TfLiteModelPtr>();
return ::mediapipe::OkStatus();
}
::mediapipe::Status Open(CalculatorContext* cc) override {
const Packet& model_packet = cc->InputSidePackets().Tag("MODEL_BLOB");
const std::string& model_blob = model_packet.Get<std::string>();
std::unique_ptr<tflite::FlatBufferModel> model =
tflite::FlatBufferModel::BuildFromBuffer(model_blob.data(),
model_blob.size());
RET_CHECK(model) << "Failed to load TfLite model from blob.";
cc->OutputSidePackets().Tag("MODEL").Set(
MakePacket<TfLiteModelPtr>(TfLiteModelPtr(
model.release(), [model_packet](tflite::FlatBufferModel* model) {
// Keeping model_packet in order to keep underlying model blob
// which can be released only after TfLite model is not needed
// anymore (deleted).
delete model;
})));
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) override {
return ::mediapipe::OkStatus();
}
};
REGISTER_CALCULATOR(TfLiteModelCalculator);
} // namespace mediapipe
@@ -0,0 +1,88 @@
// Copyright 2020 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 "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" // NOLINT
#include "tensorflow/lite/model.h"
namespace mediapipe {
TEST(TfLiteModelCalculatorTest, SmokeTest) {
// Prepare single calculator graph to and wait for packets.
CalculatorGraphConfig graph_config = ParseTextProtoOrDie<
CalculatorGraphConfig>(
R"(
node {
calculator: "ConstantSidePacketCalculator"
output_side_packet: "PACKET:model_path"
options: {
[mediapipe.ConstantSidePacketCalculatorOptions.ext]: {
packet {
string_value: "mediapipe/calculators/tflite/testdata/add.bin"
}
}
}
}
node {
calculator: "LocalFileContentsCalculator"
input_side_packet: "FILE_PATH:model_path"
output_side_packet: "CONTENTS:model_blob"
}
node {
calculator: "TfLiteModelCalculator"
input_side_packet: "MODEL_BLOB:model_blob"
output_side_packet: "MODEL:model"
}
)");
CalculatorGraph graph(graph_config);
MP_ASSERT_OK(graph.StartRun({}));
MP_ASSERT_OK(graph.WaitUntilIdle());
auto status_or_packet = graph.GetOutputSidePacket("model");
MP_ASSERT_OK(status_or_packet);
auto model_packet = status_or_packet.ValueOrDie();
const auto& model = model_packet.Get<
std::unique_ptr<tflite::FlatBufferModel,
std::function<void(tflite::FlatBufferModel*)>>>();
auto expected_model = tflite::FlatBufferModel::BuildFromFile(
"mediapipe/calculators/tflite/testdata/add.bin");
EXPECT_EQ(model->GetModel()->version(),
expected_model->GetModel()->version());
EXPECT_EQ(model->GetModel()->buffers()->size(),
expected_model->GetModel()->buffers()->size());
const int num_subgraphs = expected_model->GetModel()->subgraphs()->size();
EXPECT_EQ(model->GetModel()->subgraphs()->size(), num_subgraphs);
for (int i = 0; i < num_subgraphs; ++i) {
const auto* expected_subgraph =
expected_model->GetModel()->subgraphs()->Get(i);
const auto* subgraph = model->GetModel()->subgraphs()->Get(i);
const int num_tensors = expected_subgraph->tensors()->size();
EXPECT_EQ(subgraph->tensors()->size(), num_tensors);
for (int j = 0; j < num_tensors; ++j) {
EXPECT_EQ(subgraph->tensors()->Get(j)->name()->str(),
expected_subgraph->tensors()->Get(j)->name()->str());
}
}
}
} // namespace mediapipe
@@ -129,22 +129,43 @@ REGISTER_CALCULATOR(TfLiteTensorsToClassificationCalculator);
num_classes *= raw_score_tensor->dims->data[i]; num_classes *= raw_score_tensor->dims->data[i];
} }
if (options_.binary_classification()) {
RET_CHECK_EQ(num_classes, 1);
// Number of classes for binary classification.
num_classes = 2;
}
if (label_map_loaded_) { if (label_map_loaded_) {
RET_CHECK_EQ(num_classes, label_map_.size()); RET_CHECK_EQ(num_classes, label_map_.size());
} }
const float* raw_scores = raw_score_tensor->data.f; const float* raw_scores = raw_score_tensor->data.f;
auto classification_list = absl::make_unique<ClassificationList>(); auto classification_list = absl::make_unique<ClassificationList>();
for (int i = 0; i < num_classes; ++i) { if (options_.binary_classification()) {
if (options_.has_min_score_threshold() && Classification* class_first = classification_list->add_classification();
raw_scores[i] < options_.min_score_threshold()) { Classification* class_second = classification_list->add_classification();
continue; class_first->set_index(0);
} class_second->set_index(1);
Classification* classification = classification_list->add_classification(); class_first->set_score(raw_scores[0]);
classification->set_index(i); class_second->set_score(1. - raw_scores[0]);
classification->set_score(raw_scores[i]);
if (label_map_loaded_) { if (label_map_loaded_) {
classification->set_label(label_map_[i]); class_first->set_label(label_map_[0]);
class_second->set_label(label_map_[1]);
}
} else {
for (int i = 0; i < num_classes; ++i) {
if (options_.has_min_score_threshold() &&
raw_scores[i] < options_.min_score_threshold()) {
continue;
}
Classification* classification =
classification_list->add_classification();
classification->set_index(i);
classification->set_score(raw_scores[i]);
if (label_map_loaded_) {
classification->set_label(label_map_[i]);
}
} }
} }
@@ -32,4 +32,10 @@ message TfLiteTensorsToClassificationCalculatorOptions {
optional int32 top_k = 2; optional int32 top_k = 2;
// Path to a label map file for getting the actual name of class ids. // Path to a label map file for getting the actual name of class ids.
optional string label_map_path = 3; optional string label_map_path = 3;
// Whether the input is a single float for binary classification.
// When true, only a single float is expected in the input tensor and the
// label map, if provided, is expected to have exactly two labels.
// The single score(float) represent the probability of first label, and
// 1 - score is the probabilility of the second label.
optional bool binary_classification = 4;
} }
@@ -145,7 +145,7 @@ REGISTER_CALCULATOR(TfLiteTensorsToLandmarksCalculator);
? cc->InputSidePackets().Tag("FLIP_HORIZONTALLY").Get<bool>() ? cc->InputSidePackets().Tag("FLIP_HORIZONTALLY").Get<bool>()
: options_.flip_horizontally(); : options_.flip_horizontally();
flip_horizontally_ = flip_vertically_ =
cc->InputSidePackets().HasTag("FLIP_VERTICALLY") cc->InputSidePackets().HasTag("FLIP_VERTICALLY")
? cc->InputSidePackets().Tag("FLIP_VERTICALLY").Get<bool>() ? cc->InputSidePackets().Tag("FLIP_VERTICALLY").Get<bool>()
: options_.flip_vertically(); : options_.flip_vertically();
+5 -5
View File
@@ -15,11 +15,11 @@
#ifndef MEDIAPIPE_CALCULATORS_TFLITE_UTIL_H_ #ifndef MEDIAPIPE_CALCULATORS_TFLITE_UTIL_H_
#define MEDIAPIPE_CALCULATORS_TFLITE_UTIL_H_ #define MEDIAPIPE_CALCULATORS_TFLITE_UTIL_H_
#define RET_CHECK_CALL(call) \ #define RET_CHECK_CALL(call) \
do { \ do { \
const auto status = (call); \ const auto status = (call); \
if (ABSL_PREDICT_FALSE(!status.ok())) \ if (ABSL_PREDICT_FALSE(!status.ok())) \
return ::mediapipe::InternalError(status.error_message()); \ return ::mediapipe::InternalError(status.message()); \
} while (0); } while (0);
#endif // MEDIAPIPE_CALCULATORS_TFLITE_UTIL_H_ #endif // MEDIAPIPE_CALCULATORS_TFLITE_UTIL_H_
+18 -4
View File
@@ -321,7 +321,7 @@ cc_library(
"//mediapipe:android": [ "//mediapipe:android": [
"//mediapipe/util/android/file/base", "//mediapipe/util/android/file/base",
], ],
"//mediapipe:apple": [ "//mediapipe:ios": [
"//mediapipe/util/android/file/base", "//mediapipe/util/android/file/base",
], ],
"//mediapipe:macos": [ "//mediapipe:macos": [
@@ -349,7 +349,7 @@ cc_library(
"//mediapipe:android": [ "//mediapipe:android": [
"//mediapipe/util/android/file/base", "//mediapipe/util/android/file/base",
], ],
"//mediapipe:apple": [ "//mediapipe:ios": [
"//mediapipe/util/android/file/base", "//mediapipe/util/android/file/base",
], ],
"//mediapipe:macos": [ "//mediapipe:macos": [
@@ -926,7 +926,7 @@ cc_library(
"//mediapipe:android": [ "//mediapipe:android": [
"//mediapipe/util/android/file/base", "//mediapipe/util/android/file/base",
], ],
"//mediapipe:apple": [ "//mediapipe:ios": [
"//mediapipe/util/android/file/base", "//mediapipe/util/android/file/base",
], ],
"//mediapipe:macos": [ "//mediapipe:macos": [
@@ -971,9 +971,9 @@ cc_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/port:file_helpers",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
"//mediapipe/util:resource_util",
], ],
alwayslink = 1, alwayslink = 1,
) )
@@ -998,6 +998,7 @@ cc_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:classification_cc_proto",
"//mediapipe/framework/formats:landmark_cc_proto", "//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/formats:rect_cc_proto", "//mediapipe/framework/formats:rect_cc_proto",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
@@ -1015,6 +1016,7 @@ cc_library(
deps = [ deps = [
":collection_has_min_size_calculator_cc_proto", ":collection_has_min_size_calculator_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:landmark_cc_proto",
"//mediapipe/framework/formats:rect_cc_proto", "//mediapipe/framework/formats:rect_cc_proto",
"//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
@@ -1022,6 +1024,18 @@ cc_library(
alwayslink = 1, alwayslink = 1,
) )
cc_test(
name = "collection_has_min_size_calculator_test",
srcs = ["collection_has_min_size_calculator_test.cc"],
deps = [
":collection_has_min_size_calculator",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework:calculator_runner",
"//mediapipe/framework/port:gtest_main",
"//mediapipe/framework/port:parse_text_proto",
],
)
cc_library( cc_library(
name = "association_calculator", name = "association_calculator",
hdrs = ["association_calculator.h"], hdrs = ["association_calculator.h"],
@@ -55,7 +55,7 @@ size_t RoundUp(size_t n, size_t m) { return ((n + m - 1) / m) * m; } // NOLINT
// When using GPU, this color will become transparent when the calculator // When using GPU, this color will become transparent when the calculator
// merges the annotation overlay with the image frame. As a result, drawing in // merges the annotation overlay with the image frame. As a result, drawing in
// this color is not supported and it should be set to something unlikely used. // this color is not supported and it should be set to something unlikely used.
constexpr int kAnnotationBackgroundColor[] = {100, 101, 102}; constexpr uchar kAnnotationBackgroundColor = 2; // Grayscale value.
} // namespace } // namespace
// A calculator for rendering data on images. // A calculator for rendering data on images.
@@ -491,11 +491,9 @@ REGISTER_CALCULATOR(AnnotationOverlayCalculator);
if (format != mediapipe::ImageFormat::SRGBA && if (format != mediapipe::ImageFormat::SRGBA &&
format != mediapipe::ImageFormat::SRGB) format != mediapipe::ImageFormat::SRGB)
RET_CHECK_FAIL() << "Unsupported GPU input format: " << format; RET_CHECK_FAIL() << "Unsupported GPU input format: " << format;
image_mat = absl::make_unique<cv::Mat>(height_, width_, CV_8UC3);
image_mat = absl::make_unique<cv::Mat>( memset(image_mat->data, kAnnotationBackgroundColor,
height_, width_, CV_8UC3, height_ * width_ * image_mat->elemSize());
cv::Scalar(kAnnotationBackgroundColor[0], kAnnotationBackgroundColor[1],
kAnnotationBackgroundColor[2]));
} else { } else {
image_mat = absl::make_unique<cv::Mat>( image_mat = absl::make_unique<cv::Mat>(
options_.canvas_height_px(), options_.canvas_width_px(), CV_8UC3, options_.canvas_height_px(), options_.canvas_width_px(), CV_8UC3,
@@ -617,9 +615,9 @@ REGISTER_CALCULATOR(AnnotationOverlayCalculator);
glUniform1i(glGetUniformLocation(program_, "input_frame"), 1); glUniform1i(glGetUniformLocation(program_, "input_frame"), 1);
glUniform1i(glGetUniformLocation(program_, "overlay"), 2); glUniform1i(glGetUniformLocation(program_, "overlay"), 2);
glUniform3f(glGetUniformLocation(program_, "transparent_color"), glUniform3f(glGetUniformLocation(program_, "transparent_color"),
kAnnotationBackgroundColor[0] / 255.0, kAnnotationBackgroundColor / 255.0,
kAnnotationBackgroundColor[1] / 255.0, kAnnotationBackgroundColor / 255.0,
kAnnotationBackgroundColor[2] / 255.0); kAnnotationBackgroundColor / 255.0);
// Init texture for opencv rendered frame. // Init texture for opencv rendered frame.
const auto& input_frame = const auto& input_frame =
@@ -15,6 +15,9 @@
#include "mediapipe/calculators/util/collection_has_min_size_calculator.h" #include "mediapipe/calculators/util/collection_has_min_size_calculator.h"
#include <vector>
#include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/formats/rect.pb.h" #include "mediapipe/framework/formats/rect.pb.h"
namespace mediapipe { namespace mediapipe {
@@ -23,4 +26,9 @@ typedef CollectionHasMinSizeCalculator<std::vector<::mediapipe::NormalizedRect>>
NormalizedRectVectorHasMinSizeCalculator; NormalizedRectVectorHasMinSizeCalculator;
REGISTER_CALCULATOR(NormalizedRectVectorHasMinSizeCalculator); REGISTER_CALCULATOR(NormalizedRectVectorHasMinSizeCalculator);
typedef CollectionHasMinSizeCalculator<
std::vector<::mediapipe::NormalizedLandmarkList>>
NormalizedLandmarkListVectorHasMinSizeCalculator;
REGISTER_CALCULATOR(NormalizedLandmarkListVectorHasMinSizeCalculator);
} // namespace mediapipe } // namespace mediapipe
@@ -0,0 +1,156 @@
// Copyright 2020 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/calculators/util/collection_has_min_size_calculator.h"
#include <memory>
#include <string>
#include <vector>
#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" // NOLINT
namespace mediapipe {
typedef CollectionHasMinSizeCalculator<std::vector<int>>
TestIntCollectionHasMinSizeCalculator;
REGISTER_CALCULATOR(TestIntCollectionHasMinSizeCalculator);
void AddInputVector(const std::vector<int>& input, int64 timestamp,
CalculatorRunner* runner) {
runner->MutableInputs()
->Tag("ITERABLE")
.packets.push_back(
MakePacket<std::vector<int>>(input).At(Timestamp(timestamp)));
}
TEST(TestIntCollectionHasMinSizeCalculator, DoesHaveMinSize) {
CalculatorGraphConfig::Node node_config =
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
calculator: "TestIntCollectionHasMinSizeCalculator"
input_stream: "ITERABLE:input_vector"
output_stream: "output_vector"
options {
[mediapipe.CollectionHasMinSizeCalculatorOptions.ext] { min_size: 2 }
}
)");
CalculatorRunner runner(node_config);
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
AddInputVector({1, 2}, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
EXPECT_EQ(1, outputs.size());
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
EXPECT_TRUE(outputs[0].Get<bool>());
AddInputVector({1, 2, 3}, /*timestamp=*/2, &runner);
MP_ASSERT_OK(runner.Run());
EXPECT_EQ(2, outputs.size());
EXPECT_EQ(Timestamp(2), outputs[1].Timestamp());
EXPECT_TRUE(outputs[1].Get<bool>());
}
TEST(TestIntCollectionHasMinSizeCalculator,
DoesHaveMinSize_MinSizeAsSidePacket) {
CalculatorGraphConfig::Node node_config =
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
calculator: "TestIntCollectionHasMinSizeCalculator"
input_stream: "ITERABLE:input_vector"
input_side_packet: "min_size"
output_stream: "output_vector"
)");
CalculatorRunner runner(node_config);
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
runner.MutableSidePackets()->Index(0) = MakePacket<int>(2);
AddInputVector({1, 2}, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
EXPECT_EQ(1, outputs.size());
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
EXPECT_TRUE(outputs[0].Get<bool>());
AddInputVector({1, 2, 3}, /*timestamp=*/2, &runner);
MP_ASSERT_OK(runner.Run());
EXPECT_EQ(2, outputs.size());
EXPECT_EQ(Timestamp(2), outputs[1].Timestamp());
EXPECT_TRUE(outputs[1].Get<bool>());
}
TEST(TestIntCollectionHasMinSizeCalculator, DoesNotHaveMinSize) {
CalculatorGraphConfig::Node node_config =
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
calculator: "TestIntCollectionHasMinSizeCalculator"
input_stream: "ITERABLE:input_vector"
output_stream: "output_vector"
options {
[mediapipe.CollectionHasMinSizeCalculatorOptions.ext] { min_size: 3 }
}
)");
CalculatorRunner runner(node_config);
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
AddInputVector({1}, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
EXPECT_EQ(1, outputs.size());
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
EXPECT_FALSE(outputs[0].Get<bool>());
AddInputVector({1, 2}, /*timestamp=*/2, &runner);
MP_ASSERT_OK(runner.Run());
EXPECT_EQ(2, outputs.size());
EXPECT_EQ(Timestamp(2), outputs[1].Timestamp());
EXPECT_FALSE(outputs[1].Get<bool>());
}
TEST(TestIntCollectionHasMinSizeCalculator,
DoesNotHaveMinSize_MinSizeAsSidePacket) {
CalculatorGraphConfig::Node node_config =
ParseTextProtoOrDie<CalculatorGraphConfig::Node>(R"(
calculator: "TestIntCollectionHasMinSizeCalculator"
input_stream: "ITERABLE:input_vector"
input_side_packet: "min_size"
output_stream: "output_vector"
)");
CalculatorRunner runner(node_config);
const std::vector<Packet>& outputs = runner.Outputs().Index(0).packets;
runner.MutableSidePackets()->Index(0) = MakePacket<int>(3);
AddInputVector({1}, /*timestamp=*/1, &runner);
MP_ASSERT_OK(runner.Run());
EXPECT_EQ(1, outputs.size());
EXPECT_EQ(Timestamp(1), outputs[0].Timestamp());
EXPECT_FALSE(outputs[0].Get<bool>());
AddInputVector({1, 2}, /*timestamp=*/2, &runner);
MP_ASSERT_OK(runner.Run());
EXPECT_EQ(2, outputs.size());
EXPECT_EQ(Timestamp(2), outputs[1].Timestamp());
EXPECT_FALSE(outputs[1].Get<bool>());
}
} // namespace mediapipe
@@ -17,6 +17,7 @@
#include <vector> #include <vector>
#include "mediapipe/framework/formats/classification.pb.h"
#include "mediapipe/framework/formats/landmark.pb.h" #include "mediapipe/framework/formats/landmark.pb.h"
#include "mediapipe/framework/formats/rect.pb.h" #include "mediapipe/framework/formats/rect.pb.h"
@@ -31,4 +32,8 @@ typedef FilterCollectionCalculator<
FilterLandmarkListCollectionCalculator; FilterLandmarkListCollectionCalculator;
REGISTER_CALCULATOR(FilterLandmarkListCollectionCalculator); REGISTER_CALCULATOR(FilterLandmarkListCollectionCalculator);
typedef FilterCollectionCalculator<std::vector<::mediapipe::ClassificationList>>
FilterClassificationListCollectionCalculator;
REGISTER_CALCULATOR(FilterClassificationListCollectionCalculator);
} // namespace mediapipe } // namespace mediapipe
@@ -128,16 +128,19 @@ REGISTER_CALCULATOR(LabelsToRenderDataCalculator);
} else { } else {
const std::vector<std::string>& label_vector = const std::vector<std::string>& label_vector =
cc->Inputs().Tag("LABELS").Get<std::vector<std::string>>(); cc->Inputs().Tag("LABELS").Get<std::vector<std::string>>();
std::vector<float> score_vector;
if (cc->Inputs().HasTag("SCORES")) {
score_vector = cc->Inputs().Tag("SCORES").Get<std::vector<float>>();
}
CHECK_EQ(label_vector.size(), score_vector.size());
labels.resize(label_vector.size()); labels.resize(label_vector.size());
scores.resize(label_vector.size());
for (int i = 0; i < label_vector.size(); ++i) { for (int i = 0; i < label_vector.size(); ++i) {
labels[i] = label_vector[i]; labels[i] = label_vector[i];
scores[i] = score_vector[i]; }
if (cc->Inputs().HasTag("SCORES")) {
std::vector<float> score_vector =
cc->Inputs().Tag("SCORES").Get<std::vector<float>>();
CHECK_EQ(label_vector.size(), score_vector.size());
scores.resize(label_vector.size());
for (int i = 0; i < label_vector.size(); ++i) {
scores[i] = score_vector[i];
}
} }
} }
@@ -29,6 +29,7 @@ namespace {
constexpr char kLandmarksTag[] = "LANDMARKS"; constexpr char kLandmarksTag[] = "LANDMARKS";
constexpr char kNormLandmarksTag[] = "NORM_LANDMARKS"; constexpr char kNormLandmarksTag[] = "NORM_LANDMARKS";
constexpr char kRenderScaleTag[] = "RENDER_SCALE";
constexpr char kRenderDataTag[] = "RENDER_DATA"; constexpr char kRenderDataTag[] = "RENDER_DATA";
constexpr char kLandmarkLabel[] = "KEYPOINT"; constexpr char kLandmarkLabel[] = "KEYPOINT";
constexpr int kMaxLandmarkThickness = 18; constexpr int kMaxLandmarkThickness = 18;
@@ -71,6 +72,83 @@ void SetColorSizeValueFromZ(float z, float z_min, float z_max,
render_annotation->set_thickness(thickness); render_annotation->set_thickness(thickness);
} }
template <class LandmarkType>
void AddConnectionToRenderData(const LandmarkType& start,
const LandmarkType& end, int gray_val1,
int gray_val2, float thickness, bool normalized,
RenderData* render_data) {
auto* connection_annotation = render_data->add_render_annotations();
RenderAnnotation::GradientLine* line =
connection_annotation->mutable_gradient_line();
line->set_x_start(start.x());
line->set_y_start(start.y());
line->set_x_end(end.x());
line->set_y_end(end.y());
line->set_normalized(normalized);
line->mutable_color1()->set_r(gray_val1);
line->mutable_color1()->set_g(gray_val1);
line->mutable_color1()->set_b(gray_val1);
line->mutable_color2()->set_r(gray_val2);
line->mutable_color2()->set_g(gray_val2);
line->mutable_color2()->set_b(gray_val2);
connection_annotation->set_thickness(thickness);
}
template <class LandmarkListType, class LandmarkType>
void AddConnectionsWithDepth(const LandmarkListType& landmarks,
const std::vector<int>& landmark_connections,
float thickness, bool normalized, float min_z,
float max_z, RenderData* render_data) {
for (int i = 0; i < landmark_connections.size(); i += 2) {
const auto& ld0 = landmarks.landmark(landmark_connections[i]);
const auto& ld1 = landmarks.landmark(landmark_connections[i + 1]);
const int gray_val1 =
255 - static_cast<int>(Remap(ld0.z(), min_z, max_z, 255));
const int gray_val2 =
255 - static_cast<int>(Remap(ld1.z(), min_z, max_z, 255));
AddConnectionToRenderData<LandmarkType>(ld0, ld1, gray_val1, gray_val2,
thickness, normalized, render_data);
}
}
template <class LandmarkType>
void AddConnectionToRenderData(const LandmarkType& start,
const LandmarkType& end,
const Color& connection_color, float thickness,
bool normalized, RenderData* render_data) {
auto* connection_annotation = render_data->add_render_annotations();
RenderAnnotation::Line* line = connection_annotation->mutable_line();
line->set_x_start(start.x());
line->set_y_start(start.y());
line->set_x_end(end.x());
line->set_y_end(end.y());
line->set_normalized(normalized);
SetColor(connection_annotation, connection_color);
connection_annotation->set_thickness(thickness);
}
template <class LandmarkListType, class LandmarkType>
void AddConnections(const LandmarkListType& landmarks,
const std::vector<int>& landmark_connections,
const Color& connection_color, float thickness,
bool normalized, RenderData* render_data) {
for (int i = 0; i < landmark_connections.size(); i += 2) {
const auto& ld0 = landmarks.landmark(landmark_connections[i]);
const auto& ld1 = landmarks.landmark(landmark_connections[i + 1]);
AddConnectionToRenderData<LandmarkType>(ld0, ld1, connection_color,
thickness, normalized, render_data);
}
}
RenderAnnotation* AddPointRenderData(const Color& landmark_color,
float thickness, RenderData* render_data) {
auto* landmark_data_annotation = render_data->add_render_annotations();
landmark_data_annotation->set_scene_tag(kLandmarkLabel);
SetColor(landmark_data_annotation, landmark_color);
landmark_data_annotation->set_thickness(thickness);
return landmark_data_annotation;
}
} // namespace } // namespace
// A calculator that converts Landmark proto to RenderData proto for // A calculator that converts Landmark proto to RenderData proto for
@@ -107,29 +185,6 @@ class LandmarksToRenderDataCalculator : public CalculatorBase {
::mediapipe::Status Process(CalculatorContext* cc) override; ::mediapipe::Status Process(CalculatorContext* cc) override;
private: private:
static void AddConnectionToRenderData(
float start_x, float start_y, float end_x, float end_y,
const LandmarksToRenderDataCalculatorOptions& options, bool normalized,
RenderData* render_data);
static void SetRenderAnnotationColorThickness(
const LandmarksToRenderDataCalculatorOptions& options,
RenderAnnotation* render_annotation);
static RenderAnnotation* AddPointRenderData(
const LandmarksToRenderDataCalculatorOptions& options,
RenderData* render_data);
static void AddConnectionToRenderData(
float start_x, float start_y, float end_x, float end_y,
const LandmarksToRenderDataCalculatorOptions& options, bool normalized,
int gray_val1, int gray_val2, RenderData* render_data);
template <class LandmarkListType>
void AddConnections(const LandmarkListType& landmarks, bool normalized,
RenderData* render_data);
template <class LandmarkListType>
void AddConnectionsWithDepth(const LandmarkListType& landmarks,
bool normalized, float min_z, float max_z,
RenderData* render_data);
LandmarksToRenderDataCalculatorOptions options_; LandmarksToRenderDataCalculatorOptions options_;
}; };
REGISTER_CALCULATOR(LandmarksToRenderDataCalculator); REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
@@ -150,6 +205,9 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
if (cc->Inputs().HasTag(kNormLandmarksTag)) { if (cc->Inputs().HasTag(kNormLandmarksTag)) {
cc->Inputs().Tag(kNormLandmarksTag).Set<NormalizedLandmarkList>(); cc->Inputs().Tag(kNormLandmarksTag).Set<NormalizedLandmarkList>();
} }
if (cc->Inputs().HasTag(kRenderScaleTag)) {
cc->Inputs().Tag(kRenderScaleTag).Set<float>();
}
cc->Outputs().Tag(kRenderDataTag).Set<RenderData>(); cc->Outputs().Tag(kRenderDataTag).Set<RenderData>();
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
@@ -169,11 +227,26 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
float z_min = 0.f; float z_min = 0.f;
float z_max = 0.f; float z_max = 0.f;
// Apply scale to `thickness` of rendered landmarks and connections to make
// them bigger when object (e.g. pose, hand or face) is closer/bigger and
// snaller when object is further/smaller.
float thickness = options_.thickness();
if (cc->Inputs().HasTag(kRenderScaleTag)) {
const float render_scale = cc->Inputs().Tag(kRenderScaleTag).Get<float>();
thickness *= render_scale;
}
// Parse landmarks connections to a vector.
RET_CHECK_EQ(options_.landmark_connections_size() % 2, 0)
<< "Number of entries in landmark connections must be a multiple of 2";
std::vector<int> landmark_connections;
for (int i = 0; i < options_.landmark_connections_size(); i += 1) {
landmark_connections.push_back(options_.landmark_connections(i));
}
if (cc->Inputs().HasTag(kLandmarksTag)) { if (cc->Inputs().HasTag(kLandmarksTag)) {
const LandmarkList& landmarks = const LandmarkList& landmarks =
cc->Inputs().Tag(kLandmarksTag).Get<LandmarkList>(); cc->Inputs().Tag(kLandmarksTag).Get<LandmarkList>();
RET_CHECK_EQ(options_.landmark_connections_size() % 2, 0)
<< "Number of entries in landmark connections must be a multiple of 2";
if (visualize_depth) { if (visualize_depth) {
GetMinMaxZ<LandmarkList, Landmark>(landmarks, &z_min, &z_max); GetMinMaxZ<LandmarkList, Landmark>(landmarks, &z_min, &z_max);
} }
@@ -181,8 +254,8 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
visualize_depth &= ((z_max - z_min) > 1e-3); visualize_depth &= ((z_max - z_min) > 1e-3);
for (int i = 0; i < landmarks.landmark_size(); ++i) { for (int i = 0; i < landmarks.landmark_size(); ++i) {
const Landmark& landmark = landmarks.landmark(i); const Landmark& landmark = landmarks.landmark(i);
auto* landmark_data_render = auto* landmark_data_render = AddPointRenderData(
AddPointRenderData(options_, render_data.get()); options_.landmark_color(), thickness, render_data.get());
if (visualize_depth) { if (visualize_depth) {
SetColorSizeValueFromZ(landmark.z(), z_min, z_max, SetColorSizeValueFromZ(landmark.z(), z_min, z_max,
landmark_data_render); landmark_data_render);
@@ -193,19 +266,19 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
landmark_data->set_y(landmark.y()); landmark_data->set_y(landmark.y());
} }
if (visualize_depth) { if (visualize_depth) {
AddConnectionsWithDepth<LandmarkList>(landmarks, /*normalized=*/false, AddConnectionsWithDepth<LandmarkList, Landmark>(
z_min, z_max, render_data.get()); landmarks, landmark_connections, thickness, /*normalized=*/false,
z_min, z_max, render_data.get());
} else { } else {
AddConnections<LandmarkList>(landmarks, /*normalized=*/false, AddConnections<LandmarkList, Landmark>(
render_data.get()); landmarks, landmark_connections, options_.connection_color(),
thickness, /*normalized=*/false, render_data.get());
} }
} }
if (cc->Inputs().HasTag(kNormLandmarksTag)) { if (cc->Inputs().HasTag(kNormLandmarksTag)) {
const NormalizedLandmarkList& landmarks = const NormalizedLandmarkList& landmarks =
cc->Inputs().Tag(kNormLandmarksTag).Get<NormalizedLandmarkList>(); cc->Inputs().Tag(kNormLandmarksTag).Get<NormalizedLandmarkList>();
RET_CHECK_EQ(options_.landmark_connections_size() % 2, 0)
<< "Number of entries in landmark connections must be a multiple of 2";
if (visualize_depth) { if (visualize_depth) {
GetMinMaxZ<NormalizedLandmarkList, NormalizedLandmark>(landmarks, &z_min, GetMinMaxZ<NormalizedLandmarkList, NormalizedLandmark>(landmarks, &z_min,
&z_max); &z_max);
@@ -214,8 +287,8 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
visualize_depth &= ((z_max - z_min) > 1e-3); visualize_depth &= ((z_max - z_min) > 1e-3);
for (int i = 0; i < landmarks.landmark_size(); ++i) { for (int i = 0; i < landmarks.landmark_size(); ++i) {
const NormalizedLandmark& landmark = landmarks.landmark(i); const NormalizedLandmark& landmark = landmarks.landmark(i);
auto* landmark_data_render = auto* landmark_data_render = AddPointRenderData(
AddPointRenderData(options_, render_data.get()); options_.landmark_color(), thickness, render_data.get());
if (visualize_depth) { if (visualize_depth) {
SetColorSizeValueFromZ(landmark.z(), z_min, z_max, SetColorSizeValueFromZ(landmark.z(), z_min, z_max,
landmark_data_render); landmark_data_render);
@@ -226,11 +299,13 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
landmark_data->set_y(landmark.y()); landmark_data->set_y(landmark.y());
} }
if (visualize_depth) { if (visualize_depth) {
AddConnectionsWithDepth<NormalizedLandmarkList>( AddConnectionsWithDepth<NormalizedLandmarkList, NormalizedLandmark>(
landmarks, /*normalized=*/true, z_min, z_max, render_data.get()); landmarks, landmark_connections, thickness, /*normalized=*/true,
z_min, z_max, render_data.get());
} else { } else {
AddConnections<NormalizedLandmarkList>(landmarks, /*normalized=*/true, AddConnections<NormalizedLandmarkList, NormalizedLandmark>(
render_data.get()); landmarks, landmark_connections, options_.connection_color(),
thickness, /*normalized=*/true, render_data.get());
} }
} }
@@ -240,84 +315,4 @@ REGISTER_CALCULATOR(LandmarksToRenderDataCalculator);
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
template <class LandmarkListType>
void LandmarksToRenderDataCalculator::AddConnectionsWithDepth(
const LandmarkListType& landmarks, bool normalized, float min_z,
float max_z, RenderData* render_data) {
for (int i = 0; i < options_.landmark_connections_size(); i += 2) {
const auto& ld0 = landmarks.landmark(options_.landmark_connections(i));
const auto& ld1 = landmarks.landmark(options_.landmark_connections(i + 1));
const int gray_val1 =
255 - static_cast<int>(Remap(ld0.z(), min_z, max_z, 255));
const int gray_val2 =
255 - static_cast<int>(Remap(ld1.z(), min_z, max_z, 255));
AddConnectionToRenderData(ld0.x(), ld0.y(), ld1.x(), ld1.y(), options_,
normalized, gray_val1, gray_val2, render_data);
}
}
void LandmarksToRenderDataCalculator::AddConnectionToRenderData(
float start_x, float start_y, float end_x, float end_y,
const LandmarksToRenderDataCalculatorOptions& options, bool normalized,
int gray_val1, int gray_val2, RenderData* render_data) {
auto* connection_annotation = render_data->add_render_annotations();
RenderAnnotation::GradientLine* line =
connection_annotation->mutable_gradient_line();
line->set_x_start(start_x);
line->set_y_start(start_y);
line->set_x_end(end_x);
line->set_y_end(end_y);
line->set_normalized(normalized);
line->mutable_color1()->set_r(gray_val1);
line->mutable_color1()->set_g(gray_val1);
line->mutable_color1()->set_b(gray_val1);
line->mutable_color2()->set_r(gray_val2);
line->mutable_color2()->set_g(gray_val2);
line->mutable_color2()->set_b(gray_val2);
connection_annotation->set_thickness(options.thickness());
}
template <class LandmarkListType>
void LandmarksToRenderDataCalculator::AddConnections(
const LandmarkListType& landmarks, bool normalized,
RenderData* render_data) {
for (int i = 0; i < options_.landmark_connections_size(); i += 2) {
const auto& ld0 = landmarks.landmark(options_.landmark_connections(i));
const auto& ld1 = landmarks.landmark(options_.landmark_connections(i + 1));
AddConnectionToRenderData(ld0.x(), ld0.y(), ld1.x(), ld1.y(), options_,
normalized, render_data);
}
}
void LandmarksToRenderDataCalculator::AddConnectionToRenderData(
float start_x, float start_y, float end_x, float end_y,
const LandmarksToRenderDataCalculatorOptions& options, bool normalized,
RenderData* render_data) {
auto* connection_annotation = render_data->add_render_annotations();
RenderAnnotation::Line* line = connection_annotation->mutable_line();
line->set_x_start(start_x);
line->set_y_start(start_y);
line->set_x_end(end_x);
line->set_y_end(end_y);
line->set_normalized(normalized);
SetColor(connection_annotation, options.connection_color());
connection_annotation->set_thickness(options.thickness());
}
RenderAnnotation* LandmarksToRenderDataCalculator::AddPointRenderData(
const LandmarksToRenderDataCalculatorOptions& options,
RenderData* render_data) {
auto* landmark_data_annotation = render_data->add_render_annotations();
landmark_data_annotation->set_scene_tag(kLandmarkLabel);
SetRenderAnnotationColorThickness(options, landmark_data_annotation);
return landmark_data_annotation;
}
void LandmarksToRenderDataCalculator::SetRenderAnnotationColorThickness(
const LandmarksToRenderDataCalculatorOptions& options,
RenderAnnotation* render_annotation) {
SetColor(render_annotation, options.landmark_color());
render_annotation->set_thickness(options.thickness());
}
} // namespace mediapipe } // namespace mediapipe
@@ -16,34 +16,80 @@
#include <string> #include <string>
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/port/file_helpers.h"
#include "mediapipe/framework/port/status.h" #include "mediapipe/framework/port/status.h"
#include "mediapipe/util/resource_util.h"
namespace mediapipe { namespace mediapipe {
namespace {
constexpr char kFilePathTag[] = "FILE_PATH";
constexpr char kContentsTag[] = "CONTENTS";
} // namespace
// The calculator takes the path to the local file as an input side packet and // The calculator takes the path to the local file as an input side packet and
// outputs the contents of that file. // outputs the contents of that file.
// //
// NOTE: file loading can be batched by providing multiple input/output side
// packets.
//
// Example config: // Example config:
// node { // node {
// calculator: "LocalFileContentsCalculator" // calculator: "LocalFileContentsCalculator"
// input_side_packet: "FILE_PATH:file_path" // input_side_packet: "FILE_PATH:file_path"
// output_side_packet: "CONTENTS:contents" // output_side_packet: "CONTENTS:contents"
// } // }
//
// node {
// calculator: "LocalFileContentsCalculator"
// input_side_packet: "FILE_PATH:0:file_path1"
// input_side_packet: "FILE_PATH:1:file_path2"
// ...
// output_side_packet: "CONTENTS:0:contents1"
// output_side_packet: "CONTENTS:1:contents2"
// ...
// }
class LocalFileContentsCalculator : public CalculatorBase { class LocalFileContentsCalculator : public CalculatorBase {
public: public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) { static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->InputSidePackets().Tag("FILE_PATH").Set<std::string>(); RET_CHECK(cc->InputSidePackets().HasTag(kFilePathTag))
cc->OutputSidePackets().Tag("CONTENTS").Set<std::string>(); << "Missing PATH input side packet(s)";
RET_CHECK(cc->OutputSidePackets().HasTag(kContentsTag))
<< "Missing CONTENTS output side packet(s)";
RET_CHECK_EQ(cc->InputSidePackets().NumEntries(kFilePathTag),
cc->OutputSidePackets().NumEntries(kContentsTag))
<< "Same number of input streams and output streams is required.";
for (CollectionItemId id = cc->InputSidePackets().BeginId(kFilePathTag);
id != cc->InputSidePackets().EndId(kFilePathTag); ++id) {
cc->InputSidePackets().Get(id).Set<std::string>();
}
for (CollectionItemId id = cc->OutputSidePackets().BeginId(kContentsTag);
id != cc->OutputSidePackets().EndId(kContentsTag); ++id) {
cc->OutputSidePackets().Get(id).Set<std::string>();
}
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
::mediapipe::Status Open(CalculatorContext* cc) override { ::mediapipe::Status Open(CalculatorContext* cc) override {
std::string contents; CollectionItemId input_id = cc->InputSidePackets().BeginId(kFilePathTag);
MP_RETURN_IF_ERROR(mediapipe::file::GetContents( CollectionItemId output_id = cc->OutputSidePackets().BeginId(kContentsTag);
cc->InputSidePackets().Tag("FILE_PATH").Get<std::string>(), &contents)); // Number of inputs and outpus is the same according to the contract.
cc->OutputSidePackets() for (; input_id != cc->InputSidePackets().EndId(kFilePathTag);
.Tag("CONTENTS") ++input_id, ++output_id) {
.Set(MakePacket<std::string>(std::move(contents))); std::string file_path =
cc->InputSidePackets().Get(input_id).Get<std::string>();
ASSIGN_OR_RETURN(file_path, PathToResourceAsFile(file_path));
std::string contents;
MP_RETURN_IF_ERROR(GetResourceContents(file_path, &contents));
cc->OutputSidePackets().Get(output_id).Set(
MakePacket<std::string>(std::move(contents)));
}
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
@@ -276,6 +276,7 @@ TEST_F(PacketLatencyCalculatorTest, DoesNotOutputUntilReferencePacketReceived) {
"delayed_packet_0", Adopt(new double()).At(Timestamp(2)))); "delayed_packet_0", Adopt(new double()).At(Timestamp(2))));
// Send a reference packet with timestamp 10 usec. // Send a reference packet with timestamp 10 usec.
simulation_clock_->Sleep(absl::Microseconds(1));
MP_ASSERT_OK(graph_.AddPacketToInputStream( MP_ASSERT_OK(graph_.AddPacketToInputStream(
"camera_frames", Adopt(new double()).At(Timestamp(10)))); "camera_frames", Adopt(new double()).At(Timestamp(10))));
simulation_clock_->Sleep(absl::Microseconds(1)); simulation_clock_->Sleep(absl::Microseconds(1));
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <algorithm>
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h" #include "absl/strings/str_join.h"
@@ -76,14 +78,16 @@ void AddTimedBoxProtoToRenderData(
RenderAnnotation::Text* text = label_annotation->mutable_text(); RenderAnnotation::Text* text = label_annotation->mutable_text();
text->set_display_text(box_proto.label()); text->set_display_text(box_proto.label());
text->set_normalized(true); text->set_normalized(true);
constexpr float text_left_start = 0.3f; constexpr float text_left_start = 0.2f;
text->set_left((1.0f - text_left_start) * box_proto.left() + text->set_left((1.0f - text_left_start) * box_proto.left() +
text_left_start * box_proto.right()); text_left_start * box_proto.right());
constexpr float text_baseline = 0.6f; constexpr float text_baseline = 0.6f;
text->set_baseline(text_baseline * box_proto.bottom() + text->set_baseline(text_baseline * box_proto.bottom() +
(1.0f - text_baseline) * box_proto.top()); (1.0f - text_baseline) * box_proto.top());
constexpr float text_height = 0.2f; constexpr float text_height = 0.1f;
text->set_font_height((box_proto.bottom() - box_proto.top()) * text_height); text->set_font_height(std::min(box_proto.bottom() - box_proto.top(),
box_proto.right() - box_proto.left()) *
text_height);
} }
} }
+100 -11
View File
@@ -65,6 +65,26 @@ proto_library(
], ],
) )
proto_library(
name = "tracked_detection_manager_calculator_proto",
srcs = ["tracked_detection_manager_calculator.proto"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework:calculator_proto",
"//mediapipe/util/tracking:tracked_detection_manager_config_proto",
],
)
proto_library(
name = "box_detector_calculator_proto",
srcs = ["box_detector_calculator.proto"],
visibility = ["//visibility:public"],
deps = [
"//mediapipe/framework:calculator_proto",
"//mediapipe/util/tracking:box_detector_proto",
],
)
proto_library( proto_library(
name = "video_pre_stream_calculator_proto", name = "video_pre_stream_calculator_proto",
srcs = ["video_pre_stream_calculator.proto"], srcs = ["video_pre_stream_calculator.proto"],
@@ -107,6 +127,28 @@ mediapipe_cc_proto_library(
deps = [":box_tracker_calculator_proto"], deps = [":box_tracker_calculator_proto"],
) )
mediapipe_cc_proto_library(
name = "tracked_detection_manager_calculator_cc_proto",
srcs = ["tracked_detection_manager_calculator.proto"],
cc_deps = [
"//mediapipe/framework:calculator_cc_proto",
"//mediapipe/util/tracking:tracked_detection_manager_config_cc_proto",
],
visibility = ["//visibility:public"],
deps = [":tracked_detection_manager_calculator_proto"],
)
mediapipe_cc_proto_library(
name = "box_detector_calculator_cc_proto",
srcs = ["box_detector_calculator.proto"],
cc_deps = [
"//mediapipe/framework:calculator_cc_proto",
"//mediapipe/util/tracking:box_detector_cc_proto",
],
visibility = ["//visibility:public"],
deps = [":box_detector_calculator_proto"],
)
mediapipe_cc_proto_library( mediapipe_cc_proto_library(
name = "video_pre_stream_calculator_cc_proto", name = "video_pre_stream_calculator_cc_proto",
srcs = ["video_pre_stream_calculator.proto"], srcs = ["video_pre_stream_calculator.proto"],
@@ -138,7 +180,7 @@ cc_library(
srcs = ["flow_to_image_calculator.cc"], srcs = ["flow_to_image_calculator.cc"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//mediapipe/calculators/video:flow_to_image_calculator_cc_proto", ":flow_to_image_calculator_cc_proto",
"//mediapipe/calculators/video/tool:flow_quantizer_model", "//mediapipe/calculators/video/tool:flow_quantizer_model",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:image_format_cc_proto", "//mediapipe/framework/formats:image_format_cc_proto",
@@ -279,11 +321,54 @@ cc_library(
alwayslink = 1, alwayslink = 1,
) )
cc_library(
name = "box_detector_calculator",
srcs = ["box_detector_calculator.cc"],
visibility = ["//visibility:public"],
deps = [
":box_detector_calculator_cc_proto",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:image_frame",
"//mediapipe/framework/formats:image_frame_opencv",
"//mediapipe/framework/formats:video_stream_header", # fixdeps: keep -- required for exobazel build.
"//mediapipe/framework/port:integral_types",
"//mediapipe/framework/port:logging",
"//mediapipe/framework/port:opencv_core",
"//mediapipe/framework/port:opencv_features2d",
"//mediapipe/framework/port:ret_check",
"//mediapipe/framework/port:status",
"//mediapipe/util:resource_util",
"//mediapipe/util/tracking",
"//mediapipe/util/tracking:box_detector",
"//mediapipe/util/tracking:box_tracker",
"//mediapipe/util/tracking:box_tracker_cc_proto",
"//mediapipe/util/tracking:flow_packager_cc_proto",
"//mediapipe/util/tracking:tracking_visualization_utilities",
] + select({
"//mediapipe:android": [
"//mediapipe/util/android/file/base",
],
"//mediapipe:ios": [
"//mediapipe/util/android/file/base",
],
"//mediapipe:macos": [
"//mediapipe/framework/port:file_helpers",
],
"//conditions:default": [
"//mediapipe/framework/port:file_helpers",
],
}),
alwayslink = 1,
)
cc_library( cc_library(
name = "tracked_detection_manager_calculator", name = "tracked_detection_manager_calculator",
srcs = ["tracked_detection_manager_calculator.cc"], srcs = ["tracked_detection_manager_calculator.cc"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
":tracked_detection_manager_calculator_cc_proto",
"//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_framework",
"//mediapipe/framework/formats:detection_cc_proto", "//mediapipe/framework/formats:detection_cc_proto",
"//mediapipe/framework/formats:location_data_cc_proto", "//mediapipe/framework/formats:location_data_cc_proto",
@@ -384,20 +469,18 @@ cc_test(
], ],
) )
MEDIAPIPE_DEPS = [
"//mediapipe/calculators/video:box_tracker_calculator",
"//mediapipe/calculators/video:flow_packager_calculator",
"//mediapipe/calculators/video:motion_analysis_calculator",
"//mediapipe/framework/stream_handler:fixed_size_input_stream_handler",
"//mediapipe/framework/stream_handler:sync_set_input_stream_handler",
]
mediapipe_binary_graph( mediapipe_binary_graph(
name = "parallel_tracker_binarypb", name = "parallel_tracker_binarypb",
graph = "testdata/parallel_tracker_graph.pbtxt", graph = "testdata/parallel_tracker_graph.pbtxt",
output_name = "testdata/parallel_tracker.binarypb", output_name = "testdata/parallel_tracker.binarypb",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = MEDIAPIPE_DEPS, deps = [
":box_tracker_calculator",
":flow_packager_calculator",
":motion_analysis_calculator",
"//mediapipe/framework/stream_handler:fixed_size_input_stream_handler",
"//mediapipe/framework/stream_handler:sync_set_input_stream_handler",
],
) )
mediapipe_binary_graph( mediapipe_binary_graph(
@@ -405,7 +488,13 @@ mediapipe_binary_graph(
graph = "testdata/tracker_graph.pbtxt", graph = "testdata/tracker_graph.pbtxt",
output_name = "testdata/tracker.binarypb", output_name = "testdata/tracker.binarypb",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = MEDIAPIPE_DEPS, deps = [
":box_tracker_calculator",
":flow_packager_calculator",
":motion_analysis_calculator",
"//mediapipe/framework/stream_handler:fixed_size_input_stream_handler",
"//mediapipe/framework/stream_handler:sync_set_input_stream_handler",
],
) )
cc_test( cc_test(
@@ -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 <stdio.h>
#include <memory>
#include <unordered_set>
#include "absl/memory/memory.h"
#include "absl/strings/numbers.h"
#include "mediapipe/calculators/video/box_detector_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/image_frame.h"
#include "mediapipe/framework/formats/image_frame_opencv.h"
#include "mediapipe/framework/formats/video_stream_header.h"
#include "mediapipe/framework/port/integral_types.h"
#include "mediapipe/framework/port/logging.h"
#include "mediapipe/framework/port/opencv_core_inc.h"
#include "mediapipe/framework/port/opencv_features2d_inc.h"
#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/framework/port/status.h"
#include "mediapipe/util/resource_util.h"
#include "mediapipe/util/tracking/box_detector.h"
#include "mediapipe/util/tracking/box_tracker.h"
#include "mediapipe/util/tracking/box_tracker.pb.h"
#include "mediapipe/util/tracking/flow_packager.pb.h"
#include "mediapipe/util/tracking/tracking.h"
#include "mediapipe/util/tracking/tracking_visualization_utilities.h"
#if defined(MEDIAPIPE_MOBILE)
#include "mediapipe/util/android/file/base/file.h"
#include "mediapipe/util/android/file/base/helpers.h"
#else
#include "mediapipe/framework/port/file_helpers.h"
#endif
namespace mediapipe {
// A calculator to detect reappeared box positions from single frame.
//
// Input stream:
// TRACKING: Input tracking data (proto TrackingData) containing features and
// descriptors.
// VIDEO: Optional input video stream tracked boxes are rendered over
// (Required if VIZ is specified).
// FEATURES: Input feature points (std::vector<cv::KeyPoint>) in the original
// pixel space.
// DESCRIPTORS: Input feature descriptors (std::vector<float>). Actual feature
// dimension needs to be specified in detector_options.
// IMAGE_SIZE: Input image dimension.
// TRACKED_BOXES : input box tracking result (proto TimedBoxProtoList) from
// BoxTrackerCalculator.
// ADD_INDEX: Optional std::string containing binary format proto of type
// BoxDetectorIndex. Used for adding target index to the detector
// search index during runtime.
// CANCEL_OBJECT_ID: Optional id of box to be removed. This is recommended
// to be used with SyncSetInputStreamHandler.
// REACQ_SWITCH: Optional bool for swithcing on and off reacquisition
// functionality. User should initialize a graph with box detector
// calculator and be able to switch it on and off in runtime.
//
// Output streams:
// VIZ: Optional output video stream with rendered box positions
// (requires VIDEO to be present)
// BOXES: Optional output stream of type TimedBoxProtoList for each lost box.
//
// Imput side packets:
// INDEX_PROTO_STRING: Optional std::string containing binary format proto of
// type
// BoxDetectorIndex. Used for initializing box_detector
// with predefined template images.
// FRAME_ALIGNMENT: Optional integer to indicate alignment_boundary for
// outputing ImageFrame in "VIZ" stream.
// Set to ImageFrame::kDefaultAlignmentBoundary for
// offline pipeline to be compatible with FFmpeg.
// Set to ImageFrame::kGlDefaultAlignmentBoundary for Apps
// to be compatible with GL renderer.
// OUTPUT_INDEX_FILENAME: File path to the output index file.
class BoxDetectorCalculator : public CalculatorBase {
public:
~BoxDetectorCalculator() override = default;
static ::mediapipe::Status GetContract(CalculatorContract* cc);
::mediapipe::Status Open(CalculatorContext* cc) override;
::mediapipe::Status Process(CalculatorContext* cc) override;
::mediapipe::Status Close(CalculatorContext* cc) override;
private:
BoxDetectorCalculatorOptions options_;
std::unique_ptr<BoxDetectorInterface> box_detector_;
bool detector_switch_ = true;
uint32 frame_alignment_ = ImageFrame::kDefaultAlignmentBoundary;
bool write_index_ = false;
int box_id_ = 0;
};
REGISTER_CALCULATOR(BoxDetectorCalculator);
::mediapipe::Status BoxDetectorCalculator::GetContract(CalculatorContract* cc) {
if (cc->Inputs().HasTag("TRACKING")) {
cc->Inputs().Tag("TRACKING").Set<TrackingData>();
}
if (cc->Inputs().HasTag("TRACKED_BOXES")) {
cc->Inputs().Tag("TRACKED_BOXES").Set<TimedBoxProtoList>();
}
if (cc->Inputs().HasTag("VIDEO")) {
cc->Inputs().Tag("VIDEO").Set<ImageFrame>();
}
if (cc->Inputs().HasTag("FEATURES")) {
RET_CHECK(cc->Inputs().HasTag("DESCRIPTORS"))
<< "FEATURES and DESCRIPTORS need to be specified together.";
cc->Inputs().Tag("FEATURES").Set<std::vector<cv::KeyPoint>>();
}
if (cc->Inputs().HasTag("DESCRIPTORS")) {
RET_CHECK(cc->Inputs().HasTag("FEATURES"))
<< "FEATURES and DESCRIPTORS need to be specified together.";
cc->Inputs().Tag("DESCRIPTORS").Set<std::vector<float>>();
}
if (cc->Inputs().HasTag("IMAGE_SIZE")) {
cc->Inputs().Tag("IMAGE_SIZE").Set<std::pair<int, int>>();
}
if (cc->Inputs().HasTag("ADD_INDEX")) {
cc->Inputs().Tag("ADD_INDEX").Set<std::string>();
}
if (cc->Inputs().HasTag("CANCEL_OBJECT_ID")) {
cc->Inputs().Tag("CANCEL_OBJECT_ID").Set<int>();
}
if (cc->Inputs().HasTag("REACQ_SWITCH")) {
cc->Inputs().Tag("REACQ_SWITCH").Set<bool>();
}
if (cc->Outputs().HasTag("BOXES")) {
cc->Outputs().Tag("BOXES").Set<TimedBoxProtoList>();
}
if (cc->Outputs().HasTag("VIZ")) {
RET_CHECK(cc->Inputs().HasTag("VIDEO"))
<< "Output stream VIZ requires VIDEO to be present.";
cc->Outputs().Tag("VIZ").Set<ImageFrame>();
}
if (cc->InputSidePackets().HasTag("INDEX_PROTO_STRING")) {
cc->InputSidePackets().Tag("INDEX_PROTO_STRING").Set<std::string>();
}
if (cc->InputSidePackets().HasTag("OUTPUT_INDEX_FILENAME")) {
cc->InputSidePackets().Tag("OUTPUT_INDEX_FILENAME").Set<std::string>();
}
if (cc->InputSidePackets().HasTag("FRAME_ALIGNMENT")) {
cc->InputSidePackets().Tag("FRAME_ALIGNMENT").Set<int>();
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status BoxDetectorCalculator::Open(CalculatorContext* cc) {
options_ = cc->Options<BoxDetectorCalculatorOptions>();
box_detector_ = BoxDetectorInterface::Create(options_.detector_options());
if (cc->InputSidePackets().HasTag("INDEX_PROTO_STRING")) {
BoxDetectorIndex predefined_index;
if (!predefined_index.ParseFromString(cc->InputSidePackets()
.Tag("INDEX_PROTO_STRING")
.Get<std::string>())) {
LOG(FATAL) << "failed to parse BoxDetectorIndex from INDEX_PROTO_STRING";
}
box_detector_->AddBoxDetectorIndex(predefined_index);
}
for (const auto& filename : options_.index_proto_filename()) {
std::string string_path;
ASSIGN_OR_RETURN(string_path, PathToResourceAsFile(filename));
std::string index_string;
MP_RETURN_IF_ERROR(file::GetContents(string_path, &index_string));
BoxDetectorIndex predefined_index;
if (!predefined_index.ParseFromString(index_string)) {
LOG(FATAL)
<< "failed to parse BoxDetectorIndex from index_proto_filename";
}
box_detector_->AddBoxDetectorIndex(predefined_index);
}
if (cc->InputSidePackets().HasTag("OUTPUT_INDEX_FILENAME")) {
write_index_ = true;
}
if (cc->InputSidePackets().HasTag("FRAME_ALIGNMENT")) {
frame_alignment_ = cc->InputSidePackets().Tag("FRAME_ALIGNMENT").Get<int>();
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status BoxDetectorCalculator::Process(CalculatorContext* cc) {
const Timestamp timestamp = cc->InputTimestamp();
const int64 timestamp_msec = timestamp.Value() / 1000;
InputStream* cancel_object_id_stream =
cc->Inputs().HasTag("CANCEL_OBJECT_ID")
? &(cc->Inputs().Tag("CANCEL_OBJECT_ID"))
: nullptr;
if (cancel_object_id_stream && !cancel_object_id_stream->IsEmpty()) {
const int cancel_object_id = cancel_object_id_stream->Get<int>();
box_detector_->CancelBoxDetection(cancel_object_id);
}
InputStream* add_index_stream = cc->Inputs().HasTag("ADD_INDEX")
? &(cc->Inputs().Tag("ADD_INDEX"))
: nullptr;
if (add_index_stream && !add_index_stream->IsEmpty()) {
BoxDetectorIndex predefined_index;
if (!predefined_index.ParseFromString(
add_index_stream->Get<std::string>())) {
LOG(FATAL) << "failed to parse BoxDetectorIndex from ADD_INDEX";
}
box_detector_->AddBoxDetectorIndex(predefined_index);
}
InputStream* reacq_switch_stream = cc->Inputs().HasTag("REACQ_SWITCH")
? &(cc->Inputs().Tag("REACQ_SWITCH"))
: nullptr;
if (reacq_switch_stream && !reacq_switch_stream->IsEmpty()) {
detector_switch_ = reacq_switch_stream->Get<bool>();
}
if (!detector_switch_) {
return ::mediapipe::OkStatus();
}
InputStream* track_stream = cc->Inputs().HasTag("TRACKING")
? &(cc->Inputs().Tag("TRACKING"))
: nullptr;
InputStream* video_stream =
cc->Inputs().HasTag("VIDEO") ? &(cc->Inputs().Tag("VIDEO")) : nullptr;
InputStream* feature_stream = cc->Inputs().HasTag("FEATURES")
? &(cc->Inputs().Tag("FEATURES"))
: nullptr;
InputStream* descriptor_stream = cc->Inputs().HasTag("DESCRIPTORS")
? &(cc->Inputs().Tag("DESCRIPTORS"))
: nullptr;
CHECK(track_stream != nullptr || video_stream != nullptr ||
(feature_stream != nullptr && descriptor_stream != nullptr))
<< "One and only one of {tracking_data, input image frame, "
"feature/descriptor} need to be valid.";
InputStream* tracked_boxes_stream = cc->Inputs().HasTag("TRACKED_BOXES")
? &(cc->Inputs().Tag("TRACKED_BOXES"))
: nullptr;
std::unique_ptr<TimedBoxProtoList> detected_boxes(new TimedBoxProtoList());
if (track_stream != nullptr) {
// Detect from tracking data
if (track_stream->IsEmpty()) {
return ::mediapipe::OkStatus();
}
const TrackingData& tracking_data = track_stream->Get<TrackingData>();
CHECK(tracked_boxes_stream != nullptr) << "tracked_boxes needed.";
const TimedBoxProtoList tracked_boxes =
tracked_boxes_stream->Get<TimedBoxProtoList>();
box_detector_->DetectAndAddBox(tracking_data, tracked_boxes, timestamp_msec,
detected_boxes.get());
} else if (video_stream != nullptr) {
// Detect from input frame
if (video_stream->IsEmpty()) {
return ::mediapipe::OkStatus();
}
TimedBoxProtoList tracked_boxes;
if (tracked_boxes_stream != nullptr && !tracked_boxes_stream->IsEmpty()) {
tracked_boxes = tracked_boxes_stream->Get<TimedBoxProtoList>();
}
// Just directly pass along the image frame data as-is for detection; we
// don't need to worry about conforming to a specific alignment here.
const cv::Mat input_view =
formats::MatView(&video_stream->Get<ImageFrame>());
box_detector_->DetectAndAddBox(input_view, tracked_boxes, timestamp_msec,
detected_boxes.get());
} else {
if (feature_stream->IsEmpty() || descriptor_stream->IsEmpty()) {
return ::mediapipe::OkStatus();
}
const auto& image_size =
cc->Inputs().Tag("IMAGE_SIZE").Get<std::pair<int, int>>();
float inv_scale = 1.0f / std::max(image_size.first, image_size.second);
TimedBoxProtoList tracked_boxes;
if (tracked_boxes_stream != nullptr && !tracked_boxes_stream->IsEmpty()) {
tracked_boxes = tracked_boxes_stream->Get<TimedBoxProtoList>();
} else if (write_index_) {
auto* box_ptr = tracked_boxes.add_box();
box_ptr->set_id(box_id_);
box_ptr->set_reacquisition(true);
box_ptr->set_aspect_ratio((float)image_size.first /
(float)image_size.second);
box_ptr->mutable_quad()->add_vertices(0);
box_ptr->mutable_quad()->add_vertices(0);
box_ptr->mutable_quad()->add_vertices(0);
box_ptr->mutable_quad()->add_vertices(1);
box_ptr->mutable_quad()->add_vertices(1);
box_ptr->mutable_quad()->add_vertices(1);
box_ptr->mutable_quad()->add_vertices(1);
box_ptr->mutable_quad()->add_vertices(0);
++box_id_;
}
const auto& features = feature_stream->Get<std::vector<cv::KeyPoint>>();
const int feature_size = features.size();
std::vector<Vector2_f> features_vec(feature_size);
const auto& descriptors = descriptor_stream->Get<std::vector<float>>();
const int dims = options_.detector_options().descriptor_dims();
CHECK_GE(descriptors.size(), feature_size * dims);
cv::Mat descriptors_mat(feature_size, dims, CV_32F);
for (int j = 0; j < feature_size; ++j) {
features_vec[j].Set(features[j].pt.x * inv_scale,
features[j].pt.y * inv_scale);
for (int i = 0; i < dims; ++i) {
descriptors_mat.at<float>(j, i) = descriptors[j * dims + i];
}
}
box_detector_->DetectAndAddBoxFromFeatures(
features_vec, descriptors_mat, tracked_boxes, timestamp_msec,
image_size.first * inv_scale, image_size.second * inv_scale,
detected_boxes.get());
}
if (cc->Outputs().HasTag("VIZ")) {
cv::Mat viz_view;
std::unique_ptr<ImageFrame> viz_frame;
if (video_stream != nullptr && !video_stream->IsEmpty()) {
viz_frame = absl::make_unique<ImageFrame>();
viz_frame->CopyFrom(video_stream->Get<ImageFrame>(), frame_alignment_);
viz_view = formats::MatView(viz_frame.get());
}
for (const auto& box : detected_boxes->box()) {
RenderBox(box, &viz_view);
}
cc->Outputs().Tag("VIZ").Add(viz_frame.release(), timestamp);
}
if (cc->Outputs().HasTag("BOXES")) {
cc->Outputs().Tag("BOXES").Add(detected_boxes.release(), timestamp);
}
return ::mediapipe::OkStatus();
}
::mediapipe::Status BoxDetectorCalculator::Close(CalculatorContext* cc) {
if (write_index_) {
BoxDetectorIndex index = box_detector_->ObtainBoxDetectorIndex();
MEDIAPIPE_CHECK_OK(mediapipe::file::SetContents(
cc->InputSidePackets().Tag("OUTPUT_INDEX_FILENAME").Get<std::string>(),
index.SerializeAsString()));
}
return ::mediapipe::OkStatus();
}
} // namespace mediapipe
@@ -0,0 +1,31 @@
// 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.
syntax = "proto2";
package mediapipe;
import "mediapipe/framework/calculator.proto";
import "mediapipe/util/tracking/box_detector.proto";
message BoxDetectorCalculatorOptions {
extend CalculatorOptions {
optional BoxDetectorCalculatorOptions ext = 289746530;
}
optional BoxDetectorOptions detector_options = 1;
// File path to the template index files.
repeated string index_proto_filename = 2;
}
@@ -18,6 +18,7 @@
#include <vector> #include <vector>
#include "absl/container/node_hash_map.h" #include "absl/container/node_hash_map.h"
#include "mediapipe/calculators/video/tracked_detection_manager_calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/formats/detection.pb.h" #include "mediapipe/framework/formats/detection.pb.h"
#include "mediapipe/framework/formats/location_data.pb.h" #include "mediapipe/framework/formats/location_data.pb.h"
@@ -139,6 +140,7 @@ Detection GetAxisAlignedDetectionFromTrackedDetection(
class TrackedDetectionManagerCalculator : public CalculatorBase { class TrackedDetectionManagerCalculator : public CalculatorBase {
public: public:
static ::mediapipe::Status GetContract(CalculatorContract* cc); static ::mediapipe::Status GetContract(CalculatorContract* cc);
::mediapipe::Status Open(CalculatorContext* cc) override;
::mediapipe::Status Process(CalculatorContext* cc) override; ::mediapipe::Status Process(CalculatorContext* cc) override;
@@ -184,6 +186,15 @@ REGISTER_CALCULATOR(TrackedDetectionManagerCalculator);
return ::mediapipe::OkStatus(); return ::mediapipe::OkStatus();
} }
::mediapipe::Status TrackedDetectionManagerCalculator::Open(
CalculatorContext* cc) {
mediapipe::TrackedDetectionManagerCalculatorOptions options =
cc->Options<mediapipe::TrackedDetectionManagerCalculatorOptions>();
tracked_detection_manager_.SetConfig(
options.tracked_detection_manager_options());
return ::mediapipe::OkStatus();
}
::mediapipe::Status TrackedDetectionManagerCalculator::Process( ::mediapipe::Status TrackedDetectionManagerCalculator::Process(
CalculatorContext* cc) { CalculatorContext* cc) {
if (cc->Inputs().HasTag("TRACKING_BOXES")) { if (cc->Inputs().HasTag("TRACKING_BOXES")) {
@@ -0,0 +1,28 @@
// Copyright 2020 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.
syntax = "proto2";
package mediapipe;
import "mediapipe/framework/calculator.proto";
import "mediapipe/util/tracking/tracked_detection_manager_config.proto";
message TrackedDetectionManagerCalculatorOptions {
extend CalculatorOptions {
optional TrackedDetectionManagerCalculatorOptions ext = 301970230;
}
optional TrackedDetectionManagerConfig tracked_detection_manager_options = 1;
}
+1 -1
View File
@@ -51,7 +51,7 @@ To build and run the TensorFlow Lite example on desktop (GPU) with Webcam, run:
```bash ```bash
# Video from webcam running on desktop GPU # Video from webcam running on desktop GPU
# This works only for linux currently # This works only for linux currently
$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS \ $ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 \
mediapipe/examples/desktop/face_detection:face_detection_gpu mediapipe/examples/desktop/face_detection:face_detection_gpu
# It should print: # It should print:
+2 -2
View File
@@ -190,10 +190,10 @@ within the MediaPipe framework:
bazel build --define MEDIAPIPE_DISABLE_GPU=1 <my-target> bazel build --define MEDIAPIPE_DISABLE_GPU=1 <my-target>
# to enable full GPU support (OpenGL ES 3.1+ & Metal) # to enable full GPU support (OpenGL ES 3.1+ & Metal)
bazel build --copt -DMESA_EGL_NO_X11_HEADERS <my-target> bazel build --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 <my-target>
# to enable only OpenGL ES 3.0 and below (no GLES 3.1+ features) # to enable only OpenGL ES 3.0 and below (no GLES 3.1+ features)
bazel build --copt -DMESA_EGL_NO_X11_HEADERS --copt -DMEDIAPIPE_DISABLE_GL_COMPUTE <my-target> bazel build --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 --copt -DMEDIAPIPE_DISABLE_GL_COMPUTE <my-target>
``` ```
Note *MEDIAPIPE_DISABLE_GL_COMPUTE* is automatically defined on all Apple Note *MEDIAPIPE_DISABLE_GL_COMPUTE* is automatically defined on all Apple
+1 -1
View File
@@ -26,7 +26,7 @@ To build and run the TensorFlow Lite example on desktop (GPU) with Webcam, run:
```bash ```bash
# Video from webcam running on desktop GPU # Video from webcam running on desktop GPU
# This works only for linux currently # This works only for linux currently
$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS \ $ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 \
mediapipe/examples/desktop/hair_segmentation:hair_segmentation_gpu mediapipe/examples/desktop/hair_segmentation:hair_segmentation_gpu
# It should print: # It should print:
+1 -1
View File
@@ -48,7 +48,7 @@ To build and run the TensorFlow Lite example on desktop (GPU) with Webcam, run:
```bash ```bash
# Video from webcam running on desktop GPU # Video from webcam running on desktop GPU
# This works only for linux currently # This works only for linux currently
$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS \ $ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 \
mediapipe/examples/desktop/hand_tracking:hand_tracking_gpu mediapipe/examples/desktop/hand_tracking:hand_tracking_gpu
# It should print: # It should print:
+18 -25
View File
@@ -40,12 +40,11 @@ To build and run iOS apps:
$ cd mediapipe $ cd mediapipe
``` ```
2. Install Bazel (version between 1.0.0 and 1.2.1). 2. Install Bazel.
Follow the official Follow the official
[Bazel documentation](https://docs.bazel.build/versions/master/install-ubuntu.html) [Bazel documentation](https://docs.bazel.build/versions/master/install-ubuntu.html)
to install Bazel manually. Note that MediaPipe doesn't support Bazel 2.0.0+ to install Bazel 2.0 or higher.
yet.
3. Install OpenCV and FFmpeg. 3. Install OpenCV and FFmpeg.
@@ -111,7 +110,7 @@ To build and run iOS apps:
# To compile with GPU support, replace # To compile with GPU support, replace
--define MEDIAPIPE_DISABLE_GPU=1 --define MEDIAPIPE_DISABLE_GPU=1
# with # with
--copt -DMESA_EGL_NO_X11_HEADERS --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11
# when building GPU examples. # when building GPU examples.
``` ```
@@ -125,7 +124,7 @@ To build and run iOS apps:
mediapipe/examples/desktop/hello_world:hello_world mediapipe/examples/desktop/hello_world:hello_world
# If you are running on Linux desktop with GPU support enabled (via mesa drivers) # If you are running on Linux desktop with GPU support enabled (via mesa drivers)
$ bazel run --copt -DMESA_EGL_NO_X11_HEADERS \ $ bazel run --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 \
mediapipe/examples/desktop/hello_world:hello_world mediapipe/examples/desktop/hello_world:hello_world
# Should print: # Should print:
@@ -152,12 +151,11 @@ To build and run iOS apps:
$ cd mediapipe $ cd mediapipe
``` ```
2. Install Bazel (version between 1.0.0 and 1.2.1). 2. Install Bazel.
Follow the official Follow the official
[Bazel documentation](https://docs.bazel.build/versions/master/install-redhat.html) [Bazel documentation](https://docs.bazel.build/versions/master/install-redhat.html)
to install Bazel manually. Note that MediaPipe doesn't support Bazel 2.0.0+ to install Bazel 2.0 or higher.
yet.
3. Install OpenCV. 3. Install OpenCV.
@@ -241,23 +239,18 @@ To build and run iOS apps:
$ cd mediapipe $ cd mediapipe
``` ```
3. Install Bazel (version between 1.0.0 and 1.1.0). 3. Install Bazel.
Option 1. Use package manager tool to install Bazel 1.1.0 Option 1. Use package manager tool to install Bazel
```bash ```bash
# If Bazel 1.1.0+ was installed. $ brew install bazel
$ brew uninstall bazel
# Install Bazel 1.1.0
$ brew install https://raw.githubusercontent.com/bazelbuild/homebrew-tap/f8a0fa981bcb1784a0d0823e14867b844e94fb3d/Formula/bazel.rb
$ brew link bazel
# Run 'bazel version' to check version of bazel # Run 'bazel version' to check version of bazel
``` ```
Option 2. Follow the official Option 2. Follow the official
[Bazel documentation](https://docs.bazel.build/versions/master/install-os-x.html#install-with-installer-mac-os-x) [Bazel documentation](https://docs.bazel.build/versions/master/install-os-x.html#install-with-installer-mac-os-x)
to install any version of Bazel manually. Note that MediaPipe doesn't to install Bazel 2.0 or higher.
support Bazel 1.1.0+ on macOS yet.
4. Install OpenCV and FFmpeg. 4. Install OpenCV and FFmpeg.
@@ -391,18 +384,18 @@ cameras. Alternatively, you use a video file as input.
username@DESKTOP-TMVLBJ1:~$ sudo apt-get update && sudo apt-get install -y build-essential git python zip adb openjdk-8-jdk username@DESKTOP-TMVLBJ1:~$ sudo apt-get update && sudo apt-get install -y build-essential git python zip adb openjdk-8-jdk
``` ```
5. Install Bazel (version between 1.0.0 and 1.2.1). 5. Install Bazel.
```bash ```bash
username@DESKTOP-TMVLBJ1:~$ curl -sLO --retry 5 --retry-max-time 10 \ username@DESKTOP-TMVLBJ1:~$ curl -sLO --retry 5 --retry-max-time 10 \
https://storage.googleapis.com/bazel/1.0.0/release/bazel-1.0.0-installer-linux-x86_64.sh && \ https://storage.googleapis.com/bazel/2.0.0/release/bazel-2.0.0-installer-linux-x86_64.sh && \
sudo mkdir -p /usr/local/bazel/1.0.0 && \ sudo mkdir -p /usr/local/bazel/2.0.0 && \
chmod 755 bazel-1.0.0-installer-linux-x86_64.sh && \ chmod 755 bazel-2.0.0-installer-linux-x86_64.sh && \
sudo ./bazel-1.0.0-installer-linux-x86_64.sh --prefix=/usr/local/bazel/1.0.0 && \ sudo ./bazel-2.0.0-installer-linux-x86_64.sh --prefix=/usr/local/bazel/2.0.0 && \
source /usr/local/bazel/1.0.0/lib/bazel/bin/bazel-complete.bash source /usr/local/bazel/2.0.0/lib/bazel/bin/bazel-complete.bash
username@DESKTOP-TMVLBJ1:~$ /usr/local/bazel/1.0.0/lib/bazel/bin/bazel version && \ username@DESKTOP-TMVLBJ1:~$ /usr/local/bazel/2.0.0/lib/bazel/bin/bazel version && \
alias bazel='/usr/local/bazel/1.0.0/lib/bazel/bin/bazel' alias bazel='/usr/local/bazel/2.0.0/lib/bazel/bin/bazel'
``` ```
6. Checkout MediaPipe repository. 6. Checkout MediaPipe repository.
+3 -3
View File
@@ -89,11 +89,11 @@ process new data sets, in the documentation of
dataset = d.as_dataset('test') dataset = d.as_dataset('test')
# implement additional processing and batching here # implement additional processing and batching here
dataset_output = dataset.make_one_shot_iterator().get_next() dataset_output = dataset.make_one_shot_iterator().get_next()
images = dataset_output=['images'] images = dataset_output['images']
labels = dataset_output=['labels'] labels = dataset_output['labels']
with tf.Session() as sess: with tf.Session() as sess:
images_, labels_ = sess.run(images, labels) images_, labels_ = sess.run([images, labels])
print('The shape of images_ is %s' % str(images_.shape)) print('The shape of images_ is %s' % str(images_.shape))
print('The shape of labels_ is %s' % str(labels_.shape)) print('The shape of labels_ is %s' % str(labels_.shape))
``` ```
+4
View File
@@ -76,6 +76,10 @@ in the next section.
2. Open `mediapipe/Mediapipe.tulsiproj` using the Tulsi app. 2. Open `mediapipe/Mediapipe.tulsiproj` using the Tulsi app.
Important: If Tulsi displays an error saying "Bazel could not be found",
press the "Bazel..." button in the Packages tab and select the `bazel`
executable in your homebrew `/bin/` directory.
3. Select the MediaPipe config in the Configs tab, then press the Generate 3. Select the MediaPipe config in the Configs tab, then press the Generate
button below. You will be asked for a location to save the Xcode project. button below. You will be asked for a location to save the Xcode project.
Once the project is generated, it will be opened in Xcode. Once the project is generated, it will be opened in Xcode.
@@ -48,7 +48,7 @@ To build and run the TensorFlow Lite example on desktop (GPU) with Webcam, run:
```bash ```bash
# Video from webcam running on desktop GPU # Video from webcam running on desktop GPU
# This works only for linux currently # This works only for linux currently
$ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS \ $ bazel build -c opt --copt -DMESA_EGL_NO_X11_HEADERS --copt -DEGL_NO_X11 \
mediapipe/examples/desktop/multi_hand_tracking:multi_hand_tracking_gpu mediapipe/examples/desktop/multi_hand_tracking:multi_hand_tracking_gpu
# It should print: # It should print:
@@ -0,0 +1,7 @@
tricorder: {
options: {
builder: {
config: "android_arm64"
}
}
}
@@ -60,8 +60,8 @@ android_library(
"//third_party:androidx_appcompat", "//third_party:androidx_appcompat",
"//third_party:androidx_constraint_layout", "//third_party:androidx_constraint_layout",
"//third_party:opencv", "//third_party:opencv",
"@androidx_concurrent_futures//jar", "@maven//:androidx_concurrent_concurrent_futures",
"@com_google_guava_android//jar", "@maven//:com_google_guava_guava",
], ],
) )
@@ -64,10 +64,10 @@ android_library(
"//third_party:androidx_legacy_support_v4", "//third_party:androidx_legacy_support_v4",
"//third_party:androidx_recyclerview", "//third_party:androidx_recyclerview",
"//third_party:opencv", "//third_party:opencv",
"@androidx_concurrent_futures//jar", "@maven//:androidx_concurrent_concurrent_futures",
"@androidx_lifecycle//jar", "@maven//:androidx_lifecycle_lifecycle_common",
"@com_google_code_findbugs//jar", "@maven//:com_google_code_findbugs_jsr305",
"@com_google_guava_android//jar", "@maven//:com_google_guava_guava",
], ],
) )
@@ -64,10 +64,10 @@ android_library(
"//third_party:androidx_legacy_support_v4", "//third_party:androidx_legacy_support_v4",
"//third_party:androidx_recyclerview", "//third_party:androidx_recyclerview",
"//third_party:opencv", "//third_party:opencv",
"@androidx_concurrent_futures//jar", "@maven//:androidx_concurrent_concurrent_futures",
"@androidx_lifecycle//jar", "@maven//:androidx_lifecycle_lifecycle_common",
"@com_google_code_findbugs//jar", "@maven//:com_google_code_findbugs_jsr305",
"@com_google_guava_android//jar", "@maven//:com_google_guava_guava",
], ],
) )
@@ -63,10 +63,10 @@ android_library(
"//third_party:androidx_legacy_support_v4", "//third_party:androidx_legacy_support_v4",
"//third_party:androidx_recyclerview", "//third_party:androidx_recyclerview",
"//third_party:opencv", "//third_party:opencv",
"@androidx_concurrent_futures//jar", "@maven//:androidx_concurrent_concurrent_futures",
"@androidx_lifecycle//jar", "@maven//:androidx_lifecycle_lifecycle_common",
"@com_google_code_findbugs//jar", "@maven//:com_google_code_findbugs_jsr305",
"@com_google_guava_android//jar", "@maven//:com_google_guava_guava",
], ],
) )
@@ -64,10 +64,10 @@ android_library(
"//third_party:androidx_legacy_support_v4", "//third_party:androidx_legacy_support_v4",
"//third_party:androidx_recyclerview", "//third_party:androidx_recyclerview",
"//third_party:opencv", "//third_party:opencv",
"@androidx_concurrent_futures//jar", "@maven//:androidx_concurrent_concurrent_futures",
"@androidx_lifecycle//jar", "@maven//:androidx_lifecycle_lifecycle_common",
"@com_google_code_findbugs//jar", "@maven//:com_google_code_findbugs_jsr305",
"@com_google_guava_android//jar", "@maven//:com_google_guava_guava",
], ],
) )
@@ -85,10 +85,10 @@ android_library(
"//third_party:androidx_legacy_support_v4", "//third_party:androidx_legacy_support_v4",
"//third_party:androidx_recyclerview", "//third_party:androidx_recyclerview",
"//third_party:opencv", "//third_party:opencv",
"@androidx_concurrent_futures//jar", "@maven//:androidx_concurrent_concurrent_futures",
"@androidx_lifecycle//jar", "@maven//:androidx_lifecycle_lifecycle_common",
"@com_google_code_findbugs//jar", "@maven//:com_google_code_findbugs_jsr305",
"@com_google_guava_android//jar", "@maven//:com_google_guava_guava",
], ],
) )
@@ -85,10 +85,10 @@ android_library(
"//third_party:androidx_legacy_support_v4", "//third_party:androidx_legacy_support_v4",
"//third_party:androidx_recyclerview", "//third_party:androidx_recyclerview",
"//third_party:opencv", "//third_party:opencv",
"@androidx_concurrent_futures//jar", "@maven//:androidx_concurrent_concurrent_futures",
"@androidx_lifecycle//jar", "@maven//:androidx_lifecycle_lifecycle_common",
"@com_google_code_findbugs//jar", "@maven//:com_google_code_findbugs_jsr305",
"@com_google_guava_android//jar", "@maven//:com_google_guava_guava",
], ],
) )
@@ -97,10 +97,10 @@ android_library(
"//third_party:androidx_legacy_support_v4", "//third_party:androidx_legacy_support_v4",
"//third_party:androidx_recyclerview", "//third_party:androidx_recyclerview",
"//third_party:opencv", "//third_party:opencv",
"@androidx_concurrent_futures//jar", "@maven//:androidx_concurrent_concurrent_futures",
"@androidx_lifecycle//jar", "@maven//:androidx_lifecycle_lifecycle_common",
"@com_google_code_findbugs//jar", "@maven//:com_google_code_findbugs_jsr305",
"@com_google_guava_android//jar", "@maven//:com_google_guava_guava",
], ],
) )
@@ -64,10 +64,10 @@ android_library(
"//third_party:androidx_legacy_support_v4", "//third_party:androidx_legacy_support_v4",
"//third_party:androidx_recyclerview", "//third_party:androidx_recyclerview",
"//third_party:opencv", "//third_party:opencv",
"@androidx_concurrent_futures//jar", "@maven//:androidx_concurrent_concurrent_futures",
"@androidx_lifecycle//jar", "@maven//:androidx_lifecycle_lifecycle_common",
"@com_google_code_findbugs//jar", "@maven//:com_google_code_findbugs_jsr305",
"@com_google_guava_android//jar", "@maven//:com_google_guava_guava",
], ],
) )
@@ -65,10 +65,10 @@ android_library(
"//third_party:androidx_legacy_support_v4", "//third_party:androidx_legacy_support_v4",
"//third_party:androidx_recyclerview", "//third_party:androidx_recyclerview",
"//third_party:opencv", "//third_party:opencv",
"@androidx_concurrent_futures//jar", "@maven//:androidx_concurrent_concurrent_futures",
"@androidx_lifecycle//jar", "@maven//:androidx_lifecycle_lifecycle_common",
"@com_google_code_findbugs//jar", "@maven//:com_google_code_findbugs_jsr305",
"@com_google_guava_android//jar", "@maven//:com_google_guava_guava",
], ],
) )
@@ -64,9 +64,9 @@ android_library(
"//third_party:androidx_legacy_support_v4", "//third_party:androidx_legacy_support_v4",
"//third_party:androidx_recyclerview", "//third_party:androidx_recyclerview",
"//third_party:opencv", "//third_party:opencv",
"@androidx_concurrent_futures//jar", "@maven//:androidx_concurrent_concurrent_futures",
"@androidx_lifecycle//jar", "@maven//:androidx_lifecycle_lifecycle_common",
"@com_google_guava_android//jar", "@maven//:com_google_guava_guava",
], ],
) )
+3 -5
View File
@@ -35,10 +35,8 @@ RUN dpkg --add-architecture arm64
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
build-essential \ build-essential \
crossbuild-essential-arm64 \ crossbuild-essential-arm64 \
libusb-1.0-0-dev \
libusb-1.0-0-dev:arm64 \ libusb-1.0-0-dev:arm64 \
zlib1g-dev \ zlibc:arm64 \
zlib1g-dev:arm64 \
pkg-config \ pkg-config \
zip \ zip \
unzip \ unzip \
@@ -62,8 +60,8 @@ RUN pip3 install six
COPY . /mediapipe/ COPY . /mediapipe/
# Install bazel # Install bazel
# Please match the current MediaPipe Bazel requirements according to docs.
ARG BAZEL_VERSION=1.1.0 ARG BAZEL_VERSION=2.0.0
RUN mkdir /bazel && \ RUN mkdir /bazel && \
wget --no-check-certificate -O /bazel/installer.sh "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh" && \ wget --no-check-certificate -O /bazel/installer.sh "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh" && \
wget --no-check-certificate -O /bazel/LICENSE.txt "https://raw.githubusercontent.com/bazelbuild/bazel/master/LICENSE" && \ wget --no-check-certificate -O /bazel/LICENSE.txt "https://raw.githubusercontent.com/bazelbuild/bazel/master/LICENSE" && \
-356
View File
@@ -1,356 +0,0 @@
workspace(name = "mediapipe")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
skylib_version = "0.8.0"
http_archive(
name = "bazel_skylib",
type = "tar.gz",
url = "https://github.com/bazelbuild/bazel-skylib/releases/download/{}/bazel-skylib.{}.tar.gz".format (skylib_version, skylib_version),
sha256 = "2ef429f5d7ce7111263289644d233707dba35e39696377ebab8b0bc701f7818e",
)
load("@bazel_skylib//lib:versions.bzl", "versions")
versions.check(minimum_bazel_version = "1.0.0",
maximum_bazel_version = "1.2.1")
# ABSL cpp library lts_2020_02_25
http_archive(
name = "com_google_absl",
urls = [
"https://github.com/abseil/abseil-cpp/archive/20200225.tar.gz",
],
# Remove after https://github.com/abseil/abseil-cpp/issues/326 is solved.
patches = [
"@//third_party:com_google_absl_f863b622fe13612433fdf43f76547d5edda0c93001.diff"
],
patch_args = [
"-p1",
],
strip_prefix = "abseil-cpp-20200225",
sha256 = "728a813291bdec2aa46eab8356ace9f75ac2ed9dfe2df5ab603c4e6c09f1c353"
)
http_archive(
name = "rules_cc",
strip_prefix = "rules_cc-master",
urls = ["https://github.com/bazelbuild/rules_cc/archive/master.zip"],
)
# GoogleTest/GoogleMock framework. Used by most unit-tests.
http_archive(
name = "com_google_googletest",
urls = ["https://github.com/google/googletest/archive/master.zip"],
strip_prefix = "googletest-master",
)
# Google Benchmark library.
http_archive(
name = "com_google_benchmark",
urls = ["https://github.com/google/benchmark/archive/master.zip"],
strip_prefix = "benchmark-master",
build_file = "@//third_party:benchmark.BUILD",
)
# gflags needed by glog
http_archive(
name = "com_github_gflags_gflags",
sha256 = "6e16c8bc91b1310a44f3965e616383dbda48f83e8c1eaa2370a215057b00cabe",
strip_prefix = "gflags-77592648e3f3be87d6c7123eb81cbad75f9aef5a",
urls = [
"https://mirror.bazel.build/github.com/gflags/gflags/archive/77592648e3f3be87d6c7123eb81cbad75f9aef5a.tar.gz",
"https://github.com/gflags/gflags/archive/77592648e3f3be87d6c7123eb81cbad75f9aef5a.tar.gz",
],
)
# glog
http_archive(
name = "com_github_glog_glog",
url = "https://github.com/google/glog/archive/v0.3.5.zip",
sha256 = "267103f8a1e9578978aa1dc256001e6529ef593e5aea38193d31c2872ee025e8",
strip_prefix = "glog-0.3.5",
build_file = "@//third_party:glog.BUILD",
patches = [
"@//third_party:com_github_glog_glog_9779e5ea6ef59562b030248947f787d1256132ae.diff"
],
patch_args = [
"-p1",
],
)
# easyexif
http_archive(
name = "easyexif",
url = "https://github.com/mayanklahiri/easyexif/archive/master.zip",
strip_prefix = "easyexif-master",
build_file = "@//third_party:easyexif.BUILD",
)
# libyuv
http_archive(
name = "libyuv",
urls = ["https://chromium.googlesource.com/libyuv/libyuv/+archive/refs/heads/master.tar.gz"],
build_file = "@//third_party:libyuv.BUILD",
)
http_archive(
name = "com_google_protobuf_javalite",
sha256 = "79d102c61e2a479a0b7e5fc167bcfaa4832a0c6aad4a75fa7da0480564931bcc",
strip_prefix = "protobuf-384989534b2246d413dbcd750744faab2607b516",
urls = ["https://github.com/google/protobuf/archive/384989534b2246d413dbcd750744faab2607b516.zip"],
)
http_archive(
name = "com_google_audio_tools",
strip_prefix = "multichannel-audio-tools-master",
urls = ["https://github.com/google/multichannel-audio-tools/archive/master.zip"],
)
# Needed by TensorFlow
http_archive(
name = "io_bazel_rules_closure",
sha256 = "e0a111000aeed2051f29fcc7a3f83be3ad8c6c93c186e64beb1ad313f0c7f9f9",
strip_prefix = "rules_closure-cf1e44edb908e9616030cc83d085989b8e6cd6df",
urls = [
"http://mirror.tensorflow.org/github.com/bazelbuild/rules_closure/archive/cf1e44edb908e9616030cc83d085989b8e6cd6df.tar.gz",
"https://github.com/bazelbuild/rules_closure/archive/cf1e44edb908e9616030cc83d085989b8e6cd6df.tar.gz", # 2019-04-04
],
)
# 2020-02-12
# The last commit before TensorFlow switched to Bazel 2.0
_TENSORFLOW_GIT_COMMIT = "77e9ffb9b2bfb1a4f7056e62d84039626923e328"
_TENSORFLOW_SHA256= "176ccd82f7dd17c5e117b50d353603b129c7a6ccbfebd522ca47cc2a40f33f13"
http_archive(
name = "org_tensorflow",
urls = [
"https://mirror.bazel.build/github.com/tensorflow/tensorflow/archive/%s.tar.gz" % _TENSORFLOW_GIT_COMMIT,
"https://github.com/tensorflow/tensorflow/archive/%s.tar.gz" % _TENSORFLOW_GIT_COMMIT,
],
# A compatibility patch
patches = [
"@//third_party:org_tensorflow_528e22eae8bf3206189a066032c66e9e5c9b4a61.diff"
],
patch_args = [
"-p1",
],
strip_prefix = "tensorflow-%s" % _TENSORFLOW_GIT_COMMIT,
sha256 = _TENSORFLOW_SHA256,
)
load("@org_tensorflow//tensorflow:workspace.bzl", "tf_workspace")
tf_workspace(tf_repo_name = "org_tensorflow")
http_archive(
name = "ceres_solver",
url = "https://github.com/ceres-solver/ceres-solver/archive/1.14.0.zip",
patches = [
"@//third_party:ceres_solver_9bf9588988236279e1262f75d7f4d85711dfa172.diff"
],
patch_args = [
"-p1",
],
strip_prefix = "ceres-solver-1.14.0",
sha256 = "5ba6d0db4e784621fda44a50c58bb23b0892684692f0c623e2063f9c19f192f1"
)
# Please run
# $ sudo apt-get install libopencv-core-dev libopencv-highgui-dev \
# libopencv-calib3d-dev libopencv-features2d-dev \
# libopencv-imgproc-dev libopencv-video-dev
new_local_repository(
name = "linux_opencv",
build_file = "@//third_party:opencv_linux.BUILD",
path = "/usr",
)
new_local_repository(
name = "linux_ffmpeg",
build_file = "@//third_party:ffmpeg_linux.BUILD",
path = "/usr"
)
# Please run $ brew install opencv@3
new_local_repository(
name = "macos_opencv",
build_file = "@//third_party:opencv_macos.BUILD",
path = "/usr",
)
new_local_repository(
name = "macos_ffmpeg",
build_file = "@//third_party:ffmpeg_macos.BUILD",
path = "/usr",
)
http_archive(
name = "android_opencv",
build_file = "@//third_party:opencv_android.BUILD",
strip_prefix = "OpenCV-android-sdk",
type = "zip",
url = "https://github.com/opencv/opencv/releases/download/3.4.3/opencv-3.4.3-android-sdk.zip",
)
# After OpenCV 3.2.0, the pre-compiled opencv2.framework has google protobuf symbols, which will
# trigger duplicate symbol errors in the linking stage of building a mediapipe ios app.
# To get a higher version of OpenCV for iOS, opencv2.framework needs to be built from source with
# '-DBUILD_PROTOBUF=OFF -DBUILD_opencv_dnn=OFF'.
http_archive(
name = "ios_opencv",
sha256 = "7dd536d06f59e6e1156b546bd581523d8df92ce83440002885ec5abc06558de2",
build_file = "@//third_party:opencv_ios.BUILD",
type = "zip",
url = "https://github.com/opencv/opencv/releases/download/3.2.0/opencv-3.2.0-ios-framework.zip",
)
RULES_JVM_EXTERNAL_TAG = "2.2"
RULES_JVM_EXTERNAL_SHA = "f1203ce04e232ab6fdd81897cf0ff76f2c04c0741424d192f28e65ae752ce2d6"
http_archive(
name = "rules_jvm_external",
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
sha256 = RULES_JVM_EXTERNAL_SHA,
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)
load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_install(
artifacts = [
"androidx.annotation:annotation:aar:1.1.0",
"androidx.appcompat:appcompat:aar:1.1.0-rc01",
"androidx.camera:camera-core:aar:1.0.0-alpha06",
"androidx.camera:camera-camera2:aar:1.0.0-alpha06",
"androidx.constraintlayout:constraintlayout:aar:1.1.3",
"androidx.core:core:aar:1.1.0-rc03",
"androidx.legacy:legacy-support-v4:aar:1.0.0",
"androidx.recyclerview:recyclerview:aar:1.1.0-beta02",
"com.google.android.material:material:aar:1.0.0-rc01",
],
repositories = [
"https://dl.google.com/dl/android/maven2",
"https://repo1.maven.org/maven2",
],
)
maven_server(
name = "google_server",
url = "https://dl.google.com/dl/android/maven2",
)
maven_jar(
name = "androidx_lifecycle",
artifact = "androidx.lifecycle:lifecycle-common:2.0.0",
sha1 = "e070ffae07452331bc5684734fce6831d531785c",
server = "google_server",
)
maven_jar(
name = "androidx_concurrent_futures",
artifact = "androidx.concurrent:concurrent-futures:1.0.0-alpha03",
sha1 = "b528df95c7e2fefa2210c0c742bf3e491c1818ae",
server = "google_server",
)
maven_jar(
name = "com_google_guava_android",
artifact = "com.google.guava:guava:27.0.1-android",
sha1 = "b7e1c37f66ef193796ccd7ea6e80c2b05426182d",
)
maven_jar(
name = "com_google_common_flogger",
artifact = "com.google.flogger:flogger:0.3.1",
sha1 = "585030fe1ec709760cbef997a459729fb965df0e",
)
maven_jar(
name = "com_google_common_flogger_system_backend",
artifact = "com.google.flogger:flogger-system-backend:0.3.1",
sha1 = "287b569d76abcd82f9de87fe41829fbc7ebd8ac9",
)
maven_jar(
name = "com_google_code_findbugs",
artifact = "com.google.code.findbugs:jsr305:3.0.2",
sha1 = "25ea2e8b0c338a877313bd4672d3fe056ea78f0d",
)
# You may run setup_android.sh to install Android SDK and NDK.
android_ndk_repository(
name = "androidndk",
)
android_sdk_repository(
name = "androidsdk",
)
# iOS basic build deps.
http_archive(
name = "build_bazel_rules_apple",
sha256 = "bdc8e66e70b8a75da23b79f1f8c6207356df07d041d96d2189add7ee0780cf4e",
strip_prefix = "rules_apple-b869b0d3868d78a1d4ffd866ccb304fb68aa12c3",
url = "https://github.com/bazelbuild/rules_apple/archive/b869b0d3868d78a1d4ffd866ccb304fb68aa12c3.tar.gz",
)
load(
"@build_bazel_rules_apple//apple:repositories.bzl",
"apple_rules_dependencies",
)
apple_rules_dependencies()
load(
"@build_bazel_rules_swift//swift:repositories.bzl",
"swift_rules_dependencies",
)
swift_rules_dependencies()
load(
"@build_bazel_apple_support//lib:repositories.bzl",
"apple_support_dependencies",
)
apple_support_dependencies()
# More iOS deps.
http_archive(
name = "google_toolbox_for_mac",
url = "https://github.com/google/google-toolbox-for-mac/archive/v2.2.1.zip",
sha256 = "e3ac053813c989a88703556df4dc4466e424e30d32108433ed6beaec76ba4fdc",
strip_prefix = "google-toolbox-for-mac-2.2.1",
build_file = "@//third_party:google_toolbox_for_mac.BUILD",
)
### Coral ###
#COMMIT=$(git ls-remote https://github.com/google-coral/crosstool master | awk '{print $1}')
#SHA256=$(curl -L "https://github.com/google-coral/crosstool/archive/${COMMIT}.tar.gz" | sha256sum | awk '{print $1}')
# Oct 2019
#COMMIT=9e00d5be43bf001f883b5700f5d04882fea00229
#SHA256=cb31b1417ccdcf7dd9fca5ec63e1571672372c30427730255997a547569d2feb
http_archive(
name = "coral_crosstool",
sha256 = "cb31b1417ccdcf7dd9fca5ec63e1571672372c30427730255997a547569d2feb",
strip_prefix = "crosstool-9e00d5be43bf001f883b5700f5d04882fea00229",
urls = [
"https://github.com/google-coral/crosstool/archive/9e00d5be43bf001f883b5700f5d04882fea00229.tar.gz",
],
)
load("@coral_crosstool//:configure.bzl", "cc_crosstool")
cc_crosstool(name = "crosstool")
# EdgeTPU
new_local_repository(
name = "edgetpu",
path = "/edgetpu/libedgetpu",
build_file = "/edgetpu/libedgetpu/BUILD"
)
new_local_repository(
name = "libedgetpu",
path = "/usr/lib/aarch64-linux-gnu",
build_file = "/edgetpu/libedgetpu/BUILD"
)
+30
View File
@@ -0,0 +1,30 @@
### Coral additions to MediaPipe WORKSPACE ###
#COMMIT=$(git ls-remote https://github.com/google-coral/crosstool master | awk '{print $1}')
#SHA256=$(curl -L "https://github.com/google-coral/crosstool/archive/${COMMIT}.tar.gz" | sha256sum | awk '{print $1}')
# Oct 2019
#COMMIT=9e00d5be43bf001f883b5700f5d04882fea00229
#SHA256=cb31b1417ccdcf7dd9fca5ec63e1571672372c30427730255997a547569d2feb
http_archive(
name = "coral_crosstool",
sha256 = "cb31b1417ccdcf7dd9fca5ec63e1571672372c30427730255997a547569d2feb",
strip_prefix = "crosstool-9e00d5be43bf001f883b5700f5d04882fea00229",
urls = [
"https://github.com/google-coral/crosstool/archive/9e00d5be43bf001f883b5700f5d04882fea00229.tar.gz",
],
)
load("@coral_crosstool//:configure.bzl", "cc_crosstool")
cc_crosstool(name = "crosstool")
# EdgeTPU
new_local_repository(
name = "edgetpu",
path = "/edgetpu/libedgetpu",
build_file = "/edgetpu/libedgetpu/BUILD"
)
new_local_repository(
name = "libedgetpu",
path = "/usr/lib/aarch64-linux-gnu",
build_file = "/edgetpu/libedgetpu/BUILD"
)
+14 -1
View File
@@ -10,12 +10,25 @@ sleep 3
mkdir -p opencv32_arm64_libs mkdir -p opencv32_arm64_libs
# prepare docker aux script
cp mediapipe/examples/coral/update_sources.sh update_sources.sh cp mediapipe/examples/coral/update_sources.sh update_sources.sh
chmod +x update_sources.sh chmod +x update_sources.sh
# backup non-coral Dockerfile
mv Dockerfile Dockerfile.orig mv Dockerfile Dockerfile.orig
cp mediapipe/examples/coral/Dockerfile Dockerfile cp mediapipe/examples/coral/Dockerfile Dockerfile
# backup non-coral workspace
cp WORKSPACE WORKSPACE.orig cp WORKSPACE WORKSPACE.orig
cp mediapipe/examples/coral/WORKSPACE WORKSPACE
# create temps
cp WORKSPACE WORKSPACE.1
cp mediapipe/examples/coral/WORKSPACE.coral WORKSPACE.2
# merge (shell decides concat order, unless numbered appropriately)
cat WORKSPACE.1 WORKSPACE.2 > WORKSPACE
# cleanup
rm WORKSPACE.1 WORKSPACE.2
echo 'done'
@@ -85,7 +85,7 @@ REGISTER_CALCULATOR(ShotBoundaryCalculator);
void ShotBoundaryCalculator::ComputeHistogram(const cv::Mat& image, void ShotBoundaryCalculator::ComputeHistogram(const cv::Mat& image,
cv::Mat* image_histogram) { cv::Mat* image_histogram) {
cv::Mat equalized_image; cv::Mat equalized_image;
cv::cvtColor(image.clone(), equalized_image, CV_RGB2GRAY); cv::cvtColor(image.clone(), equalized_image, cv::COLOR_RGB2GRAY);
double min, max; double min, max;
cv::minMaxLoc(equalized_image, &min, &max); cv::minMaxLoc(equalized_image, &min, &max);
@@ -95,7 +95,7 @@ void PolynomialRegressionPathSolver::AddCostFunctionToProblem(
const std::vector<FocusPointFrame>& focus_point_frames, const std::vector<FocusPointFrame>& focus_point_frames,
const std::vector<FocusPointFrame>& prior_focus_point_frames, const std::vector<FocusPointFrame>& prior_focus_point_frames,
const int original_width, const int original_height, const int output_width, const int original_width, const int original_height, const int output_width,
const int output_height, std::vector<cv::Mat>* all_xforms) { const int output_height, std::vector<cv::Mat>* all_transforms) {
RET_CHECK_GE(original_width, output_width); RET_CHECK_GE(original_width, output_width);
RET_CHECK_GE(original_height, output_height); RET_CHECK_GE(original_height, output_height);
const bool should_solve_x_problem = original_width != output_width; const bool should_solve_x_problem = original_width != output_width;
@@ -138,9 +138,10 @@ void PolynomialRegressionPathSolver::AddCostFunctionToProblem(
Solver::Options options; Solver::Options options;
options.linear_solver_type = ceres::DENSE_QR; options.linear_solver_type = ceres::DENSE_QR;
Solver::Summary summary; Solver::Summary summary_x, summary_y;
Solve(options, &problem_x, &summary); Solve(options, &problem_x, &summary_x);
all_xforms->clear(); Solve(options, &problem_y, &summary_y);
all_transforms->clear();
for (int i = 0; for (int i = 0;
i < focus_point_frames.size() + prior_focus_point_frames.size(); i++) { i < focus_point_frames.size() + prior_focus_point_frames.size(); i++) {
// Code below assigns values into an affine model, defined as: // Code below assigns values into an affine model, defined as:
@@ -160,7 +161,7 @@ void PolynomialRegressionPathSolver::AddCostFunctionToProblem(
yb_, yc_, yd_, yk_); yb_, yc_, yd_, yk_);
transform.at<float>(1, 2) = delta; transform.at<float>(1, 2) = delta;
} }
all_xforms->push_back(transform); all_transforms->push_back(transform);
} }
return mediapipe::OkStatus(); return mediapipe::OkStatus();
} }
@@ -40,14 +40,14 @@ class PolynomialRegressionPathSolver {
// Given a series of focus points on frames, uses polynomial regression to // Given a series of focus points on frames, uses polynomial regression to
// compute a best guess of a 1D camera movement trajectory along x-axis and // compute a best guess of a 1D camera movement trajectory along x-axis and
// y-axis, such that focus points can be preserved as much as possible. The // y-axis, such that focus points can be preserved as much as possible. The
// returned |all_xforms| hold the camera location at each timestamp // returned |all_transforms| hold the camera location at each timestamp
// corresponding to each input frame. // corresponding to each input frame.
::mediapipe::Status ComputeCameraPath( ::mediapipe::Status ComputeCameraPath(
const std::vector<FocusPointFrame>& focus_point_frames, const std::vector<FocusPointFrame>& focus_point_frames,
const std::vector<FocusPointFrame>& prior_focus_point_frames, const std::vector<FocusPointFrame>& prior_focus_point_frames,
const int original_width, const int original_height, const int original_width, const int original_height,
const int output_width, const int output_height, const int output_width, const int output_height,
std::vector<cv::Mat>* all_xforms); std::vector<cv::Mat>* all_transforms);
private: private:
// Adds a new cost function, constructed using |in| and |out|, into |problem|. // Adds a new cost function, constructed using |in| and |out|, into |problem|.
@@ -24,3 +24,17 @@ cc_binary(
"//mediapipe/graphs/hair_segmentation:mobile_calculators", "//mediapipe/graphs/hair_segmentation:mobile_calculators",
], ],
) )
cc_binary(
name = "hair_segmentation_cpu",
deps = [
"//mediapipe/examples/desktop:demo_run_graph_main",
] + select({
"//mediapipe/gpu:disable_gpu": [
"//mediapipe/graphs/hair_segmentation:desktop_calculators",
],
"//conditions:default": [
"//mediapipe/graphs/hair_segmentation:mobile_calculators",
],
}),
)
+5
View File
@@ -361,6 +361,7 @@ cc_library(
"//mediapipe/framework:mediapipe_options_cc_proto", "//mediapipe/framework:mediapipe_options_cc_proto",
"//mediapipe/framework:packet_generator_cc_proto", "//mediapipe/framework:packet_generator_cc_proto",
"//mediapipe/framework:status_handler_cc_proto", "//mediapipe/framework:status_handler_cc_proto",
"//mediapipe/framework:stream_handler_cc_proto",
"//mediapipe/framework/port:any_proto", "//mediapipe/framework/port:any_proto",
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
"//mediapipe/framework/tool:options_util", "//mediapipe/framework/tool:options_util",
@@ -1495,13 +1496,17 @@ cc_test(
"//mediapipe/framework/port:status", "//mediapipe/framework/port:status",
"//mediapipe/framework/stream_handler:barrier_input_stream_handler", "//mediapipe/framework/stream_handler:barrier_input_stream_handler",
"//mediapipe/framework/stream_handler:early_close_input_stream_handler", "//mediapipe/framework/stream_handler:early_close_input_stream_handler",
"//mediapipe/framework/stream_handler:fixed_size_input_stream_handler",
"//mediapipe/framework/stream_handler:immediate_input_stream_handler", "//mediapipe/framework/stream_handler:immediate_input_stream_handler",
"//mediapipe/framework/stream_handler:mux_input_stream_handler", "//mediapipe/framework/stream_handler:mux_input_stream_handler",
"//mediapipe/framework/stream_handler:sync_set_input_stream_handler",
"//mediapipe/framework/stream_handler:timestamp_align_input_stream_handler",
"//mediapipe/framework/tool:sink", "//mediapipe/framework/tool:sink",
"//mediapipe/framework/tool:status_util", "//mediapipe/framework/tool:status_util",
"@com_google_absl//absl/container:fixed_array", "@com_google_absl//absl/container:fixed_array",
"@com_google_absl//absl/memory", "@com_google_absl//absl/memory",
"@com_google_absl//absl/strings", "@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/time", "@com_google_absl//absl/time",
], ],
) )
+26 -1
View File
@@ -84,7 +84,7 @@ class CalculatorContract {
return *output_side_packets_; return *output_side_packets_;
} }
// Set this Node's default InputStreamHandler. // Specifies the preferred InputStreamHandler for this Node.
// If there is an InputStreamHandler specified in the graph (.pbtxt) for this // If there is an InputStreamHandler specified in the graph (.pbtxt) for this
// Node, then the graph's InputStreamHandler will take priority. // Node, then the graph's InputStreamHandler will take priority.
void SetInputStreamHandler(const std::string& name) { void SetInputStreamHandler(const std::string& name) {
@@ -104,6 +104,29 @@ class CalculatorContract {
return input_stream_handler_options_; return input_stream_handler_options_;
} }
// The next few methods are concerned with timestamp bound propagation
// (see scheduling_sync.md#input-policies). Every calculator that processes
// live inputs should specify either ProcessTimestampBounds or
// TimestampOffset. Calculators that produce output at the same timestamp as
// the input, or with a fixed offset, should declare this fact using
// SetTimestampOffset. Calculators that require custom timestamp bound
// calculations should use SetProcessTimestampBounds.
// When true, Process is called for every new timestamp bound, with or without
// new packets. A call to Process with only an input timestamp bound is
// normally used to compute a new output timestamp bound.
void SetProcessTimestampBounds(bool process_timestamps) {
process_timestamps_ = process_timestamps;
}
bool GetProcessTimestampBounds() const { return process_timestamps_; }
// Specifies the maximum difference between input and output timestamps.
// When specified, the mediapipe framework automatically computes output
// timestamp bounds based on input timestamps. The special value
// TimestampDiff::Unset disables the timestamp offset.
void SetTimestampOffset(TimestampDiff offset) { timestamp_offset_ = offset; }
TimestampDiff GetTimestampOffset() const { return timestamp_offset_; }
class GraphServiceRequest { class GraphServiceRequest {
public: public:
// APIs that should be used by calculators. // APIs that should be used by calculators.
@@ -147,6 +170,8 @@ class CalculatorContract {
MediaPipeOptions input_stream_handler_options_; MediaPipeOptions input_stream_handler_options_;
std::string node_name_; std::string node_name_;
std::map<std::string, GraphServiceRequest> service_requests_; std::map<std::string, GraphServiceRequest> service_requests_;
bool process_timestamps_ = false;
TimestampDiff timestamp_offset_ = TimestampDiff::Unset();
}; };
} // namespace mediapipe } // namespace mediapipe
+1 -1
View File
@@ -143,7 +143,7 @@ class CalculatorGraph {
const std::string& graph_type = "", const std::string& graph_type = "",
const Subgraph::SubgraphOptions* options = nullptr); const Subgraph::SubgraphOptions* options = nullptr);
// Resturns the canonicalized CalculatorGraphConfig for this graph. // Returns the canonicalized CalculatorGraphConfig for this graph.
const CalculatorGraphConfig& Config() const { const CalculatorGraphConfig& Config() const {
return validated_graph_->Config(); return validated_graph_->Config();
} }
@@ -31,6 +31,17 @@ namespace {
typedef std::function<::mediapipe::Status(CalculatorContext* cc)> typedef std::function<::mediapipe::Status(CalculatorContext* cc)>
CalculatorContextFunction; CalculatorContextFunction;
// Returns the contents of a set of Packets.
// The contents must be copyable.
template <typename T>
std::vector<T> GetContents(const std::vector<Packet>& packets) {
std::vector<T> result;
for (Packet p : packets) {
result.push_back(p.Get<T>());
}
return result;
}
// A simple Semaphore for synchronizing test threads. // A simple Semaphore for synchronizing test threads.
class AtomicSemaphore { class AtomicSemaphore {
public: public:
@@ -671,9 +682,9 @@ REGISTER_CALCULATOR(BoundToPacketCalculator);
// A Calculator that produces packets at timestamps beyond the input timestamp. // A Calculator that produces packets at timestamps beyond the input timestamp.
class FuturePacketCalculator : public CalculatorBase { class FuturePacketCalculator : public CalculatorBase {
public:
static constexpr int64 kOutputFutureMicros = 3; static constexpr int64 kOutputFutureMicros = 3;
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) { static ::mediapipe::Status GetContract(CalculatorContract* cc) {
cc->Inputs().Index(0).Set<int>(); cc->Inputs().Index(0).Set<int>();
cc->Outputs().Index(0).Set<int>(); cc->Outputs().Index(0).Set<int>();
@@ -742,9 +753,8 @@ TEST(CalculatorGraphBoundsTest, OffsetBoundPropagation) {
MP_ASSERT_OK(graph.WaitUntilDone()); MP_ASSERT_OK(graph.WaitUntilDone());
} }
// Shows that bounds changes alone do not invoke Process. // Shows that timestamp bounds changes alone do not invoke Process,
// Note: Bounds changes alone will invoke Process eventually // without SetProcessTimestampBounds(true).
// when SetOffset is cleared, see: go/mediapipe-realtime-graph.
TEST(CalculatorGraphBoundsTest, BoundWithoutInputPackets) { TEST(CalculatorGraphBoundsTest, BoundWithoutInputPackets) {
// OffsetBoundCalculator produces only timestamp bounds. // OffsetBoundCalculator produces only timestamp bounds.
// The BoundToPacketCalculator delivers an output packet whenever the // The BoundToPacketCalculator delivers an output packet whenever the
@@ -753,8 +763,13 @@ TEST(CalculatorGraphBoundsTest, BoundWithoutInputPackets) {
::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(R"( ::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(R"(
input_stream: 'input' input_stream: 'input'
node { node {
calculator: 'OffsetBoundCalculator' calculator: 'FuturePacketCalculator'
input_stream: 'input' input_stream: 'input'
output_stream: 'input_2'
}
node {
calculator: 'OffsetBoundCalculator'
input_stream: 'input_2'
output_stream: 'bounds' output_stream: 'bounds'
} }
node { node {
@@ -778,6 +793,7 @@ TEST(CalculatorGraphBoundsTest, BoundWithoutInputPackets) {
for (int i = 0; i < kNumInputs; ++i) { for (int i = 0; i < kNumInputs; ++i) {
Packet p = MakePacket<int>(33).At(Timestamp(i)); Packet p = MakePacket<int>(33).At(Timestamp(i));
MP_ASSERT_OK(graph.AddPacketToInputStream("input", p)); MP_ASSERT_OK(graph.AddPacketToInputStream("input", p));
MP_ASSERT_OK(graph.WaitUntilIdle());
} }
// No packets arrive, because updated timestamp bounds do not invoke // No packets arrive, because updated timestamp bounds do not invoke
@@ -1104,5 +1120,254 @@ TEST(CalculatorGraphBoundsTest, BoundsForEmptyInputs_SyncSets) {
)"); )");
} }
// A Calculator that produces a packet for each timestamp bounds update.
class ProcessBoundToPacketCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
for (int i = 0; i < cc->Inputs().NumEntries(); ++i) {
cc->Inputs().Index(i).SetAny();
}
for (int i = 0; i < cc->Outputs().NumEntries(); ++i) {
cc->Outputs().Index(i).Set<Timestamp>();
}
cc->SetInputStreamHandler("ImmediateInputStreamHandler");
cc->SetProcessTimestampBounds(true);
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) final {
for (int i = 0; i < cc->Outputs().NumEntries(); ++i) {
Timestamp t = cc->Inputs().Index(i).Value().Timestamp();
if (t == cc->InputTimestamp() &&
t >= cc->Outputs().Index(i).NextTimestampBound()) {
cc->Outputs().Index(i).Add(new auto(t), t);
}
}
return ::mediapipe::OkStatus();
}
};
REGISTER_CALCULATOR(ProcessBoundToPacketCalculator);
// A Calculator that passes through each packet and timestamp immediately.
class ImmediatePassthroughCalculator : public CalculatorBase {
public:
static ::mediapipe::Status GetContract(CalculatorContract* cc) {
for (int i = 0; i < cc->Inputs().NumEntries(); ++i) {
cc->Inputs().Index(i).SetAny();
}
for (int i = 0; i < cc->Outputs().NumEntries(); ++i) {
cc->Outputs().Index(i).SetSameAs(&cc->Inputs().Index(i));
}
cc->SetInputStreamHandler("ImmediateInputStreamHandler");
cc->SetProcessTimestampBounds(true);
return ::mediapipe::OkStatus();
}
::mediapipe::Status Process(CalculatorContext* cc) final {
for (int i = 0; i < cc->Outputs().NumEntries(); ++i) {
if (!cc->Inputs().Index(i).IsEmpty()) {
cc->Outputs().Index(i).AddPacket(cc->Inputs().Index(i).Value());
} else {
Timestamp input_bound =
cc->Inputs().Index(i).Value().Timestamp().NextAllowedInStream();
if (cc->Outputs().Index(i).NextTimestampBound() < input_bound) {
cc->Outputs().Index(i).SetNextTimestampBound(input_bound);
}
}
}
return ::mediapipe::OkStatus();
}
};
REGISTER_CALCULATOR(ImmediatePassthroughCalculator);
// Shows that Process is called for input-sets without input packets.
void TestProcessForEmptyInputs(const std::string& input_stream_handler) {
// FuturePacketCalculator and OffsetBoundCalculator produce only ts bounds,
// The ProcessBoundToPacketCalculator has SetProcessTimestampBounds(true),
// and produces an output packet for every timestamp bound update.
std::string config_str = R"(
input_stream: 'input'
node {
calculator: 'FuturePacketCalculator'
input_stream: 'input'
output_stream: 'futures'
}
node {
calculator: 'OffsetBoundCalculator'
input_stream: 'futures'
output_stream: 'bounds'
}
node {
calculator: 'ProcessBoundToPacketCalculator'
input_stream: 'bounds'
output_stream: 'bounds_ts'
input_stream_handler { $input_stream_handler }
}
)";
absl::StrReplaceAll({{"$input_stream_handler", input_stream_handler}},
&config_str);
CalculatorGraphConfig config =
::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(config_str);
CalculatorGraph graph;
std::vector<Packet> input_ts_packets;
std::vector<Packet> bounds_ts_packets;
MP_ASSERT_OK(graph.Initialize(config));
MP_ASSERT_OK(graph.ObserveOutputStream("bounds_ts", [&](const Packet& p) {
bounds_ts_packets.push_back(p);
return ::mediapipe::OkStatus();
}));
MP_ASSERT_OK(graph.StartRun({}));
MP_ASSERT_OK(graph.WaitUntilIdle());
// Add four packets into the graph.
constexpr int kFutureMicros = FuturePacketCalculator::kOutputFutureMicros;
Packet p;
p = MakePacket<int>(33).At(Timestamp(0));
MP_ASSERT_OK(graph.AddPacketToInputStream("input", p));
MP_ASSERT_OK(graph.WaitUntilIdle());
p = MakePacket<int>(33).At(Timestamp(10));
MP_ASSERT_OK(graph.AddPacketToInputStream("input", p));
MP_ASSERT_OK(graph.WaitUntilIdle());
p = MakePacket<int>(33).At(Timestamp(20));
MP_ASSERT_OK(graph.AddPacketToInputStream("input", p));
MP_ASSERT_OK(graph.WaitUntilIdle());
p = MakePacket<int>(33).At(Timestamp(30));
MP_ASSERT_OK(graph.AddPacketToInputStream("input", p));
MP_ASSERT_OK(graph.WaitUntilIdle());
// Packets arrive.
MP_ASSERT_OK(graph.WaitUntilIdle());
EXPECT_EQ(bounds_ts_packets.size(), 4);
std::vector<Timestamp> expected = {
Timestamp(0 + kFutureMicros), Timestamp(10 + kFutureMicros),
Timestamp(20 + kFutureMicros), Timestamp(30 + kFutureMicros)};
EXPECT_EQ(GetContents<Timestamp>(bounds_ts_packets), expected);
// Shutdown the graph.
MP_ASSERT_OK(graph.CloseAllPacketSources());
MP_ASSERT_OK(graph.WaitUntilDone());
}
// Shows that Process is called for input-sets without input packets
// using an DefaultInputStreamHandler.
TEST(CalculatorGraphBoundsTest, ProcessTimestampBounds_Default) {
TestProcessForEmptyInputs(R"(
input_stream_handler: "DefaultInputStreamHandler")");
}
// Shows that Process is called for input-sets without input packets
// using an ImmediateInputStreamHandler.
TEST(CalculatorGraphBoundsTest, ProcessTimestampBounds_Immediate) {
TestProcessForEmptyInputs(R"(
input_stream_handler: "ImmediateInputStreamHandler")");
}
// Shows that Process is called for input-sets without input packets
// using a SyncSetInputStreamHandler with a single sync-set.
TEST(CalculatorGraphBoundsTest, ProcessTimestampBounds_SyncSet) {
TestProcessForEmptyInputs(R"(
input_stream_handler: "SyncSetInputStreamHandler")");
}
// Shows that Process is called for input-sets without input packets
// using a SyncSetInputStreamHandler with multiple sync-sets.
TEST(CalculatorGraphBoundsTest, ProcessTimestampBounds_SyncSets) {
TestProcessForEmptyInputs(R"(
input_stream_handler: "SyncSetInputStreamHandler"
options {
[mediapipe.SyncSetInputStreamHandlerOptions.ext] {
sync_set { tag_index: ":0" }
}
}
)");
}
// Demonstrates the functionality of an "ImmediatePassthroughCalculator".
// The ImmediatePassthroughCalculator simply relays each input packet to
// the corresponding output stream. ProcessTimestampBounds is needed to
// relay timestamp bounds as well as packets.
TEST(CalculatorGraphBoundsTest, ProcessTimestampBounds_Passthrough) {
// OffsetBoundCalculator produces timestamp bounds.
// ImmediatePassthroughCalculator relays packets and bounds.
// ProcessBoundToPacketCalculator reports packets and bounds as packets.
std::string config_str = R"(
input_stream: "input_0"
input_stream: "input_1"
node {
calculator: "OffsetBoundCalculator"
input_stream: "input_1"
output_stream: "bound_1"
}
node {
calculator: "ImmediatePassthroughCalculator"
input_stream: "input_0"
input_stream: "bound_1"
output_stream: "same_0"
output_stream: "same_1"
}
node {
calculator: "ProcessBoundToPacketCalculator"
input_stream: "same_0"
input_stream: "same_1"
output_stream: "output_0"
output_stream: "output_1"
}
)";
CalculatorGraphConfig config =
::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(config_str);
CalculatorGraph graph;
std::vector<Packet> output_0_packets;
std::vector<Packet> output_1_packets;
MP_ASSERT_OK(graph.Initialize(config));
MP_ASSERT_OK(graph.ObserveOutputStream("output_0", [&](const Packet& p) {
output_0_packets.push_back(p);
return ::mediapipe::OkStatus();
}));
MP_ASSERT_OK(graph.ObserveOutputStream("output_1", [&](const Packet& p) {
output_1_packets.push_back(p);
return ::mediapipe::OkStatus();
}));
MP_ASSERT_OK(graph.StartRun({}));
MP_ASSERT_OK(graph.WaitUntilIdle());
// Add four packets to input_0.
for (int i = 0; i < 4; ++i) {
Packet p = MakePacket<int>(33).At(Timestamp(i * 10));
MP_ASSERT_OK(graph.AddPacketToInputStream("input_0", p));
MP_ASSERT_OK(graph.WaitUntilIdle());
}
// Packets arrive.
MP_ASSERT_OK(graph.WaitUntilIdle());
EXPECT_EQ(output_0_packets.size(), 4);
EXPECT_EQ(output_1_packets.size(), 0);
std::vector<Timestamp> expected = //
{Timestamp(0), Timestamp(10), Timestamp(20), Timestamp(30)};
EXPECT_EQ(GetContents<Timestamp>(output_0_packets), expected);
// Add two timestamp bounds to bound_1.
for (int i = 0; i < 2; ++i) {
Packet p = MakePacket<int>(33).At(Timestamp(10 + i * 10));
MP_ASSERT_OK(graph.AddPacketToInputStream("input_1", p));
MP_ASSERT_OK(graph.WaitUntilIdle());
}
// Bounds arrive.
MP_ASSERT_OK(graph.WaitUntilIdle());
EXPECT_EQ(output_0_packets.size(), 4);
EXPECT_EQ(output_1_packets.size(), 2);
expected = //
{Timestamp(10), Timestamp(20)};
EXPECT_EQ(GetContents<Timestamp>(output_1_packets), expected);
// Shutdown the graph.
MP_ASSERT_OK(graph.CloseAllPacketSources());
MP_ASSERT_OK(graph.WaitUntilDone());
}
} // namespace } // namespace
} // namespace mediapipe } // namespace mediapipe
@@ -29,6 +29,7 @@
#include "absl/memory/memory.h" #include "absl/memory/memory.h"
#include "absl/strings/escaping.h" #include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h" #include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
#include "absl/strings/substitute.h" #include "absl/strings/substitute.h"
#include "absl/time/clock.h" #include "absl/time/clock.h"
@@ -4558,5 +4559,68 @@ TEST(CalculatorGraph, SimpleMuxCalculatorWithCustomInputStreamHandler) {
testing::HasSubstr("ImmediateInputStreamHandler class comment"))); testing::HasSubstr("ImmediateInputStreamHandler class comment")));
} }
void DoTestMultipleGraphRuns(absl::string_view input_stream_handler,
bool select_packet) {
std::string graph_proto = absl::StrFormat(R"(
input_stream: 'input'
input_stream: 'select'
node {
calculator: 'PassThroughCalculator'
input_stream: 'input'
input_stream: 'select'
input_stream_handler {
input_stream_handler: "%s"
}
output_stream: 'output'
output_stream: 'select_out'
}
)",
input_stream_handler.data());
CalculatorGraphConfig config =
::mediapipe::ParseTextProtoOrDie<CalculatorGraphConfig>(graph_proto);
std::vector<Packet> packet_dump;
tool::AddVectorSink("output", &config, &packet_dump);
CalculatorGraph graph;
MP_ASSERT_OK(graph.Initialize(config));
struct Run {
Timestamp timestamp;
int value;
};
std::vector<Run> runs = {{.timestamp = Timestamp(2000), .value = 2},
{.timestamp = Timestamp(1000), .value = 1}};
for (const Run& run : runs) {
MP_ASSERT_OK(graph.StartRun({}));
if (select_packet) {
MP_EXPECT_OK(graph.AddPacketToInputStream(
"select", MakePacket<int>(0).At(run.timestamp)));
}
MP_EXPECT_OK(graph.AddPacketToInputStream(
"input", MakePacket<int>(run.value).At(run.timestamp)));
MP_ASSERT_OK(graph.WaitUntilIdle());
ASSERT_EQ(1, packet_dump.size());
EXPECT_EQ(run.value, packet_dump[0].Get<int>());
EXPECT_EQ(run.timestamp, packet_dump[0].Timestamp());
MP_ASSERT_OK(graph.CloseAllPacketSources());
MP_ASSERT_OK(graph.WaitUntilDone());
packet_dump.clear();
}
}
TEST(CalculatorGraph, MultipleRunsWithDifferentInputStreamHandlers) {
DoTestMultipleGraphRuns("BarrierInputStreamHandler", true);
DoTestMultipleGraphRuns("DefaultInputStreamHandler", true);
DoTestMultipleGraphRuns("EarlyCloseInputStreamHandler", true);
DoTestMultipleGraphRuns("FixedSizeInputStreamHandler", true);
DoTestMultipleGraphRuns("ImmediateInputStreamHandler", false);
DoTestMultipleGraphRuns("MuxInputStreamHandler", true);
DoTestMultipleGraphRuns("SyncSetInputStreamHandler", true);
DoTestMultipleGraphRuns("TimestampAlignInputStreamHandler", true);
}
} // namespace } // namespace
} // namespace mediapipe } // namespace mediapipe
+14 -1
View File
@@ -97,6 +97,7 @@ Timestamp CalculatorNode::SourceProcessOrder(
const NodeTypeInfo& node_type_info = const NodeTypeInfo& node_type_info =
validated_graph_->CalculatorInfos()[node_id_]; validated_graph_->CalculatorInfos()[node_id_];
const CalculatorContract& contract = node_type_info.Contract();
uses_gpu_ = uses_gpu_ =
node_type_info.InputSidePacketTypes().HasTag(kGpuSharedTagName) || node_type_info.InputSidePacketTypes().HasTag(kGpuSharedTagName) ||
@@ -147,6 +148,14 @@ Timestamp CalculatorNode::SourceProcessOrder(
use_calc_specified ? handler_config : node_config.input_stream_handler(), use_calc_specified ? handler_config : node_config.input_stream_handler(),
node_type_info.InputStreamTypes())); node_type_info.InputStreamTypes()));
for (auto& stream : output_stream_handler_->OutputStreams()) {
stream->Spec()->offset_enabled =
(contract.GetTimestampOffset() != TimestampDiff::Unset());
stream->Spec()->offset = contract.GetTimestampOffset();
}
input_stream_handler_->SetProcessTimestampBounds(
contract.GetProcessTimestampBounds());
return InitializeInputStreams(input_stream_managers, output_stream_managers); return InitializeInputStreams(input_stream_managers, output_stream_managers);
} }
@@ -805,7 +814,8 @@ std::string CalculatorNode::DebugName() const {
input_stream_handler_->FinalizeInputSet(input_timestamp, inputs); input_stream_handler_->FinalizeInputSet(input_timestamp, inputs);
output_stream_handler_->PrepareOutputs(input_timestamp, outputs); output_stream_handler_->PrepareOutputs(input_timestamp, outputs);
VLOG(2) << "Calling Calculator::Process() for node: " << DebugName(); VLOG(2) << "Calling Calculator::Process() for node: " << DebugName()
<< " timestamp: " << input_timestamp;
if (OutputsAreConstant(calculator_context)) { if (OutputsAreConstant(calculator_context)) {
// Do nothing. // Do nothing.
@@ -817,6 +827,9 @@ std::string CalculatorNode::DebugName() const {
result = calculator_->Process(calculator_context); result = calculator_->Process(calculator_context);
} }
VLOG(2) << "Called Calculator::Process() for node: " << DebugName()
<< " timestamp: " << input_timestamp;
// Removes one packet from each shard and progresses to the next input // Removes one packet from each shard and progresses to the next input
// timestamp. // timestamp.
input_stream_handler_->ClearCurrentInputs(calculator_context); input_stream_handler_->ClearCurrentInputs(calculator_context);
+5 -1
View File
@@ -113,7 +113,10 @@ message GraphTrace {
optional int32 stream_id = 4; optional int32 stream_id = 4;
// The address of the packet contents. // The address of the packet contents.
optional int64 packet_id = 5; optional int64 packet_id = 5 [deprecated = true];
// Data describing the event, such as the packet contents.
optional int64 event_data = 6;
} }
// The kind of event recorded. // The kind of event recorded.
@@ -133,6 +136,7 @@ message GraphTrace {
DSP_TASK = 12; DSP_TASK = 12;
TPU_TASK = 13; TPU_TASK = 13;
GPU_CALIBRATION = 14; GPU_CALIBRATION = 14;
PACKET_QUEUED = 15;
} }
// The timing for one packet set being processed at one caclulator node. // The timing for one packet set being processed at one caclulator node.
+4
View File
@@ -18,6 +18,10 @@ namespace mediapipe {
namespace { namespace {
// List of namespaces that can register calculators inside the namespace
// and still refer to them using an unqualified name. This whitelist
// is meant to facilitate migration from unqualified to fully qualified
// calculator names.
constexpr char const* kTopNamespaces[] = { constexpr char const* kTopNamespaces[] = {
"mediapipe", "mediapipe",
}; };
+12 -97
View File
@@ -13,7 +13,7 @@
# limitations under the License. # limitations under the License.
# #
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library") load("//mediapipe/framework/port:build_config.bzl", "mediapipe_proto_library")
package( package(
default_visibility = ["//visibility:private"], default_visibility = ["//visibility:private"],
@@ -24,95 +24,45 @@ licenses(["notice"]) # Apache 2.0
exports_files(["LICENSE"]) exports_files(["LICENSE"])
proto_library( mediapipe_proto_library(
name = "detection_proto", name = "detection_proto",
srcs = ["detection.proto"], srcs = ["detection.proto"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = ["//mediapipe/framework/formats:location_data_proto"], deps = ["//mediapipe/framework/formats:location_data_proto"],
) )
proto_library( mediapipe_proto_library(
name = "classification_proto", name = "classification_proto",
srcs = ["classification.proto"], srcs = ["classification.proto"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
proto_library( mediapipe_proto_library(
name = "image_format_proto", name = "image_format_proto",
srcs = ["image_format.proto"], srcs = ["image_format.proto"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
proto_library( mediapipe_proto_library(
name = "matrix_data_proto", name = "matrix_data_proto",
srcs = ["matrix_data.proto"], srcs = ["matrix_data.proto"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
proto_library( mediapipe_proto_library(
name = "location_data_proto", name = "location_data_proto",
srcs = ["location_data.proto"], srcs = ["location_data.proto"],
portable_deps = ["//mediapipe/framework/formats/annotation:rasterization_cc_proto"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = ["//mediapipe/framework/formats/annotation:rasterization_proto"], deps = ["//mediapipe/framework/formats/annotation:rasterization_proto"],
) )
proto_library( mediapipe_proto_library(
name = "time_series_header_proto", name = "time_series_header_proto",
srcs = ["time_series_header.proto"], srcs = ["time_series_header.proto"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
mediapipe_cc_proto_library(
name = "detection_cc_proto",
srcs = ["detection.proto"],
cc_deps = [":location_data_cc_proto"],
visibility = ["//visibility:public"],
deps = [":detection_proto"],
)
java_lite_proto_library(
name = "detection_java_proto_lite",
strict_deps = 0,
visibility = ["//mediapipe:__subpackages__"],
deps = [":detection_proto"],
)
mediapipe_cc_proto_library(
name = "classification_cc_proto",
srcs = ["classification.proto"],
visibility = ["//visibility:public"],
deps = [":classification_proto"],
)
mediapipe_cc_proto_library(
name = "image_format_cc_proto",
srcs = ["image_format.proto"],
visibility = ["//visibility:public"],
deps = [":image_format_proto"],
)
mediapipe_cc_proto_library(
name = "matrix_data_cc_proto",
srcs = ["matrix_data.proto"],
visibility = ["//visibility:public"],
deps = [":matrix_data_proto"],
)
mediapipe_cc_proto_library(
name = "location_data_cc_proto",
srcs = ["location_data.proto"],
cc_deps = ["//mediapipe/framework/formats/annotation:rasterization_cc_proto"],
visibility = ["//visibility:public"],
deps = [":location_data_proto"],
)
mediapipe_cc_proto_library(
name = "time_series_header_cc_proto",
srcs = ["time_series_header.proto"],
visibility = ["//visibility:public"],
deps = [":time_series_header_proto"],
)
cc_library( cc_library(
name = "deleting_file", name = "deleting_file",
srcs = ["deleting_file.cc"], srcs = ["deleting_file.cc"],
@@ -245,33 +195,14 @@ cc_test(
], ],
) )
proto_library( mediapipe_proto_library(
name = "rect_proto", name = "rect_proto",
srcs = ["rect.proto"], srcs = ["rect.proto"],
visibility = [ visibility = ["//visibility:public"],
"//visibility:public", deps = ["//mediapipe/framework/formats:location_data_proto"],
],
) )
mediapipe_cc_proto_library( mediapipe_proto_library(
name = "rect_cc_proto",
srcs = ["rect.proto"],
visibility = [
"//visibility:public",
],
deps = [":rect_proto"],
)
java_lite_proto_library(
name = "rect_java_proto_lite",
strict_deps = 0,
visibility = [
"//mediapipe:__subpackages__",
],
deps = [":rect_proto"],
)
proto_library(
name = "landmark_proto", name = "landmark_proto",
srcs = ["landmark.proto"], srcs = ["landmark.proto"],
visibility = [ visibility = [
@@ -279,22 +210,6 @@ proto_library(
], ],
) )
mediapipe_cc_proto_library(
name = "landmark_cc_proto",
srcs = ["landmark.proto"],
visibility = ["//visibility:public"],
deps = [":landmark_proto"],
)
java_lite_proto_library(
name = "landmark_java_proto_lite",
strict_deps = 0,
visibility = [
"//mediapipe:__subpackages__",
],
deps = [":landmark_proto"],
)
# Expose the proto source files for building mediapipe AAR. # Expose the proto source files for building mediapipe AAR.
filegroup( filegroup(
name = "protos_src", name = "protos_src",
+12 -19
View File
@@ -14,38 +14,31 @@
# limitations under the License. # limitations under the License.
# #
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_proto_library")
package(default_visibility = ["//visibility:private"])
licenses(["notice"]) # Apache 2.0 licenses(["notice"]) # Apache 2.0
exports_files(["LICENSE"]) exports_files(["LICENSE"])
load("//mediapipe/framework/port:build_config.bzl", "mediapipe_cc_proto_library") mediapipe_proto_library(
package(default_visibility = ["//visibility:private"])
proto_library(
name = "locus_proto", name = "locus_proto",
srcs = ["locus.proto"], srcs = ["locus.proto"],
portable_deps = ["//mediapipe/framework/formats/annotation:rasterization_cc_proto"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = ["//mediapipe/framework/formats/annotation:rasterization_proto"], deps = ["//mediapipe/framework/formats/annotation:rasterization_proto"],
) )
proto_library( mediapipe_proto_library(
name = "rasterization_proto", name = "rasterization_proto",
srcs = ["rasterization.proto"], srcs = ["rasterization.proto"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
mediapipe_cc_proto_library( # Expose the proto source files for building mediapipe AAR.
name = "locus_cc_proto", filegroup(
srcs = ["locus.proto"], name = "protos_src",
cc_deps = [":rasterization_cc_proto"], srcs = glob(["*.proto"]),
visibility = ["//visibility:public"], visibility = ["//mediapipe:__subpackages__"],
deps = [":locus_proto"],
)
mediapipe_cc_proto_library(
name = "rasterization_cc_proto",
srcs = ["rasterization.proto"],
visibility = ["//visibility:public"],
deps = [":rasterization_proto"],
) )
@@ -16,6 +16,9 @@ syntax = "proto2";
package mediapipe; package mediapipe;
option java_package = "com.google.mediapipe.formats.annotation.proto";
option java_outer_classname = "RasterizationProto";
// A Region can be represented in each frame as a set of scanlines // A Region can be represented in each frame as a set of scanlines
// (compressed RLE, similar to rasterization of polygons). // (compressed RLE, similar to rasterization of polygons).
// For each scanline with y-coordinate y, we save (possibly multiple) intervals // For each scanline with y-coordinate y, we save (possibly multiple) intervals
@@ -23,6 +23,9 @@ package mediapipe;
import "mediapipe/framework/formats/annotation/rasterization.proto"; import "mediapipe/framework/formats/annotation/rasterization.proto";
option java_package = "com.google.mediapipe.formats.proto";
option java_outer_classname = "LocationDataProto";
message LocationData { message LocationData {
// The supported formats for representing location data. A single location // The supported formats for representing location data. A single location
// must store its data in exactly one way. // must store its data in exactly one way.
+122
View File
@@ -22,6 +22,8 @@
namespace mediapipe { namespace mediapipe {
using SyncSet = InputStreamHandler::SyncSet;
::mediapipe::Status InputStreamHandler::InitializeInputStreamManagers( ::mediapipe::Status InputStreamHandler::InitializeInputStreamManagers(
InputStreamManager* flat_input_stream_managers) { InputStreamManager* flat_input_stream_managers) {
for (CollectionItemId id = input_stream_managers_.BeginId(); for (CollectionItemId id = input_stream_managers_.BeginId();
@@ -228,8 +230,36 @@ void InputStreamHandler::FinalizeInputSet(Timestamp timestamp,
} }
} }
// Returns the default CalculatorContext.
CalculatorContext* GetCalculatorContext(CalculatorContextManager* manager) {
return (manager && manager->HasDefaultCalculatorContext())
? manager->GetDefaultCalculatorContext()
: nullptr;
}
// Logs the current queue size of an input stream.
void LogQueuedPackets(CalculatorContext* context, InputStreamManager* stream,
Packet queue_tail) {
if (context) {
TraceEvent event = TraceEvent(TraceEvent::PACKET_QUEUED)
.set_node_id(context->NodeId())
.set_input_ts(queue_tail.Timestamp())
.set_stream_id(&stream->Name())
.set_event_data(stream->QueueSize() + 1);
::mediapipe::LogEvent(context->GetProfilingContext(),
event.set_packet_ts(queue_tail.Timestamp()));
Packet queue_head = stream->QueueHead();
if (!queue_head.IsEmpty()) {
::mediapipe::LogEvent(context->GetProfilingContext(),
event.set_packet_ts(queue_head.Timestamp()));
}
}
}
void InputStreamHandler::AddPackets(CollectionItemId id, void InputStreamHandler::AddPackets(CollectionItemId id,
const std::list<Packet>& packets) { const std::list<Packet>& packets) {
LogQueuedPackets(GetCalculatorContext(calculator_context_manager_),
input_stream_managers_.Get(id), packets.back());
bool notify = false; bool notify = false;
::mediapipe::Status result = ::mediapipe::Status result =
input_stream_managers_.Get(id)->AddPackets(packets, &notify); input_stream_managers_.Get(id)->AddPackets(packets, &notify);
@@ -243,6 +273,8 @@ void InputStreamHandler::AddPackets(CollectionItemId id,
void InputStreamHandler::MovePackets(CollectionItemId id, void InputStreamHandler::MovePackets(CollectionItemId id,
std::list<Packet>* packets) { std::list<Packet>* packets) {
LogQueuedPackets(GetCalculatorContext(calculator_context_manager_),
input_stream_managers_.Get(id), packets->back());
bool notify = false; bool notify = false;
::mediapipe::Status result = ::mediapipe::Status result =
input_stream_managers_.Get(id)->MovePackets(packets, &notify); input_stream_managers_.Get(id)->MovePackets(packets, &notify);
@@ -300,4 +332,94 @@ void InputStreamHandler::SetLatePreparation(bool late_preparation) {
late_preparation_ = late_preparation; late_preparation_ = late_preparation;
} }
SyncSet::SyncSet(InputStreamHandler* input_stream_handler,
std::vector<CollectionItemId> stream_ids)
: input_stream_handler_(input_stream_handler),
stream_ids_(std::move(stream_ids)) {}
void SyncSet::PrepareForRun() { last_processed_ts_ = Timestamp::Unset(); }
NodeReadiness SyncSet::GetReadiness(Timestamp* min_stream_timestamp) {
Timestamp min_bound = Timestamp::Done();
Timestamp min_packet = Timestamp::Done();
for (CollectionItemId id : stream_ids_) {
const auto& stream = input_stream_handler_->input_stream_managers_.Get(id);
bool empty;
Timestamp stream_timestamp = stream->MinTimestampOrBound(&empty);
if (empty) {
min_bound = std::min(min_bound, stream_timestamp);
} else {
min_packet = std::min(min_packet, stream_timestamp);
}
}
*min_stream_timestamp = std::min(min_packet, min_bound);
if (*min_stream_timestamp == Timestamp::Done()) {
last_processed_ts_ = Timestamp::Done().PreviousAllowedInStream();
return NodeReadiness::kReadyForClose;
}
if (!input_stream_handler_->process_timestamps_) {
// Only an input_ts with packets can be processed.
// Note that (min_bound - 1) is the highest fully settled timestamp.
if (min_bound > min_packet) {
last_processed_ts_ = *min_stream_timestamp;
return NodeReadiness::kReadyForProcess;
}
} else {
// Any unprocessed input_ts can be processed.
// Note that (min_bound - 1) is the highest fully settled timestamp.
Timestamp input_timestamp =
std::min(min_packet, min_bound.PreviousAllowedInStream());
if (input_timestamp >
std::max(last_processed_ts_, Timestamp::Unstarted())) {
*min_stream_timestamp = input_timestamp;
last_processed_ts_ = input_timestamp;
return NodeReadiness::kReadyForProcess;
}
}
return NodeReadiness::kNotReady;
}
Timestamp SyncSet::LastProcessed() const { return last_processed_ts_; }
Timestamp SyncSet::MinPacketTimestamp() const {
Timestamp result = Timestamp::Done();
for (CollectionItemId id : stream_ids_) {
const auto& stream = input_stream_handler_->input_stream_managers_.Get(id);
bool empty;
Timestamp stream_timestamp = stream->MinTimestampOrBound(&empty);
if (!empty) {
result = std::min(result, stream_timestamp);
}
}
return result;
}
void SyncSet::FillInputSet(Timestamp input_timestamp,
InputStreamShardSet* input_set) {
CHECK(input_timestamp.IsAllowedInStream());
CHECK(input_set);
for (CollectionItemId id : stream_ids_) {
const auto& stream = input_stream_handler_->input_stream_managers_.Get(id);
int num_packets_dropped = 0;
bool stream_is_done = false;
Packet current_packet = stream->PopPacketAtTimestamp(
input_timestamp, &num_packets_dropped, &stream_is_done);
CHECK_EQ(num_packets_dropped, 0)
<< absl::Substitute("Dropped $0 packet(s) on input stream \"$1\".",
num_packets_dropped, stream->Name());
input_stream_handler_->AddPacketToShard(
&input_set->Get(id), std::move(current_packet), stream_is_done);
}
}
void SyncSet::FillInputBounds(InputStreamShardSet* input_set) {
for (CollectionItemId id : stream_ids_) {
const auto* stream = input_stream_handler_->input_stream_managers_.Get(id);
Timestamp bound = stream->MinTimestampOrBound(nullptr);
input_stream_handler_->AddPacketToShard(
&input_set->Get(id), Packet().At(bound.PreviousAllowedInStream()),
bound == Timestamp::Done());
}
}
} // namespace mediapipe } // namespace mediapipe
+60 -5
View File
@@ -74,9 +74,7 @@ class InputStreamHandler {
: input_stream_managers_(std::move(tag_map)), : input_stream_managers_(std::move(tag_map)),
calculator_context_manager_(calculator_context_manager), calculator_context_manager_(calculator_context_manager),
options_(options), options_(options),
calculator_run_in_parallel_(calculator_run_in_parallel), calculator_run_in_parallel_(calculator_run_in_parallel) {}
late_preparation_(false),
batch_size_(1) {}
virtual ~InputStreamHandler() = default; virtual ~InputStreamHandler() = default;
@@ -174,6 +172,60 @@ class InputStreamHandler {
return unset_header_count_.load(std::memory_order_relaxed); return unset_header_count_.load(std::memory_order_relaxed);
} }
// When true, Calculator::Process is called for any increase in the
// timestamp bound, whether or not any packets are available.
// Calculator::Process is called when the minimum timestamp bound
// increases for any synchronized set of input streams.
// DefaultInputStreamHandler groups all input streams into a single set.
// ImmediateInputStreamHandler treats each input stream as a separate set.
void SetProcessTimestampBounds(bool process_ts) {
process_timestamps_ = process_ts;
}
// When true, Calculator::Process is called for every input timestamp bound.
bool ProcessTimestampBounds() { return process_timestamps_; }
// A helper class to build input packet sets for a certain set of streams.
//
// ReadyForProcess requires all of the streams to be fully determined
// at the same input-timestamp.
// This is the readiness policy for all streams in DefaultInputStreamHandler.
// It is also the policy for each sync-set in SyncSetInputStreamHandler.
// It is also the policy for each input-stream in ImmediateInputStreamHandler.
//
// If ProcessTimestampBounds() is set, then a fully determined input timestamp
// with only empty input packets will qualify as ReadyForProcess.
class SyncSet {
public:
// Creates a SyncSet for a certain set of streams, |stream_ids|.
SyncSet(InputStreamHandler* input_stream_handler,
std::vector<CollectionItemId> stream_ids);
// Reinitializes this SyncSet before each CalculatorGraph run.
void PrepareForRun();
// Answers whether this stream is ready for Process or Close.
NodeReadiness GetReadiness(Timestamp* min_stream_timestamp);
// Returns the latest timestamp returned for processing.
Timestamp LastProcessed() const;
// The earliest available packet timestamp, or Timestamp::Done.
Timestamp MinPacketTimestamp() const;
// Moves packets from all input streams to the input_set.
void FillInputSet(Timestamp input_timestamp,
InputStreamShardSet* input_set);
// Copies timestamp bounds from all input streams to the input_set.
void FillInputBounds(InputStreamShardSet* input_set);
private:
InputStreamHandler* input_stream_handler_;
std::vector<CollectionItemId> stream_ids_;
Timestamp last_processed_ts_ = Timestamp::Unset();
};
protected: protected:
typedef internal::Collection<InputStreamManager*> InputStreamManagerSet; typedef internal::Collection<InputStreamManager*> InputStreamManagerSet;
@@ -240,11 +292,14 @@ class InputStreamHandler {
// The variable is set to false by default. A subclass should set it to true // The variable is set to false by default. A subclass should set it to true
// with SetLatePreparation(true) in the constructor if the input sets need to // with SetLatePreparation(true) in the constructor if the input sets need to
// be filled in ProcessNode(). // be filled in ProcessNode().
bool late_preparation_; bool late_preparation_ = false;
// Determines how many sets of input packets are collected before a // Determines how many sets of input packets are collected before a
// CalculatorNode is scheduled. // CalculatorNode is scheduled.
int batch_size_; int batch_size_ = 1;
// When true, any increase in timestamp bound invokes Calculator::Process.
bool process_timestamps_ = false;
// A callback to notify the observer when all the input stream headers // A callback to notify the observer when all the input stream headers
// (excluding headers of back edges) become available. // (excluding headers of back edges) become available.
+14 -14
View File
@@ -152,7 +152,7 @@ template <typename Container>
// If the caller is MovePackets(), packet's underlying holder should be // If the caller is MovePackets(), packet's underlying holder should be
// transferred into queue_. Otherwise, queue_ keeps a copy of the packet. // transferred into queue_. Otherwise, queue_ keeps a copy of the packet.
++num_packets_added_; ++num_packets_added_;
VLOG(2) << "Input stream:" << name_ VLOG(3) << "Input stream:" << name_
<< " has added packet at time: " << packet.Timestamp(); << " has added packet at time: " << packet.Timestamp();
if (std::is_const< if (std::is_const<
typename std::remove_reference<Container>::type>::value) { typename std::remove_reference<Container>::type>::value) {
@@ -163,15 +163,15 @@ template <typename Container>
} }
queue_became_full = (!was_queue_full && max_queue_size_ != -1 && queue_became_full = (!was_queue_full && max_queue_size_ != -1 &&
queue_.size() >= max_queue_size_); queue_.size() >= max_queue_size_);
VLOG_IF(2, queue_.size() > 1) VLOG_IF(3, queue_.size() > 1)
<< "Queue size greater than 1: stream name: " << name_ << "Queue size greater than 1: stream name: " << name_
<< " queue_size: " << queue_.size(); << " queue_size: " << queue_.size();
VLOG(2) << "Input stream:" << name_ VLOG(3) << "Input stream:" << name_
<< " becomes non-empty status:" << queue_became_non_empty << " becomes non-empty status:" << queue_became_non_empty
<< " Size: " << queue_.size(); << " Size: " << queue_.size();
} }
if (queue_became_full) { if (queue_became_full) {
VLOG(2) << "Queue became full: " << Name(); VLOG(3) << "Queue became full: " << Name();
becomes_full_callback_(this, &last_reported_stream_full_); becomes_full_callback_(this, &last_reported_stream_full_);
} }
*notify = queue_became_non_empty; *notify = queue_became_non_empty;
@@ -257,7 +257,7 @@ Packet InputStreamManager::PopPacketAtTimestamp(Timestamp timestamp,
next_timestamp_bound_ = timestamp.NextAllowedInStream(); next_timestamp_bound_ = timestamp.NextAllowedInStream();
} }
VLOG(2) << "Input stream " << name_ VLOG(3) << "Input stream " << name_
<< " selecting at timestamp:" << timestamp.Value() << " selecting at timestamp:" << timestamp.Value()
<< " next timestamp bound: " << next_timestamp_bound_; << " next timestamp bound: " << next_timestamp_bound_;
@@ -282,13 +282,13 @@ Packet InputStreamManager::PopPacketAtTimestamp(Timestamp timestamp,
++(*num_packets_dropped); ++(*num_packets_dropped);
} }
VLOG(2) << "Input stream removed packets:" << name_ VLOG(3) << "Input stream removed packets:" << name_
<< " Size:" << queue_.size(); << " Size:" << queue_.size();
queue_became_non_full = (was_queue_full && queue_.size() < max_queue_size_); queue_became_non_full = (was_queue_full && queue_.size() < max_queue_size_);
*stream_is_done = IsDone(); *stream_is_done = IsDone();
} }
if (queue_became_non_full) { if (queue_became_non_full) {
VLOG(2) << "Queue became non-full: " << Name(); VLOG(3) << "Queue became non-full: " << Name();
becomes_not_full_callback_(this, &last_reported_stream_full_); becomes_not_full_callback_(this, &last_reported_stream_full_);
} }
return packet; return packet;
@@ -302,7 +302,7 @@ Packet InputStreamManager::PopQueueHead(bool* stream_is_done) {
{ {
absl::MutexLock stream_lock(&stream_mutex_); absl::MutexLock stream_lock(&stream_mutex_);
VLOG(2) << "Input stream " << name_ << " selecting at queue head"; VLOG(3) << "Input stream " << name_ << " selecting at queue head";
// Check if queue is full. // Check if queue is full.
bool was_queue_full = bool was_queue_full =
@@ -315,13 +315,13 @@ Packet InputStreamManager::PopQueueHead(bool* stream_is_done) {
packet = Packet(); packet = Packet();
} }
VLOG(2) << "Input stream removed a packet:" << name_ VLOG(3) << "Input stream removed a packet:" << name_
<< " Size:" << queue_.size(); << " Size:" << queue_.size();
queue_became_non_full = (was_queue_full && queue_.size() < max_queue_size_); queue_became_non_full = (was_queue_full && queue_.size() < max_queue_size_);
*stream_is_done = IsDone(); *stream_is_done = IsDone();
} }
if (queue_became_non_full) { if (queue_became_non_full) {
VLOG(2) << "Queue became non-full: " << Name(); VLOG(3) << "Queue became non-full: " << Name();
becomes_not_full_callback_(this, &last_reported_stream_full_); becomes_not_full_callback_(this, &last_reported_stream_full_);
} }
return packet; return packet;
@@ -349,10 +349,10 @@ void InputStreamManager::SetMaxQueueSize(int max_queue_size) {
// QueueSizeCallback is called with no mutexes held. // QueueSizeCallback is called with no mutexes held.
if (!was_full && is_full) { if (!was_full && is_full) {
VLOG(2) << "Queue became full: " << Name(); VLOG(3) << "Queue became full: " << Name();
becomes_full_callback_(this, &last_reported_stream_full_); becomes_full_callback_(this, &last_reported_stream_full_);
} else if (was_full && !is_full) { } else if (was_full && !is_full) {
VLOG(2) << "Queue became non-full: " << Name(); VLOG(3) << "Queue became non-full: " << Name();
becomes_not_full_callback_(this, &last_reported_stream_full_); becomes_not_full_callback_(this, &last_reported_stream_full_);
} }
} }
@@ -382,12 +382,12 @@ void InputStreamManager::ErasePacketsEarlierThan(Timestamp timestamp) {
queue_.pop_front(); queue_.pop_front();
} }
VLOG(2) << "Input stream removed packets:" << name_ VLOG(3) << "Input stream removed packets:" << name_
<< " Size:" << queue_.size(); << " Size:" << queue_.size();
queue_became_non_full = (was_queue_full && queue_.size() < max_queue_size_); queue_became_non_full = (was_queue_full && queue_.size() < max_queue_size_);
} }
if (queue_became_non_full) { if (queue_became_non_full) {
VLOG(2) << "Queue became non-full: " << Name(); VLOG(3) << "Queue became non-full: " << Name();
becomes_not_full_callback_(this, &last_reported_stream_full_); becomes_not_full_callback_(this, &last_reported_stream_full_);
} }
} }
@@ -107,6 +107,9 @@ CalculatorContext* LegacyCalculatorSupport::Scoped<CalculatorContext>::current_;
template <> template <>
CalculatorContract* CalculatorContract*
LegacyCalculatorSupport::Scoped<CalculatorContract>::current_; LegacyCalculatorSupport::Scoped<CalculatorContract>::current_;
#elif _MSC_VER
// MSVC interprets these declarations as definitions and during linking it
// generates an error about multiple definitions of current_.
#else #else
template <> template <>
thread_local CalculatorContext* thread_local CalculatorContext*
+5 -2
View File
@@ -46,6 +46,7 @@ class OutputStreamHandler {
// ids of upstream sources that affect it. // ids of upstream sources that affect it.
typedef std::unordered_map<std::string, std::unordered_set<int>> typedef std::unordered_map<std::string, std::unordered_set<int>>
OutputStreamToSourcesMap; OutputStreamToSourcesMap;
typedef internal::Collection<OutputStreamManager*> OutputStreamManagerSet;
// The constructor of the OutputStreamHandler takes four arguments. // The constructor of the OutputStreamHandler takes four arguments.
// The tag_map argument holds the information needed for tag/index retrieval // The tag_map argument holds the information needed for tag/index retrieval
@@ -119,9 +120,11 @@ class OutputStreamHandler {
// collection for debugging purpose. // collection for debugging purpose.
std::string FirstStreamName() const; std::string FirstStreamName() const;
protected: const OutputStreamManagerSet& OutputStreams() {
typedef internal::Collection<OutputStreamManager*> OutputStreamManagerSet; return output_stream_managers_;
}
protected:
// Checks if the given input bound should be propagated or not. If any output // Checks if the given input bound should be propagated or not. If any output
// streams with OffsetEnabled() need to have the timestamp bounds updated, // streams with OffsetEnabled() need to have the timestamp bounds updated,
// then propagates the timestamp bounds of all output streams with // then propagates the timestamp bounds of all output streams with
+2 -2
View File
@@ -164,9 +164,9 @@ void OutputStreamManager::PropagateUpdatesToMirrors(
next_timestamp_bound_ = next_timestamp_bound; next_timestamp_bound_ = next_timestamp_bound;
} }
std::list<Packet>* packets_to_propagate = output_stream_shard->OutputQueue(); std::list<Packet>* packets_to_propagate = output_stream_shard->OutputQueue();
VLOG(2) << "Output stream: " << Name() VLOG(3) << "Output stream: " << Name()
<< " queue size: " << packets_to_propagate->size(); << " queue size: " << packets_to_propagate->size();
VLOG(2) << "Output stream: " << Name() VLOG(3) << "Output stream: " << Name()
<< " next timestamp: " << next_timestamp_bound; << " next timestamp: " << next_timestamp_bound;
bool add_packets = !packets_to_propagate->empty(); bool add_packets = !packets_to_propagate->empty();
bool set_bound = bool set_bound =
@@ -27,6 +27,9 @@ class OutputStreamPoller {
OutputStreamPoller(const OutputStreamPoller&) = delete; OutputStreamPoller(const OutputStreamPoller&) = delete;
OutputStreamPoller& operator=(const OutputStreamPoller&) = delete; OutputStreamPoller& operator=(const OutputStreamPoller&) = delete;
OutputStreamPoller(OutputStreamPoller&&) = default; OutputStreamPoller(OutputStreamPoller&&) = default;
// Move assignment needs to be explicitly defaulted to allow ASSIGN_OR_RETURN
// on `StatusOr<OutputStreamPoller>`.
OutputStreamPoller& operator=(OutputStreamPoller&&) = default;
// Resets OutputStramPollerImpl and cleans the internal packet queue. // Resets OutputStramPollerImpl and cleans the internal packet queue.
void Reset() { void Reset() {
+14 -14
View File
@@ -540,11 +540,11 @@ const Holder<T>* HolderBase::As() const {
inline Packet::Packet(const Packet& packet) inline Packet::Packet(const Packet& packet)
: holder_(packet.holder_), timestamp_(packet.timestamp_) { : holder_(packet.holder_), timestamp_(packet.timestamp_) {
VLOG(2) << "Using copy constructor of " << packet.DebugString(); VLOG(4) << "Using copy constructor of " << packet.DebugString();
} }
inline Packet& Packet::operator=(const Packet& packet) { inline Packet& Packet::operator=(const Packet& packet) {
VLOG(2) << "Using copy assignment operator of " << packet.DebugString(); VLOG(4) << "Using copy assignment operator of " << packet.DebugString();
if (this != &packet) { if (this != &packet) {
holder_ = packet.holder_; holder_ = packet.holder_;
timestamp_ = packet.timestamp_; timestamp_ = packet.timestamp_;
@@ -559,11 +559,11 @@ inline ::mediapipe::StatusOr<std::unique_ptr<T>> Packet::Consume() {
// Clients who use this function are responsible for ensuring that no // Clients who use this function are responsible for ensuring that no
// other thread is doing anything with this Packet. // other thread is doing anything with this Packet.
if (holder_.unique()) { if (holder_.unique()) {
VLOG(1) << "Consuming the data of " << DebugString(); VLOG(2) << "Consuming the data of " << DebugString();
::mediapipe::StatusOr<std::unique_ptr<T>> release_result = ::mediapipe::StatusOr<std::unique_ptr<T>> release_result =
holder_->As<T>()->Release(); holder_->As<T>()->Release();
if (release_result.ok()) { if (release_result.ok()) {
VLOG(1) << "Setting " << DebugString() << " to empty."; VLOG(2) << "Setting " << DebugString() << " to empty.";
holder_.reset(); holder_.reset();
} }
return release_result; return release_result;
@@ -582,11 +582,11 @@ inline ::mediapipe::StatusOr<std::unique_ptr<T>> Packet::ConsumeOrCopy(
// If holder is the sole owner of the underlying data, consumes this packet. // If holder is the sole owner of the underlying data, consumes this packet.
if (!holder_->HolderIsOfType<packet_internal::ForeignHolder<T>>() && if (!holder_->HolderIsOfType<packet_internal::ForeignHolder<T>>() &&
holder_.unique()) { holder_.unique()) {
VLOG(1) << "Consuming the data of " << DebugString(); VLOG(2) << "Consuming the data of " << DebugString();
::mediapipe::StatusOr<std::unique_ptr<T>> release_result = ::mediapipe::StatusOr<std::unique_ptr<T>> release_result =
holder_->As<T>()->Release(); holder_->As<T>()->Release();
if (release_result.ok()) { if (release_result.ok()) {
VLOG(1) << "Setting " << DebugString() << " to empty."; VLOG(2) << "Setting " << DebugString() << " to empty.";
holder_.reset(); holder_.reset();
} }
if (was_copied) { if (was_copied) {
@@ -594,9 +594,9 @@ inline ::mediapipe::StatusOr<std::unique_ptr<T>> Packet::ConsumeOrCopy(
} }
return release_result; return release_result;
} }
VLOG(1) << "Copying the data of " << DebugString(); VLOG(2) << "Copying the data of " << DebugString();
std::unique_ptr<T> data_ptr = absl::make_unique<T>(Get<T>()); std::unique_ptr<T> data_ptr = absl::make_unique<T>(Get<T>());
VLOG(1) << "Setting " << DebugString() << " to empty."; VLOG(2) << "Setting " << DebugString() << " to empty.";
holder_.reset(); holder_.reset();
if (was_copied) { if (was_copied) {
*was_copied = true; *was_copied = true;
@@ -613,11 +613,11 @@ inline ::mediapipe::StatusOr<std::unique_ptr<T>> Packet::ConsumeOrCopy(
// If holder is the sole owner of the underlying data, consumes this packet. // If holder is the sole owner of the underlying data, consumes this packet.
if (!holder_->HolderIsOfType<packet_internal::ForeignHolder<T>>() && if (!holder_->HolderIsOfType<packet_internal::ForeignHolder<T>>() &&
holder_.unique()) { holder_.unique()) {
VLOG(1) << "Consuming the data of " << DebugString(); VLOG(2) << "Consuming the data of " << DebugString();
::mediapipe::StatusOr<std::unique_ptr<T>> release_result = ::mediapipe::StatusOr<std::unique_ptr<T>> release_result =
holder_->As<T>()->Release(); holder_->As<T>()->Release();
if (release_result.ok()) { if (release_result.ok()) {
VLOG(1) << "Setting " << DebugString() << " to empty."; VLOG(2) << "Setting " << DebugString() << " to empty.";
holder_.reset(); holder_.reset();
} }
if (was_copied) { if (was_copied) {
@@ -625,7 +625,7 @@ inline ::mediapipe::StatusOr<std::unique_ptr<T>> Packet::ConsumeOrCopy(
} }
return release_result; return release_result;
} }
VLOG(1) << "Copying the data of " << DebugString(); VLOG(2) << "Copying the data of " << DebugString();
const auto& original_array = Get<T>(); const auto& original_array = Get<T>();
// Type T is bounded array type, such as int[N] and float[M]. // Type T is bounded array type, such as int[N] and float[M].
// The new operator creates a new bounded array. // The new operator creates a new bounded array.
@@ -633,7 +633,7 @@ inline ::mediapipe::StatusOr<std::unique_ptr<T>> Packet::ConsumeOrCopy(
// Copies bounded array data into data_ptr. // Copies bounded array data into data_ptr.
std::copy(std::begin(original_array), std::end(original_array), std::copy(std::begin(original_array), std::end(original_array),
std::begin(*data_ptr)); std::begin(*data_ptr));
VLOG(1) << "Setting " << DebugString() << " to empty."; VLOG(2) << "Setting " << DebugString() << " to empty.";
holder_.reset(); holder_.reset();
if (was_copied) { if (was_copied) {
*was_copied = true; *was_copied = true;
@@ -650,14 +650,14 @@ inline ::mediapipe::StatusOr<std::unique_ptr<T>> Packet::ConsumeOrCopy(
} }
inline Packet::Packet(Packet&& packet) { inline Packet::Packet(Packet&& packet) {
VLOG(2) << "Using move constructor of " << packet.DebugString(); VLOG(4) << "Using move constructor of " << packet.DebugString();
holder_ = std::move(packet.holder_); holder_ = std::move(packet.holder_);
timestamp_ = packet.timestamp_; timestamp_ = packet.timestamp_;
packet.timestamp_ = Timestamp::Unset(); packet.timestamp_ = Timestamp::Unset();
} }
inline Packet& Packet::operator=(Packet&& packet) { inline Packet& Packet::operator=(Packet&& packet) {
VLOG(2) << "Using move assignment operator of " << packet.DebugString(); VLOG(4) << "Using move assignment operator of " << packet.DebugString();
if (this != &packet) { if (this != &packet) {
holder_ = std::move(packet.holder_); holder_ = std::move(packet.holder_);
timestamp_ = packet.timestamp_; timestamp_ = packet.timestamp_;
+1
View File
@@ -246,6 +246,7 @@ cc_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
":opencv_core", ":opencv_core",
"//mediapipe/framework:port",
"//third_party:opencv", "//third_party:opencv",
], ],
) )
+96 -4
View File
@@ -5,6 +5,98 @@
load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library", "py_proto_library") load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library", "py_proto_library")
def provided_args(**kwargs):
"""Returns the keyword arguments omitting None arguments."""
return {k: v for k, v in kwargs.items() if v != None}
def allowed_args(**kwargs):
"""Returns the keyword arguments allowed for proto_library().
Args:
**kwargs: the specified keyword arguments.
Returns:
the allowed keyword arguments.
"""
result = dict(kwargs)
result.pop("cc_api_version", None)
return result
# TODO: load this macro from a common helper file.
def mediapipe_proto_library(
name,
srcs,
deps = [],
visibility = None,
testonly = 0,
compatible_with = [],
def_proto = True,
def_cc_proto = True,
def_py_proto = True,
def_java_lite_proto = True,
def_portable_proto = True,
portable_deps = None):
"""Defines the proto_library targets needed for all mediapipe platforms.
Args:
name: the new proto_library target name.
srcs: the ".proto" source files to compile.
deps: the proto_library targets for all referenced protobufs.
portable_deps: the portable_proto_library targets for all referenced protobufs.
visibility: visibility of this target.
testonly: true means the proto can be used for testing only.
compatible_with: see go/target-constraints.
def_proto: define the proto_library target
def_cc_proto: define the cc_proto_library target
def_py_proto: define the py_proto_library target
def_java_lite_proto: define the java_lite_proto_library target
def_portable_proto: define the portable_proto_library target
"""
_ignore = [def_portable_proto, portable_deps]
# The proto_library targets for the compiled ".proto" source files.
proto_deps = [":" + name]
if def_proto:
native.proto_library(**allowed_args(**provided_args(
name = name,
srcs = srcs,
deps = deps,
visibility = visibility,
testonly = testonly,
cc_api_version = 2,
compatible_with = compatible_with,
)))
if def_cc_proto:
cc_deps = [dep.replace("_proto", "_cc_proto") for dep in deps]
mediapipe_cc_proto_library(**provided_args(
name = name.replace("_proto", "_cc_proto"),
srcs = srcs,
deps = proto_deps,
cc_deps = cc_deps,
visibility = visibility,
testonly = testonly,
))
if def_py_proto:
py_deps = [dep.replace("_proto", "_py_pb2") for dep in deps]
mediapipe_py_proto_library(**provided_args(
name = name.replace("_proto", "_py_pb2"),
srcs = srcs,
proto_deps = proto_deps,
py_proto_deps = py_deps,
visibility = visibility,
api_version = 2,
))
if def_java_lite_proto:
native.java_lite_proto_library(**provided_args(
name = name.replace("_proto", "_java_proto_lite"),
deps = proto_deps,
strict_deps = 0,
visibility = visibility,
))
def mediapipe_py_proto_library( def mediapipe_py_proto_library(
name, name,
srcs, srcs,
@@ -21,14 +113,14 @@ def mediapipe_py_proto_library(
py_proto_deps: a list of dependency labels for Bazel use; must be py_proto_library. py_proto_deps: a list of dependency labels for Bazel use; must be py_proto_library.
""" """
_ignore = [api_version, proto_deps] _ignore = [api_version, proto_deps]
py_proto_library( py_proto_library(**provided_args(
name = name, name = name,
srcs = srcs, srcs = srcs,
visibility = visibility, visibility = visibility,
default_runtime = "@com_google_protobuf//:protobuf_python", default_runtime = "@com_google_protobuf//:protobuf_python",
protoc = "@com_google_protobuf//:protoc", protoc = "@com_google_protobuf//:protoc",
deps = py_proto_deps + ["@com_google_protobuf//:protobuf_python"], deps = py_proto_deps + ["@com_google_protobuf//:protobuf_python"],
) ))
def mediapipe_cc_proto_library(name, srcs, visibility, deps = [], cc_deps = [], testonly = 0): def mediapipe_cc_proto_library(name, srcs, visibility, deps = [], cc_deps = [], testonly = 0):
"""Generate cc_proto_library for mediapipe open source version. """Generate cc_proto_library for mediapipe open source version.
@@ -41,7 +133,7 @@ def mediapipe_cc_proto_library(name, srcs, visibility, deps = [], cc_deps = [],
testonly: test only proto or not. testonly: test only proto or not.
""" """
_ignore = [deps] _ignore = [deps]
cc_proto_library( cc_proto_library(**provided_args(
name = name, name = name,
srcs = srcs, srcs = srcs,
visibility = visibility, visibility = visibility,
@@ -51,4 +143,4 @@ def mediapipe_cc_proto_library(name, srcs, visibility, deps = [], cc_deps = [],
protoc = "@com_google_protobuf//:protoc", protoc = "@com_google_protobuf//:protoc",
default_runtime = "@com_google_protobuf//:protobuf", default_runtime = "@com_google_protobuf//:protobuf",
alwayslink = 1, alwayslink = 1,
) ))

Some files were not shown because too many files have changed in this diff Show More