diff --git a/WORKSPACE b/WORKSPACE
index d3cc40fbe..146916c5c 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -157,6 +157,13 @@ http_archive(
urls = ["https://github.com/google/multichannel-audio-tools/archive/master.zip"],
)
+http_archive(
+ name = "pffft",
+ strip_prefix = "jpommier-pffft-7c3b5a7dc510",
+ urls = ["https://bitbucket.org/jpommier/pffft/get/7c3b5a7dc510.zip"],
+ build_file = "@//third_party:pffft.BUILD",
+)
+
# sentencepiece
http_archive(
name = "com_google_sentencepiece",
diff --git a/docs/solutions/pose.md b/docs/solutions/pose.md
index 8c57c033e..905800228 100644
--- a/docs/solutions/pose.md
+++ b/docs/solutions/pose.md
@@ -217,7 +217,7 @@ A list of pose landmarks. Each landmark consists of the following:
*Fig 5. Example of MediaPipe Pose real-world 3D coordinates.* |
:-----------------------------------------------------------: |
- |
+ |
Another list of pose landmarks in world coordinates. Each landmark consists of
the following:
@@ -238,7 +238,7 @@ for usage details.
*Fig 6. Example of MediaPipe Pose segmentation mask.* |
:---------------------------------------------------: |
- |
+ |
### Python Solution API
diff --git a/docs/solutions/selfie_segmentation.md b/docs/solutions/selfie_segmentation.md
index 2cb155fb3..d8b17487c 100644
--- a/docs/solutions/selfie_segmentation.md
+++ b/docs/solutions/selfie_segmentation.md
@@ -22,7 +22,7 @@ nav_order: 7
*Fig 1. Example of MediaPipe Selfie Segmentation.* |
:------------------------------------------------: |
- |
+ |
MediaPipe Selfie Segmentation segments the prominent humans in the scene. It can
run in real-time on both smartphones and laptops. The intended use cases include
diff --git a/mediapipe/calculators/core/BUILD b/mediapipe/calculators/core/BUILD
index b28a3573a..26ada44bf 100644
--- a/mediapipe/calculators/core/BUILD
+++ b/mediapipe/calculators/core/BUILD
@@ -1294,8 +1294,8 @@ cc_library(
deps = [
":get_vector_item_calculator_cc_proto",
"//mediapipe/framework:calculator_framework",
- "//mediapipe/framework:packet",
"//mediapipe/framework/api2:node",
+ "//mediapipe/framework/api2:packet",
"//mediapipe/framework/api2:port",
"//mediapipe/framework/formats:classification_cc_proto",
"//mediapipe/framework/formats:landmark_cc_proto",
@@ -1319,6 +1319,32 @@ cc_test(
],
)
+cc_library(
+ name = "vector_indices_calculator",
+ srcs = ["vector_indices_calculator.cc"],
+ hdrs = ["vector_indices_calculator.h"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//mediapipe/framework:calculator_framework",
+ "//mediapipe/framework/api2:node",
+ "//mediapipe/framework/formats:landmark_cc_proto",
+ "//mediapipe/framework/port:status",
+ ],
+ alwayslink = 1,
+)
+
+cc_test(
+ name = "vector_indices_calculator_test",
+ srcs = ["vector_indices_calculator_test.cc"],
+ deps = [
+ ":vector_indices_calculator",
+ "//mediapipe/framework:calculator_framework",
+ "//mediapipe/framework:calculator_runner",
+ "//mediapipe/framework/port:gtest_main",
+ "//mediapipe/framework/port:parse_text_proto",
+ ],
+)
+
cc_library(
name = "vector_size_calculator",
srcs = ["vector_size_calculator.cc"],
diff --git a/mediapipe/calculators/core/end_loop_calculator.cc b/mediapipe/calculators/core/end_loop_calculator.cc
index d21bc03a4..b321f4275 100644
--- a/mediapipe/calculators/core/end_loop_calculator.cc
+++ b/mediapipe/calculators/core/end_loop_calculator.cc
@@ -40,6 +40,9 @@ REGISTER_CALCULATOR(EndLoopNormalizedLandmarkListVectorCalculator);
typedef EndLoopCalculator> EndLoopBooleanCalculator;
REGISTER_CALCULATOR(EndLoopBooleanCalculator);
+typedef EndLoopCalculator> EndLoopFloatCalculator;
+REGISTER_CALCULATOR(EndLoopFloatCalculator);
+
typedef EndLoopCalculator>
EndLoopRenderDataCalculator;
REGISTER_CALCULATOR(EndLoopRenderDataCalculator);
diff --git a/mediapipe/calculators/core/get_vector_item_calculator.cc b/mediapipe/calculators/core/get_vector_item_calculator.cc
index 56a2f3304..51fb46b98 100644
--- a/mediapipe/calculators/core/get_vector_item_calculator.cc
+++ b/mediapipe/calculators/core/get_vector_item_calculator.cc
@@ -24,6 +24,10 @@ using GetLandmarkListVectorItemCalculator =
GetVectorItemCalculator;
REGISTER_CALCULATOR(GetLandmarkListVectorItemCalculator);
+using GetNormalizedLandmarkListVectorItemCalculator =
+ GetVectorItemCalculator;
+REGISTER_CALCULATOR(GetNormalizedLandmarkListVectorItemCalculator);
+
using GetClassificationListVectorItemCalculator =
GetVectorItemCalculator;
REGISTER_CALCULATOR(GetClassificationListVectorItemCalculator);
diff --git a/mediapipe/calculators/core/get_vector_item_calculator.h b/mediapipe/calculators/core/get_vector_item_calculator.h
index be89aa3a3..dc98ccfe7 100644
--- a/mediapipe/calculators/core/get_vector_item_calculator.h
+++ b/mediapipe/calculators/core/get_vector_item_calculator.h
@@ -19,6 +19,7 @@
#include "mediapipe/calculators/core/get_vector_item_calculator.pb.h"
#include "mediapipe/framework/api2/node.h"
+#include "mediapipe/framework/api2/packet.h"
#include "mediapipe/framework/api2/port.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/port/ret_check.h"
@@ -58,7 +59,7 @@ template
class GetVectorItemCalculator : public Node {
public:
static constexpr Input> kIn{"VECTOR"};
- static constexpr Input::Optional kIdx{"INDEX"};
+ static constexpr Input>::Optional kIdx{"INDEX"};
static constexpr Output kOut{"ITEM"};
MEDIAPIPE_NODE_CONTRACT(kIn, kIdx, kOut);
@@ -80,7 +81,9 @@ class GetVectorItemCalculator : public Node {
int idx = 0;
if (kIdx(cc).IsConnected() && !kIdx(cc).IsEmpty()) {
- idx = kIdx(cc).Get();
+ idx = kIdx(cc).Visit(
+ [](uint64_t idx_uint64_t) { return static_cast(idx_uint64_t); },
+ [](int idx_int) { return idx_int; });
} else if (options.has_item_index()) {
idx = options.item_index();
} else {
diff --git a/mediapipe/calculators/core/get_vector_item_calculator_test.cc b/mediapipe/calculators/core/get_vector_item_calculator_test.cc
index f2f788382..c148aa9d1 100644
--- a/mediapipe/calculators/core/get_vector_item_calculator_test.cc
+++ b/mediapipe/calculators/core/get_vector_item_calculator_test.cc
@@ -227,4 +227,15 @@ TEST(TestGetIntVectorItemCalculatorTest, IndexOptionsTwoTimestamps) {
testing::ElementsAre(TimestampValue(1), TimestampValue(2)));
}
+TEST(TestGetIntVectorItemCalculatorTest, IndexUint64) {
+ CalculatorRunner runner = MakeRunnerWithStream();
+ const std::vector inputs = {1, 2, 3};
+ const uint64_t index = 1;
+ AddInputVector(runner, inputs, 1);
+ AddInputIndex(runner, index, 1);
+ MP_ASSERT_OK(runner.Run());
+ const std::vector& outputs = runner.Outputs().Tag("ITEM").packets;
+ EXPECT_THAT(outputs, testing::ElementsAre(IntPacket(inputs[index])));
+}
+
} // namespace mediapipe
diff --git a/mediapipe/calculators/core/vector_indices_calculator.cc b/mediapipe/calculators/core/vector_indices_calculator.cc
new file mode 100644
index 000000000..56baa1376
--- /dev/null
+++ b/mediapipe/calculators/core/vector_indices_calculator.cc
@@ -0,0 +1,33 @@
+// Copyright 2022 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/core/vector_indices_calculator.h"
+
+#include "mediapipe/framework/formats/landmark.pb.h"
+
+namespace mediapipe {
+namespace api2 {
+
+using IntVectorIndicesCalculator = VectorIndicesCalculator;
+REGISTER_CALCULATOR(IntVectorIndicesCalculator);
+
+using Uint64tVectorIndicesCalculator = VectorIndicesCalculator;
+REGISTER_CALCULATOR(Uint64tVectorIndicesCalculator);
+
+using NormalizedLandmarkListVectorIndicesCalculator =
+ VectorIndicesCalculator;
+REGISTER_CALCULATOR(NormalizedLandmarkListVectorIndicesCalculator);
+
+} // namespace api2
+} // namespace mediapipe
diff --git a/mediapipe/calculators/core/vector_indices_calculator.h b/mediapipe/calculators/core/vector_indices_calculator.h
new file mode 100644
index 000000000..ec67300de
--- /dev/null
+++ b/mediapipe/calculators/core/vector_indices_calculator.h
@@ -0,0 +1,65 @@
+// Copyright 2022 The MediaPipe Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MEDIAPIPE_CALCULATORS_CORE_VECTOR_INDICES_CALCULATOR_H_
+#define MEDIAPIPE_CALCULATORS_CORE_VECTOR_INDICES_CALCULATOR_H_
+
+#include
+
+#include "mediapipe/framework/api2/node.h"
+#include "mediapipe/framework/calculator_framework.h"
+#include "mediapipe/framework/port/status.h"
+
+namespace mediapipe {
+namespace api2 {
+// Calculator that takes a vector and constructs an index range vector based on
+// the size of the input vector.
+//
+// Inputs:
+// VECTOR - std::vector
+// Vector whose range of indices to return.
+//
+// Outputs:
+// INDICES - std::vector
+// Indices vector of the input vector.
+//
+// Example config:
+// node {
+// calculator: "{SpecificType}VectorIndicesCalculator"
+// input_stream: "VECTOR:vector"
+// output_stream: "INDICES:indices"
+// }
+//
+template
+class VectorIndicesCalculator : public Node {
+ public:
+ static constexpr Input> kVector{"VECTOR"};
+ static constexpr Output> kRange{"INDICES"};
+
+ MEDIAPIPE_NODE_CONTRACT(kVector, kRange);
+
+ absl::Status Process(CalculatorContext* cc) final {
+ // Get the size of the input vector.
+ const int vector_size = kVector(cc).Get().size();
+ std::vector out_idxs(vector_size);
+ std::iota(out_idxs.begin(), out_idxs.end(), 0);
+ kRange(cc).Send(out_idxs);
+ return absl::OkStatus();
+ }
+};
+
+} // namespace api2
+} // namespace mediapipe
+
+#endif // MEDIAPIPE_CALCULATORS_CORE_VECTOR_INDICES_CALCULATOR_H_
diff --git a/mediapipe/calculators/core/vector_indices_calculator_test.cc b/mediapipe/calculators/core/vector_indices_calculator_test.cc
new file mode 100644
index 000000000..ff54f1f4a
--- /dev/null
+++ b/mediapipe/calculators/core/vector_indices_calculator_test.cc
@@ -0,0 +1,87 @@
+// Copyright 2022 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/core/vector_indices_calculator.h"
+
+#include
+#include
+#include
+
+#include "mediapipe/framework/calculator_framework.h"
+#include "mediapipe/framework/calculator_runner.h"
+#include "mediapipe/framework/port/gmock.h"
+#include "mediapipe/framework/port/status_matchers.h"
+
+namespace mediapipe {
+
+namespace {
+
+using ::testing::TestParamInfo;
+using ::testing::TestWithParam;
+using ::testing::Values;
+
+template
+void AddInputVector(CalculatorRunner& runner, const std::vector& inputs,
+ int timestamp) {
+ runner.MutableInputs()->Tag("VECTOR").packets.push_back(
+ MakePacket>(inputs).At(Timestamp(timestamp)));
+}
+
+template
+struct TestParams {
+ const std::string test_name;
+ const std::vector inputs;
+ const int timestamp;
+ const std::vector expected_indices;
+};
+
+class IntVectorIndicesCalculatorTest
+ : public testing::TestWithParam> {};
+
+TEST_P(IntVectorIndicesCalculatorTest, Succeeds) {
+ CalculatorRunner runner = CalculatorRunner(R"(
+ calculator: "IntVectorIndicesCalculator"
+ input_stream: "VECTOR:vector_stream"
+ output_stream: "INDICES:indices_stream"
+ )");
+ const std::vector& inputs = GetParam().inputs;
+ std::vector expected_indices(inputs.size());
+ AddInputVector(runner, inputs, GetParam().timestamp);
+ MP_ASSERT_OK(runner.Run());
+ const std::vector& outputs = runner.Outputs().Tag("INDICES").packets;
+ EXPECT_EQ(1, outputs.size());
+ EXPECT_THAT(outputs[0].Get>(),
+ testing::ElementsAreArray(GetParam().expected_indices));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ IntVectorIndicesCalculatorTest, IntVectorIndicesCalculatorTest,
+ Values(TestParams{
+ /* test_name= */ "IntVectorIndices",
+ /* inputs= */ {1, 2, 3},
+ /* timestamp= */ 1,
+ /* expected_indices= */ {0, 1, 2},
+ },
+ TestParams{
+ /* test_name= */ "EmptyVector",
+ /* inputs= */ {},
+ /* timestamp= */ 1,
+ /* expected_indices= */ {},
+ }),
+ [](const TestParamInfo& info) {
+ return info.param.test_name;
+ });
+
+} // namespace
+} // namespace mediapipe
diff --git a/mediapipe/calculators/tensor/BUILD b/mediapipe/calculators/tensor/BUILD
index c378df7d0..93f2dbd06 100644
--- a/mediapipe/calculators/tensor/BUILD
+++ b/mediapipe/calculators/tensor/BUILD
@@ -55,6 +55,14 @@ mediapipe_proto_library(
cc_library(
name = "audio_to_tensor_calculator",
srcs = ["audio_to_tensor_calculator.cc"],
+ copts = select({
+ # b/215212850
+ "//mediapipe:apple": [
+ "-x objective-c++",
+ "-fobjc-arc",
+ ],
+ "//conditions:default": [],
+ }),
visibility = [
"//mediapipe/framework:mediapipe_internal",
],
@@ -67,13 +75,16 @@ cc_library(
"//mediapipe/framework/formats:matrix",
"//mediapipe/framework/formats:tensor",
"//mediapipe/framework/formats:time_series_header_cc_proto",
+ "//mediapipe/framework/port:ret_check",
"//mediapipe/util:time_series_util",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings:str_format",
"@com_google_audio_tools//audio/dsp:resampler_q",
+ "@com_google_audio_tools//audio/dsp:window_functions",
"@org_tensorflow//tensorflow/lite/c:common",
+ "@pffft",
],
alwayslink = 1,
)
@@ -83,6 +94,7 @@ cc_test(
srcs = ["audio_to_tensor_calculator_test.cc"],
deps = [
":audio_to_tensor_calculator",
+ ":audio_to_tensor_calculator_cc_proto",
"//mediapipe/framework:calculator_cc_proto",
"//mediapipe/framework:calculator_framework",
"//mediapipe/framework:timestamp",
@@ -97,6 +109,58 @@ cc_test(
],
)
+mediapipe_proto_library(
+ name = "feedback_tensors_calculator_proto",
+ srcs = ["feedback_tensors_calculator.proto"],
+ visibility = [
+ "//mediapipe/framework:mediapipe_internal",
+ ],
+ deps = [
+ "//mediapipe/framework:calculator_options_proto",
+ "//mediapipe/framework:calculator_proto",
+ ],
+)
+
+cc_library(
+ name = "feedback_tensors_calculator",
+ srcs = ["feedback_tensors_calculator.cc"],
+ copts = select({
+ # b/215212850
+ "//mediapipe:apple": [
+ "-x objective-c++",
+ "-fobjc-arc",
+ ],
+ "//conditions:default": [],
+ }),
+ visibility = [
+ "//mediapipe/framework:mediapipe_internal",
+ ],
+ deps = [
+ ":feedback_tensors_calculator_cc_proto",
+ "//mediapipe/framework:calculator_framework",
+ "//mediapipe/framework/api2:node",
+ "//mediapipe/framework/formats:tensor",
+ "@com_google_absl//absl/status",
+ ],
+ alwayslink = 1,
+)
+
+cc_test(
+ name = "feedback_tensors_calculator_test",
+ srcs = ["feedback_tensors_calculator_test.cc"],
+ deps = [
+ ":feedback_tensors_calculator",
+ ":feedback_tensors_calculator_cc_proto",
+ "//mediapipe/framework:calculator_cc_proto",
+ "//mediapipe/framework:calculator_framework",
+ "//mediapipe/framework:timestamp",
+ "//mediapipe/framework/formats:tensor",
+ "//mediapipe/framework/port:gtest_main",
+ "//mediapipe/framework/port:parse_text_proto",
+ "@org_tensorflow//tensorflow/lite/c:common",
+ ],
+)
+
mediapipe_proto_library(
name = "inference_calculator_proto",
srcs = ["inference_calculator.proto"],
@@ -346,6 +410,10 @@ cc_library(
}),
)
+# This target provides the InferenceCalculator and a default set of implementations tailored for the
+# current build platforms. More implementations can be added as separate dependencies to a client;
+# for clients that want a narrower set of implementations than the default should see the comment on
+# inference_calculator_interface.
cc_library(
name = "inference_calculator",
visibility = ["//visibility:public"],
diff --git a/mediapipe/calculators/tensor/audio_to_tensor_calculator.cc b/mediapipe/calculators/tensor/audio_to_tensor_calculator.cc
index 474d6cf17..59c129191 100644
--- a/mediapipe/calculators/tensor/audio_to_tensor_calculator.cc
+++ b/mediapipe/calculators/tensor/audio_to_tensor_calculator.cc
@@ -12,9 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include
-
#include
+#include
#include
#include
#include
@@ -26,6 +25,7 @@
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "audio/dsp/resampler_q.h"
+#include "audio/dsp/window_functions.h"
#include "mediapipe/calculators/tensor/audio_to_tensor_calculator.pb.h"
#include "mediapipe/framework/api2/node.h"
#include "mediapipe/framework/api2/packet.h"
@@ -34,19 +34,60 @@
#include "mediapipe/framework/formats/matrix.h"
#include "mediapipe/framework/formats/tensor.h"
#include "mediapipe/framework/formats/time_series_header.pb.h"
+#include "mediapipe/framework/port/ret_check.h"
#include "mediapipe/util/time_series_util.h"
+#include "pffft.h"
namespace mediapipe {
namespace api2 {
+namespace {
+
+using Options = ::mediapipe::AudioToTensorCalculatorOptions;
+using FlushMode = Options::FlushMode;
+
+std::vector HannWindow(int window_size, bool sqrt_hann) {
+ std::vector hann_window(window_size);
+ audio_dsp::HannWindow().GetPeriodicSamples(window_size, &hann_window);
+ if (sqrt_hann) {
+ absl::c_transform(hann_window, hann_window.begin(),
+ [](double x) { return std::sqrt(x); });
+ }
+ return hann_window;
+}
+
+// PFFFT only supports transforms for inputs of length N of the form
+// N = (2^a)*(3^b)*(5^c) where b >=0 and c >= 0 and a >= 5 for the real FFT.
+bool IsValidFftSize(int size) {
+ if (size <= 0) {
+ return false;
+ }
+ constexpr int kFactors[] = {2, 3, 5};
+ int factorization[] = {0, 0, 0};
+ int n = static_cast(size);
+ for (int i = 0; i < 3; ++i) {
+ while (n % kFactors[i] == 0) {
+ n = n / kFactors[i];
+ ++factorization[i];
+ }
+ }
+ return factorization[0] >= 5 && n == 1;
+}
+
+} // namespace
// Converts audio buffers into tensors, possibly with resampling, buffering
// and framing, according to specified inputs and options. All input audio
// buffers will be first resampled from the input sample rate to the target
// sample rate if they are not equal. The resampled audio data (with the
// buffered samples from the previous runs in the streaming mode) will be broken
-// into fixed-sized, possibly overlapping frames. Finally, all frames will be
-// converted to and outputted as MediaPipe Tensors. The last output tensor will
-// be zero-padding if the remaining samples are insufficient.
+// into fixed-sized, possibly overlapping frames. If the calculator is not asked
+// to perform fft (the fft_size is not set in the calculator options), all
+// frames will be converted to and outputted as MediaPipe Tensors. The last
+// output tensor will be zero-padding if the remaining samples are insufficient.
+// Otherwise, when the fft_size is set and valid, the calculator will perform
+// fft on the fixed-sized audio frames, the complex DFT results will be
+// converted to and outputted as 2D MediaPipe float Tensors where the first
+// rows are the DFT real parts and the second rows are the DFT imagery parts.
//
// This calculator assumes that the input timestamps refer to the first
// sample in each Matrix. The output timestamps follow this same convention.
@@ -86,11 +127,15 @@ namespace api2 {
// Outputs:
// TENSORS - std::vector
// Vector containing a single Tensor that represents a fix-sized audio
-// frame.
+// frame or the complex DFT results.
// TIMESTAMPS - std::vector @Optional
// Vector containing the output timestamps emitted by the current Process()
// invocation. In the non-streaming mode, the vector contains all of the
// output timestamps for an input audio buffer.
+// DC_AND_NYQUIST - std::pair @Optional.
+// A pair of dc component and nyquest component. Only can be connected when
+// the calculator performs fft (the fft_size is set in the calculator
+// options).
//
// Example:
// node {
@@ -116,12 +161,14 @@ class AudioToTensorCalculator : public Node {
// such as sample rate.
static constexpr Input::Optional kAudioSampleRateIn{"SAMPLE_RATE"};
static constexpr Output> kTensorsOut{"TENSORS"};
+ static constexpr Output>::Optional kDcAndNyquistOut{
+ "DC_AND_NYQUIST"};
// A vector of the output timestamps emitted by the current Process()
// invocation. The packet timestamp is the last emitted timestamp.
static constexpr Output>::Optional kTimestampsOut{
"TIMESTAMPS"};
MEDIAPIPE_NODE_CONTRACT(kAudioIn, kAudioSampleRateIn, kTensorsOut,
- kTimestampsOut);
+ kDcAndNyquistOut, kTimestampsOut);
static absl::Status UpdateContract(CalculatorContract* cc);
absl::Status Open(CalculatorContext* cc);
@@ -138,6 +185,9 @@ class AudioToTensorCalculator : public Node {
int frame_step_;
bool stream_mode_;
bool check_inconsistent_timestamps_;
+ int padding_samples_before_;
+ int padding_samples_after_;
+ FlushMode flush_mode_;
Timestamp initial_timestamp_ = Timestamp::Unstarted();
int64 cumulative_input_samples_ = 0;
Timestamp next_output_timestamp_ = Timestamp::Unstarted();
@@ -151,22 +201,33 @@ class AudioToTensorCalculator : public Node {
Matrix sample_buffer_;
int processed_buffer_cols_ = 0;
+ // The internal state of the FFT library.
+ PFFFT_Setup* fft_state_ = nullptr;
+ int fft_size_ = 0;
+ std::vector fft_window_;
+ std::vector> fft_input_buffer_;
+ // pffft requires memory to work with to avoid using the stack.
+ std::vector> fft_workplace_;
+ std::vector> fft_output_;
+
absl::Status ProcessStreamingData(CalculatorContext* cc, const Matrix& input);
absl::Status ProcessNonStreamingData(CalculatorContext* cc,
const Matrix& input);
absl::Status SetupStreamingResampler(double input_sample_rate_);
void AppendToSampleBuffer(Matrix buffer_to_append);
+ void AppendZerosToSampleBuffer(int num_samples);
absl::StatusOr> ConvertToTensor(
- const Matrix& frame_to_convert);
- absl::Status OutputTensors(const Matrix& buffer, bool should_flush,
+ const Matrix& block, std::vector tensor_dims);
+ absl::Status OutputTensor(const Matrix& block, Timestamp timestamp,
+ CalculatorContext* cc);
+ absl::Status ProcessBuffer(const Matrix& buffer, bool should_flush,
CalculatorContext* cc);
};
absl::Status AudioToTensorCalculator::UpdateContract(CalculatorContract* cc) {
- const auto& options =
- cc->Options();
+ const auto& options = cc->Options();
if (!options.has_num_channels() || !options.has_num_samples() ||
!options.has_target_sample_rate()) {
return absl::InvalidArgumentError(
@@ -174,13 +235,21 @@ absl::Status AudioToTensorCalculator::UpdateContract(CalculatorContract* cc) {
"`num_channels`, `num_samples`, and `target_sample_rate`.");
}
if (options.stream_mode()) {
- // Explicitly disables tiemstamp offset to disallow the timestamp bound
+ // Explicitly disables timestamp offset to disallow the timestamp bound
// from the input streams to be propagated to the output streams.
// In the streaming mode, the output timestamp bound is based on
// next_output_timestamp_, which can be smaller than the current input
// timestamps.
cc->SetTimestampOffset(TimestampDiff::Unset());
}
+ if (options.padding_samples_before() < 0 ||
+ options.padding_samples_after() < 0) {
+ return absl::InvalidArgumentError("Negative zero padding unsupported");
+ }
+ if (options.flush_mode() != Options::ENTIRE_TAIL_AT_TIMESTAMP_MAX &&
+ options.flush_mode() != Options::PROCEED_AS_USUAL) {
+ return absl::InvalidArgumentError("Unsupported flush mode");
+ }
return absl::OkStatus();
}
@@ -202,6 +271,9 @@ absl::Status AudioToTensorCalculator::Open(CalculatorContext* cc) {
check_inconsistent_timestamps_ = options.check_inconsistent_timestamps();
sample_buffer_.resize(num_channels_, Eigen::NoChange);
}
+ padding_samples_before_ = options.padding_samples_before();
+ padding_samples_after_ = options.padding_samples_after();
+ flush_mode_ = options.flush_mode();
RET_CHECK(kAudioSampleRateIn(cc).IsConnected() ^
!kAudioIn(cc).Header().IsEmpty())
@@ -217,6 +289,25 @@ absl::Status AudioToTensorCalculator::Open(CalculatorContext* cc) {
source_sample_rate_ = input_header.sample_rate();
}
}
+ AppendZerosToSampleBuffer(padding_samples_before_);
+ if (options.has_fft_size()) {
+ RET_CHECK(IsValidFftSize(options.fft_size()))
+ << "FFT size must be of the form fft_size = (2^a)*(3^b)*(5^c) where b "
+ ">=0 and c >= 0 and a >= 5, the requested fft size is "
+ << options.fft_size();
+ RET_CHECK_EQ(1, num_channels_)
+ << "Currently only support applying FFT on mono channel.";
+ fft_size_ = options.fft_size();
+ fft_state_ = pffft_new_setup(fft_size_, PFFFT_REAL);
+ fft_window_ = HannWindow(fft_size_, /* sqrt_hann = */ false);
+ fft_input_buffer_.resize(fft_size_);
+ fft_workplace_.resize(fft_size_);
+ fft_output_.resize(fft_size_);
+ } else {
+ RET_CHECK(!kDcAndNyquistOut(cc).IsConnected())
+ << "The DC_AND_NYQUIST output stream can only be connected when the "
+ "calculator outputs fft tensors";
+ }
return absl::OkStatus();
}
@@ -262,7 +353,12 @@ absl::Status AudioToTensorCalculator::Close(CalculatorContext* cc) {
resampler_->Flush(&resampled_buffer);
AppendToSampleBuffer(std::move(resampled_buffer));
}
- return OutputTensors(sample_buffer_, /*should_flush=*/true, cc);
+ AppendZerosToSampleBuffer(padding_samples_after_);
+ MP_RETURN_IF_ERROR(ProcessBuffer(sample_buffer_, /*should_flush=*/true, cc));
+ if (fft_state_) {
+ pffft_destroy_setup(fft_state_);
+ }
+ return absl::OkStatus();
}
absl::Status AudioToTensorCalculator::ProcessStreamingData(
@@ -303,7 +399,7 @@ absl::Status AudioToTensorCalculator::ProcessStreamingData(
}
}
- MP_RETURN_IF_ERROR(OutputTensors(sample_buffer_, /*should_flush=*/false, cc));
+ MP_RETURN_IF_ERROR(ProcessBuffer(sample_buffer_, /*should_flush=*/false, cc));
// Removes the processed samples from the global sample buffer.
sample_buffer_ = Matrix(sample_buffer_.rightCols(sample_buffer_.cols() -
processed_buffer_cols_ - 1));
@@ -323,9 +419,9 @@ absl::Status AudioToTensorCalculator::ProcessNonStreamingData(
input_frame);
Eigen::Map matrix_mapping(resampled.data(), num_channels_,
resampled.size() / num_channels_);
- return OutputTensors(matrix_mapping, /*should_flush=*/true, cc);
+ return ProcessBuffer(matrix_mapping, /*should_flush=*/true, cc);
}
- return OutputTensors(input_frame, /*should_flush=*/true, cc);
+ return ProcessBuffer(input_frame, /*should_flush=*/true, cc);
}
absl::Status AudioToTensorCalculator::SetupStreamingResampler(
@@ -344,6 +440,16 @@ absl::Status AudioToTensorCalculator::SetupStreamingResampler(
return absl::OkStatus();
}
+void AudioToTensorCalculator::AppendZerosToSampleBuffer(int num_samples) {
+ CHECK_GE(num_samples, 0); // Ensured by `UpdateContract`.
+ if (num_samples == 0) {
+ return;
+ }
+ sample_buffer_.conservativeResize(Eigen::NoChange,
+ sample_buffer_.cols() + num_samples);
+ sample_buffer_.rightCols(num_samples).setZero();
+}
+
void AudioToTensorCalculator::AppendToSampleBuffer(Matrix buffer_to_append) {
sample_buffer_.conservativeResize(
Eigen::NoChange, sample_buffer_.cols() + buffer_to_append.cols());
@@ -351,49 +457,89 @@ void AudioToTensorCalculator::AppendToSampleBuffer(Matrix buffer_to_append) {
}
absl::StatusOr> AudioToTensorCalculator::ConvertToTensor(
- const Matrix& frame_to_convert) {
- Tensor tensor(Tensor::ElementType::kFloat32,
- Tensor::Shape({num_channels_, num_samples_}));
+ const Matrix& block, std::vector tensor_dims) {
+ Tensor tensor(Tensor::ElementType::kFloat32, Tensor::Shape(tensor_dims));
auto buffer_view = tensor.GetCpuWriteView();
- if (frame_to_convert.size() < num_channels_ * num_samples_) {
+ int total_size = 1;
+ for (int dim : tensor_dims) {
+ total_size *= dim;
+ }
+ if (block.size() < total_size) {
std::memset(buffer_view.buffer(), 0, tensor.bytes());
}
- std::memcpy(buffer_view.buffer(), frame_to_convert.data(),
- frame_to_convert.size() * sizeof(float));
+ std::memcpy(buffer_view.buffer(), block.data(),
+ block.size() * sizeof(float));
std::vector tensor_vector;
tensor_vector.push_back(std::move(tensor));
return tensor_vector;
}
-absl::Status AudioToTensorCalculator::OutputTensors(const Matrix& buffer,
+absl::Status AudioToTensorCalculator::OutputTensor(const Matrix& block,
+ Timestamp timestamp,
+ CalculatorContext* cc) {
+ std::vector output_tensor;
+ if (fft_state_) {
+ Eigen::VectorXf time_series_data =
+ Eigen::VectorXf::Map(block.data(), block.size());
+ // Window on input audio prior to FFT.
+ std::transform(time_series_data.begin(), time_series_data.end(),
+ fft_window_.begin(), fft_input_buffer_.begin(),
+ std::multiplies());
+ pffft_transform_ordered(fft_state_, fft_input_buffer_.data(),
+ fft_output_.data(), fft_workplace_.data(),
+ PFFFT_FORWARD);
+ if (kDcAndNyquistOut(cc).IsConnected()) {
+ kDcAndNyquistOut(cc).Send(std::make_pair(fft_output_[0], fft_output_[1]),
+ timestamp);
+ }
+ Matrix fft_output_matrix =
+ Eigen::Map(fft_output_.data() + 2, 1, fft_size_ - 2);
+ fft_output_matrix.conservativeResize(Eigen::NoChange, fft_size_);
+ // The last two elements are the DFT Nyquist values.
+ fft_output_matrix(fft_size_ - 2) = fft_output_[1]; // Nyquist real part
+ fft_output_matrix(fft_size_ - 1) = 0.0f; // Nyquist imagery part
+ ASSIGN_OR_RETURN(output_tensor,
+ ConvertToTensor(fft_output_matrix, {2, fft_size_ / 2}));
+ } else {
+ ASSIGN_OR_RETURN(output_tensor,
+ ConvertToTensor(block, {num_channels_, num_samples_}));
+ }
+ kTensorsOut(cc).Send(std::move(output_tensor), timestamp);
+ return absl::OkStatus();
+}
+
+absl::Status AudioToTensorCalculator::ProcessBuffer(const Matrix& buffer,
bool should_flush,
CalculatorContext* cc) {
+ const bool should_flush_at_timestamp_max =
+ stream_mode_ && should_flush &&
+ flush_mode_ == Options::ENTIRE_TAIL_AT_TIMESTAMP_MAX;
int next_frame_first_col = 0;
std::vector timestamps;
- while ((!stream_mode_ || !should_flush) &&
- next_frame_first_col + num_samples_ <= buffer.cols()) {
- ASSIGN_OR_RETURN(auto output_tensor, ConvertToTensor(buffer.block(
- 0, next_frame_first_col,
- num_channels_, num_samples_)));
- kTensorsOut(cc).Send(std::move(output_tensor), next_output_timestamp_);
- timestamps.push_back(next_output_timestamp_);
- next_output_timestamp_ += round(frame_step_ / target_sample_rate_ *
- Timestamp::kTimestampUnitsPerSecond);
- next_frame_first_col += frame_step_;
+ if (!should_flush_at_timestamp_max) {
+ while (next_frame_first_col + num_samples_ <= buffer.cols()) {
+ MP_RETURN_IF_ERROR(OutputTensor(
+ buffer.block(0, next_frame_first_col, num_channels_, num_samples_),
+ next_output_timestamp_, cc));
+ timestamps.push_back(next_output_timestamp_);
+ next_output_timestamp_ += round(frame_step_ / target_sample_rate_ *
+ Timestamp::kTimestampUnitsPerSecond);
+ next_frame_first_col += frame_step_;
+ }
}
if (should_flush && next_frame_first_col < buffer.cols()) {
- ASSIGN_OR_RETURN(auto output_tensor,
- ConvertToTensor(buffer.block(
- 0, next_frame_first_col, num_channels_,
- std::min(num_samples_,
- (int)buffer.cols() - next_frame_first_col))));
// In the streaming mode, the flush happens in Close() and a packet at
// Timestamp::Max() will be emitted. In the non-streaming mode, each
// Process() invocation will process the entire buffer completely.
- Timestamp timestamp =
- stream_mode_ ? Timestamp::Max() : next_output_timestamp_;
+ Timestamp timestamp = should_flush_at_timestamp_max
+ ? Timestamp::Max()
+ : next_output_timestamp_;
+ MP_RETURN_IF_ERROR(OutputTensor(
+ buffer.block(
+ 0, next_frame_first_col, num_channels_,
+ std::min(num_samples_, (int)buffer.cols() - next_frame_first_col)),
+ timestamp, cc));
timestamps.push_back(timestamp);
- kTensorsOut(cc).Send(std::move(output_tensor), timestamp);
}
if (kTimestampsOut(cc).IsConnected()) {
Timestamp timestamp = timestamps.back();
diff --git a/mediapipe/calculators/tensor/audio_to_tensor_calculator.proto b/mediapipe/calculators/tensor/audio_to_tensor_calculator.proto
index 2090fbb81..cff6b2878 100644
--- a/mediapipe/calculators/tensor/audio_to_tensor_calculator.proto
+++ b/mediapipe/calculators/tensor/audio_to_tensor_calculator.proto
@@ -44,4 +44,28 @@ message AudioToTensorCalculatorOptions {
// Set to false to disable checks for jitter in timestamp values. Useful with
// live audio input.
optional bool check_inconsistent_timestamps = 6 [default = true];
+
+ // Size of the fft in number of bins. If set, the calculator outputs fft
+ // tensors.
+ optional int64 fft_size = 7;
+
+ // The amount of padding samples to add before the audio after resampling.
+ // Note that the timestamps shift. Currently, only zero padding is supported.
+ optional int64 padding_samples_before = 8;
+
+ // The amount of padding samples to add after the audio after resampling.
+ // Currently, only zero padding is supported.
+ optional int64 padding_samples_after = 9;
+
+ // Determines the "flushing" behavior in stream mode.
+ enum FlushMode {
+ // Unspecified (causes an error). Won't be used because of the default.
+ NONE = 0;
+ // Emit a packet with the entire remainder at `Timestamp::Max`.
+ ENTIRE_TAIL_AT_TIMESTAMP_MAX = 1;
+ // Continue emitting framed packets with relevant timestamps.
+ PROCEED_AS_USUAL = 2;
+ }
+
+ optional FlushMode flush_mode = 10 [default = ENTIRE_TAIL_AT_TIMESTAMP_MAX];
}
diff --git a/mediapipe/calculators/tensor/audio_to_tensor_calculator_test.cc b/mediapipe/calculators/tensor/audio_to_tensor_calculator_test.cc
index c2062134d..60fcfcd82 100644
--- a/mediapipe/calculators/tensor/audio_to_tensor_calculator_test.cc
+++ b/mediapipe/calculators/tensor/audio_to_tensor_calculator_test.cc
@@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include
#include
#include
#include
#include "absl/strings/substitute.h"
#include "audio/dsp/resampler_q.h"
+#include "mediapipe/calculators/tensor/audio_to_tensor_calculator.pb.h"
#include "mediapipe/framework/api2/packet.h"
#include "mediapipe/framework/calculator.pb.h"
#include "mediapipe/framework/calculator_framework.h"
@@ -32,6 +32,14 @@
namespace mediapipe {
namespace {
+using ::testing::Not;
+using Options = ::mediapipe::AudioToTensorCalculatorOptions;
+using FlushMode = Options::FlushMode;
+
+int DivideRoundedUp(int dividend, int divisor) {
+ return (dividend + divisor - 1) / divisor;
+}
+
std::unique_ptr CreateTestMatrix(int num_channels, int num_samples,
int timestamp) {
auto matrix = std::make_unique(num_channels, num_samples);
@@ -292,16 +300,17 @@ class AudioToTensorCalculatorStreamingModeTest : public ::testing::Test {
num_iterations_ = num_iterations;
}
- int GetExpectedNumOfSamples() {
- Matrix* expected_matrix =
- resampled_buffer_ ? resampled_buffer_.get() : sample_buffer_.get();
- return expected_matrix->cols();
- }
+ int GetExpectedNumOfSamples() { return output_sample_buffer_->cols(); }
void Run(int num_samples, int num_overlapping_samples,
- double resampling_factor) {
+ double resampling_factor, int padding_before = 0,
+ int padding_after = 0, bool expect_init_error = false) {
double input_sample_rate = 10000;
double target_sample_rate = input_sample_rate * resampling_factor;
+ FlushMode flush_mode = (padding_before != 0 || padding_after != 0)
+ ? Options::PROCEED_AS_USUAL
+ : Options::ENTIRE_TAIL_AT_TIMESTAMP_MAX;
+
auto graph_config = ParseTextProtoOrDie(
absl::Substitute(R"(
input_stream: "audio"
@@ -319,16 +328,25 @@ class AudioToTensorCalculatorStreamingModeTest : public ::testing::Test {
num_overlapping_samples: $1
target_sample_rate: $2
stream_mode:true
+ padding_samples_before: $3
+ padding_samples_after: $4
+ flush_mode: $5
}
}
}
)",
/*$0=*/num_samples, /*$1=*/num_overlapping_samples,
- /*$2=*/target_sample_rate));
+ /*$2=*/target_sample_rate, /*$3=*/padding_before,
+ /*$4=*/padding_after, /*$5=*/flush_mode));
tool::AddVectorSink("tensors", &graph_config, &tensors_packets_);
// Run the graph.
- MP_ASSERT_OK(graph_.Initialize(graph_config));
+ const absl::Status init_status = graph_.Initialize(graph_config);
+ if (expect_init_error) {
+ EXPECT_THAT(init_status, Not(IsOk()));
+ return;
+ }
+ MP_ASSERT_OK(init_status);
MP_ASSERT_OK(graph_.StartRun({}));
for (int i = 0; i < num_iterations_; ++i) {
Timestamp input_timestamp(Timestamp::kTimestampUnitsPerSecond * i);
@@ -345,8 +363,18 @@ class AudioToTensorCalculatorStreamingModeTest : public ::testing::Test {
}
MP_ASSERT_OK(graph_.CloseAllInputStreams());
MP_ASSERT_OK(graph_.WaitUntilIdle());
- if (resampling_factor != 1) {
- resampled_buffer_ = ResampleBuffer(*sample_buffer_, resampling_factor);
+ if (resampling_factor == 1) {
+ output_sample_buffer_ = std::make_unique(*sample_buffer_);
+ } else {
+ output_sample_buffer_ =
+ ResampleBuffer(*sample_buffer_, resampling_factor);
+ }
+ if (padding_before != 0 || padding_after != 0) {
+ Matrix padded = Matrix::Zero(
+ 2, padding_before + output_sample_buffer_->cols() + padding_after);
+ padded.block(0, padding_before, 2, output_sample_buffer_->cols()) =
+ *output_sample_buffer_;
+ output_sample_buffer_->swap(padded);
}
}
@@ -372,15 +400,13 @@ class AudioToTensorCalculatorStreamingModeTest : public ::testing::Test {
auto buffer = output_tensor.GetCpuReadView().buffer();
int num_values = output_tensor.shape().num_elements();
std::vector output_floats(buffer, buffer + num_values);
- Matrix* expected_matrix =
- resampled_buffer_ ? resampled_buffer_.get() : sample_buffer_.get();
for (int i = 0; i < num_values; ++i) {
- if (i + sample_offset >= expected_matrix->size()) {
+ if (i + sample_offset >= output_sample_buffer_->size()) {
EXPECT_FLOAT_EQ(output_floats[i], 0);
} else {
EXPECT_NEAR(output_floats[i],
- expected_matrix->coeff((i + sample_offset) % 2,
- (i + sample_offset) / 2),
+ output_sample_buffer_->coeff((i + sample_offset) % 2,
+ (i + sample_offset) / 2),
0.001)
<< "i=" << i << ", sample_offset=" << sample_offset
<< ", packet index=" << index;
@@ -391,7 +417,8 @@ class AudioToTensorCalculatorStreamingModeTest : public ::testing::Test {
// Fully close graph at end, otherwise calculator+tensors are destroyed
// after calling WaitUntilDone().
- void CloseGraph() { MP_EXPECT_OK(graph_.WaitUntilDone()); }
+ absl::Status TryCloseGraph() { return graph_.WaitUntilDone(); }
+ void CloseGraph() { MP_EXPECT_OK(TryCloseGraph()); }
private:
int input_buffer_num_samples_ = 10;
@@ -399,7 +426,7 @@ class AudioToTensorCalculatorStreamingModeTest : public ::testing::Test {
CalculatorGraph graph_;
std::vector tensors_packets_;
std::unique_ptr sample_buffer_;
- std::unique_ptr resampled_buffer_;
+ std::unique_ptr output_sample_buffer_;
};
TEST_F(AudioToTensorCalculatorStreamingModeTest,
@@ -408,7 +435,7 @@ TEST_F(AudioToTensorCalculatorStreamingModeTest,
/*resampling_factor=*/1.0f);
CheckTensorsOutputPackets(
/*sample_offset=*/10,
- /*num_packets=*/std::ceil((float)GetExpectedNumOfSamples() / 5),
+ /*num_packets=*/DivideRoundedUp(GetExpectedNumOfSamples(), 5),
/*timestamp_interval=*/500,
/*output_last_at_close=*/false);
CloseGraph();
@@ -419,7 +446,7 @@ TEST_F(AudioToTensorCalculatorStreamingModeTest, OutputRemainingInCloseMethod) {
/*resampling_factor=*/1.0f);
CheckTensorsOutputPackets(
/*sample_offset=*/12,
- /*num_packets=*/std::ceil((float)GetExpectedNumOfSamples() / 6),
+ /*num_packets=*/DivideRoundedUp(GetExpectedNumOfSamples(), 6),
/*timestamp_interval=*/600,
/*output_last_at_close=*/true);
CloseGraph();
@@ -431,7 +458,7 @@ TEST_F(AudioToTensorCalculatorStreamingModeTest, OutputOverlappingFp32Tensors) {
/*resampling_factor=*/1.0f);
CheckTensorsOutputPackets(
/*sample_offset=*/16,
- /*num_packets=*/std::ceil((float)GetExpectedNumOfSamples() / 8),
+ /*num_packets=*/DivideRoundedUp(GetExpectedNumOfSamples(), 8),
/*timestamp_interval=*/800,
/*output_last_at_close=*/true);
CloseGraph();
@@ -443,7 +470,7 @@ TEST_F(AudioToTensorCalculatorStreamingModeTest, Downsampling) {
/*resampling_factor=*/0.5f);
CheckTensorsOutputPackets(
/*sample_offset=*/512,
- /*num_packets=*/std::ceil((float)GetExpectedNumOfSamples() / 256),
+ /*num_packets=*/DivideRoundedUp(GetExpectedNumOfSamples(), 256),
/*timestamp_interval=*/51200,
/*output_last_at_close=*/true);
CloseGraph();
@@ -455,7 +482,7 @@ TEST_F(AudioToTensorCalculatorStreamingModeTest, DownsamplingWithOverlapping) {
/*resampling_factor=*/0.5f);
CheckTensorsOutputPackets(
/*sample_offset=*/384,
- /*num_packets=*/std::ceil((float)GetExpectedNumOfSamples() / 192),
+ /*num_packets=*/DivideRoundedUp(GetExpectedNumOfSamples(), 192),
/*timestamp_interval=*/38400,
/*output_last_at_close=*/true);
CloseGraph();
@@ -467,7 +494,7 @@ TEST_F(AudioToTensorCalculatorStreamingModeTest, Upsampling) {
/*resampling_factor=*/2.0f);
CheckTensorsOutputPackets(
/*sample_offset=*/512,
- /*num_packets=*/std::ceil((float)GetExpectedNumOfSamples() / 256),
+ /*num_packets=*/DivideRoundedUp(GetExpectedNumOfSamples(), 256),
/*timestamp_interval=*/12800,
/*output_last_at_close=*/true);
CloseGraph();
@@ -479,12 +506,33 @@ TEST_F(AudioToTensorCalculatorStreamingModeTest, UpsamplingWithOverlapping) {
/*resampling_factor=*/2.0f);
CheckTensorsOutputPackets(
/*sample_offset=*/384,
- /*num_packets=*/std::ceil((float)GetExpectedNumOfSamples() / 192),
+ /*num_packets=*/DivideRoundedUp(GetExpectedNumOfSamples(), 192),
/*timestamp_interval=*/9600,
/*output_last_at_close=*/true);
CloseGraph();
}
+TEST_F(AudioToTensorCalculatorStreamingModeTest,
+ UpsamplingWithOverlappingAndPadding) {
+ SetInputBufferNumSamplesPerChannel(1024);
+ Run(/*num_samples=*/256, /*num_overlapping_samples=*/64,
+ /*resampling_factor=*/2.0f, /*padding_before=*/13, /*padding_after=*/999);
+ CheckTensorsOutputPackets(
+ /*sample_offset=*/384,
+ /*num_packets=*/DivideRoundedUp(GetExpectedNumOfSamples(), 192),
+ /*timestamp_interval=*/9600,
+ /*output_last_at_close=*/false);
+ CloseGraph();
+}
+
+TEST_F(AudioToTensorCalculatorStreamingModeTest, NegativePaddingUnsupported) {
+ SetInputBufferNumSamplesPerChannel(1024);
+ Run(/*num_samples=*/256, /*num_overlapping_samples=*/64,
+ /*resampling_factor=*/2.0f, /*padding_before=*/13, /*padding_after=*/-3,
+ /*expect_init_error=*/true);
+ EXPECT_THAT(TryCloseGraph(), Not(IsOk()));
+}
+
TEST_F(AudioToTensorCalculatorStreamingModeTest,
OnlyOutputInCloseIfNoSufficientSamples) {
SetNumIterations(1);
@@ -498,5 +546,122 @@ TEST_F(AudioToTensorCalculatorStreamingModeTest,
CloseGraph();
}
+class AudioToTensorCalculatorFftTest : public ::testing::Test {
+ protected:
+ // Creates an audio matrix containing a single sample of 1.0 at a specified
+ // offset.
+ std::unique_ptr CreateImpulseSignalData(int64 num_samples,
+ int impulse_offset_idx) {
+ Matrix impulse = Matrix::Zero(1, num_samples);
+ impulse(0, impulse_offset_idx) = 1.0;
+ return std::make_unique(std::move(impulse));
+ }
+
+ void ConfigGraph(int num_channels, int num_samples,
+ int num_overlapping_samples, double sample_rate,
+ int fft_size) {
+ graph_config_ = ParseTextProtoOrDie(
+ absl::Substitute(R"(
+ input_stream: "audio"
+ input_stream: "sample_rate"
+ output_stream: "tensors"
+ output_stream: "dc_and_nyquist"
+ node {
+ calculator: "AudioToTensorCalculator"
+ input_stream: "AUDIO:audio"
+ input_stream: "SAMPLE_RATE:sample_rate"
+ output_stream: "TENSORS:tensors"
+ output_stream: "DC_AND_NYQUIST:dc_and_nyquist"
+ options {
+ [mediapipe.AudioToTensorCalculatorOptions.ext] {
+ num_channels: $0
+ num_samples: $1
+ num_overlapping_samples: $2
+ target_sample_rate: $3
+ fft_size: $4
+ }
+ }
+ }
+ )",
+ /*$0=*/num_channels,
+ /*$1=*/num_samples,
+ /*$2=*/num_overlapping_samples,
+ /*$3=*/sample_rate, /*$4=*/fft_size));
+ std::vector tensors_packets;
+ tool::AddVectorSink("tensors", &graph_config_, &tensors_packets_);
+ std::vector dc_and_nyquist_packets;
+ tool::AddVectorSink("dc_and_nyquist", &graph_config_,
+ &dc_and_nyquist_packets_);
+ }
+
+ void RunGraph(std::unique_ptr input_data, double sample_rate) {
+ MP_ASSERT_OK(graph_.Initialize(graph_config_));
+ MP_ASSERT_OK(graph_.StartRun({}));
+ MP_ASSERT_OK(graph_.AddPacketToInputStream(
+ "sample_rate", MakePacket(sample_rate).At(Timestamp(0))));
+ MP_ASSERT_OK(graph_.AddPacketToInputStream(
+ "audio", MakePacket(*input_data).At(Timestamp(0))));
+ MP_ASSERT_OK(graph_.CloseAllInputStreams());
+ MP_ASSERT_OK(graph_.WaitUntilIdle());
+ ASSERT_EQ(tensors_packets_.size(), dc_and_nyquist_packets_.size());
+ }
+
+ // Fully close graph at end, otherwise calculator+tensors are destroyed
+ // after calling WaitUntilDone().
+ void CloseGraph() { MP_EXPECT_OK(graph_.WaitUntilDone()); }
+
+ std::vector tensors_packets_;
+ std::vector dc_and_nyquist_packets_;
+ CalculatorGraphConfig graph_config_;
+ CalculatorGraph graph_;
+};
+
+TEST_F(AudioToTensorCalculatorFftTest, TestInvalidFftSize) {
+ ConfigGraph(1, 320, 160, 16000, 103);
+ MP_ASSERT_OK(graph_.Initialize(graph_config_));
+ MP_ASSERT_OK(graph_.StartRun({}));
+ auto status = graph_.WaitUntilIdle();
+ EXPECT_EQ(status.code(), absl::StatusCode::kInternal);
+ EXPECT_THAT(status.message(),
+ ::testing::HasSubstr("FFT size must be of the form"));
+}
+
+TEST_F(AudioToTensorCalculatorFftTest, TestInvalidNumChannels) {
+ ConfigGraph(3, 320, 160, 16000, 256);
+ MP_ASSERT_OK(graph_.Initialize(graph_config_));
+ MP_ASSERT_OK(graph_.StartRun({}));
+ auto status = graph_.WaitUntilIdle();
+ EXPECT_EQ(status.code(), absl::StatusCode::kInternal);
+ EXPECT_THAT(
+ status.message(),
+ ::testing::HasSubstr("only support applying FFT on mono channel"));
+}
+
+TEST_F(AudioToTensorCalculatorFftTest, TestImpulseSignal) {
+ constexpr double sample_rate = 16000;
+ ConfigGraph(1, 320, 160, sample_rate, 320);
+ RunGraph(CreateImpulseSignalData(320, 160), sample_rate);
+ for (int i = 0; i < tensors_packets_.size(); ++i) {
+ const auto& tensors = tensors_packets_[i].Get>();
+ ASSERT_EQ(1, tensors.size());
+ const Tensor& output_tensor =
+ tensors_packets_[0].Get>()[0];
+ auto* buffer = output_tensor.GetCpuReadView().buffer();
+ int num_values = output_tensor.shape().num_elements();
+ const std::vector output_floats(buffer, buffer + num_values);
+ // Impulse signal should have (approximately) const power across all
+ // frequency bins.
+ const auto& pair =
+ dc_and_nyquist_packets_[i].Get>();
+ EXPECT_FLOAT_EQ(pair.first, 1.0f);
+ EXPECT_FLOAT_EQ(pair.second, 1.0f);
+ for (int j = 0; j < num_values / 2; ++j) {
+ std::complex cf(output_floats[j * 2], output_floats[j * 2 + 1]);
+ EXPECT_FLOAT_EQ(std::norm(cf), 1.0f);
+ }
+ }
+ CloseGraph();
+}
+
} // namespace
} // namespace mediapipe
diff --git a/mediapipe/calculators/tensor/feedback_tensors_calculator.cc b/mediapipe/calculators/tensor/feedback_tensors_calculator.cc
new file mode 100644
index 000000000..5fa171ef3
--- /dev/null
+++ b/mediapipe/calculators/tensor/feedback_tensors_calculator.cc
@@ -0,0 +1,165 @@
+// Copyright 2022 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
+#include
+#include
+
+#include "absl/status/status.h"
+#include "mediapipe/calculators/tensor/feedback_tensors_calculator.pb.h"
+#include "mediapipe/framework/api2/node.h"
+#include "mediapipe/framework/calculator_framework.h"
+#include "mediapipe/framework/formats/tensor.h"
+
+namespace mediapipe {
+namespace api2 {
+
+namespace {
+constexpr char kInputTensorsTag[] = "INPUT_TENSORS";
+constexpr char kFeedbackTensorsTag[] = "FEEDBACK_TENSORS";
+constexpr char kOutputTensorsTag[] = "TENSORS";
+
+using Tensors = std::vector;
+} // namespace
+
+// FeedbackTensorsCalculator groups the input and the feedback (typically
+// recurrent neural network cell state output tensors from the previous run)
+// tensor vectors as the input tensor vector for the next recurrent model cell
+// inference. On the first step, the feedback tensor is filled with zeros to
+// jumpstart the loop.
+class FeedbackTensorsCalculator : public Node {
+ public:
+ static constexpr Input kFeedbackTensorsIn{kFeedbackTensorsTag};
+ static constexpr Input kInputTensorsIn{kInputTensorsTag};
+ static constexpr Output kTensorsOut{kOutputTensorsTag};
+
+ MEDIAPIPE_NODE_CONTRACT(kFeedbackTensorsIn, kInputTensorsIn, kTensorsOut);
+
+ static absl::Status GetContract(CalculatorContract* cc) {
+ cc->SetProcessTimestampBounds(true);
+ return absl::OkStatus();
+ }
+
+ absl::Status Open(CalculatorContext* cc) override {
+ const auto& options =
+ cc->Options();
+
+ const auto& shape_dims = options.feedback_tensor_shape().dims();
+ feedback_tensor_shape_.dims.assign(shape_dims.begin(), shape_dims.end());
+ feedback_tensor_size_ = feedback_tensor_shape_.num_elements();
+
+ num_feedback_tensors_ = options.num_feedback_tensors();
+
+ feedback_tensors_location_ = options.location();
+
+ return absl::OkStatus();
+ }
+
+ absl::Status Process(CalculatorContext* cc) override {
+ if (feedback_tensors_location_ ==
+ mediapipe::FeedbackTensorsCalculatorOptions::NONE) {
+ kTensorsOut(cc).Send(kInputTensorsIn(cc).packet().As());
+ return absl::OkStatus();
+ }
+
+ std::vector outputs;
+ switch (feedback_tensors_location_) {
+ case mediapipe::FeedbackTensorsCalculatorOptions::PREPENDED:
+ MP_RETURN_IF_ERROR(AddFeedbackTensors(cc, outputs));
+ MP_RETURN_IF_ERROR(AddInputTensors(cc, outputs));
+ break;
+ case mediapipe::FeedbackTensorsCalculatorOptions::APPENDED:
+ MP_RETURN_IF_ERROR(AddInputTensors(cc, outputs));
+ MP_RETURN_IF_ERROR(AddFeedbackTensors(cc, outputs));
+ break;
+ default:
+ return absl::InvalidArgumentError(
+ "Unsupported feedback tensors location");
+ }
+ kTensorsOut(cc).Send(std::move(outputs));
+ return absl::OkStatus();
+ }
+
+ private:
+ absl::Status AddInputTensors(CalculatorContext* cc,
+ std::vector& outputs) {
+ absl::StatusOr>> input_tensors =
+ cc->Inputs()
+ .Tag(kInputTensorsTag)
+ .Value()
+ .Consume>();
+ if (!input_tensors.ok()) {
+ return absl::InternalError("The input tensors packet is not consumable");
+ }
+ RET_CHECK(*input_tensors);
+ std::vector& inputs = **input_tensors;
+ outputs.insert(outputs.end(), std::make_move_iterator(inputs.begin()),
+ std::make_move_iterator(inputs.end()));
+ return absl::OkStatus();
+ }
+
+ absl::Status AddFeedbackTensors(CalculatorContext* cc,
+ std::vector& outputs) {
+ if (first_run_) {
+ for (int index = 0; index < num_feedback_tensors_; ++index) {
+ Tensor initial_feedback_tensor(Tensor::ElementType::kFloat32,
+ feedback_tensor_shape_);
+ float* data = initial_feedback_tensor.GetCpuWriteView().buffer();
+ std::fill_n(data, feedback_tensor_size_, 0.0f);
+ outputs.push_back(std::move(initial_feedback_tensor));
+ }
+ first_run_ = false;
+ return absl::OkStatus();
+ }
+
+ if (num_feedback_tensors_ != kFeedbackTensorsIn(cc)->size()) {
+ return absl::InvalidArgumentError(
+ "The number of tensors fed back differs from the configuration");
+ }
+ absl::StatusOr>> feedback_tensors =
+ cc->Inputs()
+ .Tag(kFeedbackTensorsTag)
+ .Value()
+ .Consume>();
+ if (!feedback_tensors.ok()) {
+ return absl::InternalError(
+ "The feedback tensors packet is not consumable");
+ }
+ RET_CHECK(*feedback_tensors);
+ std::vector& feedbacks = **feedback_tensors;
+ for (const auto& feedback : feedbacks) {
+ if (feedback.shape().dims != feedback_tensor_shape_.dims) {
+ return absl::InvalidArgumentError(
+ "The shape of a tensor fed back differs from the configuration");
+ }
+ }
+ outputs.insert(outputs.end(), std::make_move_iterator(feedbacks.begin()),
+ std::make_move_iterator(feedbacks.end()));
+
+ return absl::OkStatus();
+ }
+
+ Tensor::Shape feedback_tensor_shape_;
+ int num_feedback_tensors_ = 0;
+ mediapipe::FeedbackTensorsCalculatorOptions::FeedbackTensorsLocation
+ feedback_tensors_location_;
+
+ int feedback_tensor_size_ = 0;
+ bool first_run_ = true;
+};
+
+MEDIAPIPE_REGISTER_NODE(FeedbackTensorsCalculator);
+
+} // namespace api2
+} // namespace mediapipe
diff --git a/mediapipe/calculators/tensor/feedback_tensors_calculator.proto b/mediapipe/calculators/tensor/feedback_tensors_calculator.proto
new file mode 100644
index 000000000..ac36b6780
--- /dev/null
+++ b/mediapipe/calculators/tensor/feedback_tensors_calculator.proto
@@ -0,0 +1,47 @@
+// Copyright 2022 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 FeedbackTensorsCalculatorOptions {
+ extend mediapipe.CalculatorOptions {
+ optional FeedbackTensorsCalculatorOptions ext = 474496252;
+ }
+
+ // Represents the dimensions of a tensor starting from the outermost size.
+ message TensorShape {
+ repeated int32 dims = 1 [packed = true];
+ }
+
+ // The shape of the feedback tensors to add.
+ optional TensorShape feedback_tensor_shape = 1;
+ // The number of the feedback tensors to add.
+ optional int32 num_feedback_tensors = 2 [default = 1];
+
+ enum FeedbackTensorsLocation {
+ // The feedback tensors will not be added.
+ NONE = 0;
+ // The feedback tensors will be added before the input tensors.
+ PREPENDED = 1;
+ // The feedback tensors will be added after the input tensors.
+ APPENDED = 2;
+ }
+
+ // Determines the location of the feedback tensor(s) in the output vector.
+ optional FeedbackTensorsLocation location = 3 [default = APPENDED];
+}
diff --git a/mediapipe/calculators/tensor/feedback_tensors_calculator_test.cc b/mediapipe/calculators/tensor/feedback_tensors_calculator_test.cc
new file mode 100644
index 000000000..5797cc31c
--- /dev/null
+++ b/mediapipe/calculators/tensor/feedback_tensors_calculator_test.cc
@@ -0,0 +1,389 @@
+// Copyright 2022 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
+#include
+#include
+#include
+#include
+
+#include "mediapipe/calculators/tensor/feedback_tensors_calculator.pb.h"
+#include "mediapipe/framework/calculator.pb.h"
+#include "mediapipe/framework/calculator_framework.h"
+#include "mediapipe/framework/formats/tensor.h"
+#include "mediapipe/framework/port/gmock.h"
+#include "mediapipe/framework/port/gtest.h"
+#include "mediapipe/framework/port/parse_text_proto.h"
+#include "mediapipe/framework/port/status_matchers.h"
+#include "mediapipe/framework/timestamp.h"
+
+namespace mediapipe {
+namespace {
+
+using ::mediapipe::CalculatorGraphConfig;
+using ::testing::ElementsAreArray;
+using ::testing::Not;
+using Tensors = std::vector;
+
+template
+struct TensorElementType {
+ static constexpr Tensor::ElementType value = Tensor::ElementType::kNone;
+};
+
+template <>
+struct TensorElementType {
+ static constexpr Tensor::ElementType value = Tensor::ElementType::kFloat32;
+};
+
+template <>
+struct TensorElementType {
+ static constexpr Tensor::ElementType value = Tensor::ElementType::kInt8;
+};
+
+template <>
+struct TensorElementType {
+ static constexpr Tensor::ElementType value = Tensor::ElementType::kUInt8;
+};
+
+template <>
+struct TensorElementType {
+ static constexpr Tensor::ElementType value = Tensor::ElementType::kInt32;
+};
+
+template
+Tensor MakeTensor(std::initializer_list shape,
+ std::initializer_list values) {
+ Tensor tensor(TensorElementType::value, shape);
+ CHECK_EQ(values.size(), tensor.shape().num_elements())
+ << "The size of `values` is incompatible with `shape`";
+ absl::c_copy(values, tensor.GetCpuWriteView().buffer());
+ return tensor;
+}
+
+template
+void ValidateTensor(const Tensor& tensor,
+ const std::vector& expected_shape,
+ const std::vector& expected_values) {
+ ASSERT_EQ(tensor.element_type(), TensorElementType::value);
+ EXPECT_EQ(tensor.shape().dims, expected_shape);
+ EXPECT_EQ(tensor.shape().num_elements(), expected_values.size());
+
+ auto* tensor_buffer = tensor.GetCpuReadView().buffer();
+ const std::vector tensor_values(
+ tensor_buffer, tensor_buffer + tensor.shape().num_elements());
+ EXPECT_THAT(tensor_values, ElementsAreArray(expected_values));
+}
+
+TEST(FeedbackTensorsCalculatorTest, AppendsFeedback) {
+ auto graph_config = ParseTextProtoOrDie(R"pb(
+ input_stream: "input"
+ input_stream: "feedback"
+ node {
+ calculator: "FeedbackTensorsCalculator"
+ input_stream: "INPUT_TENSORS:input"
+ input_stream: "FEEDBACK_TENSORS:feedback"
+ output_stream: "TENSORS:output"
+ options: {
+ [mediapipe.FeedbackTensorsCalculatorOptions.ext] {
+ feedback_tensor_shape: { dims: 2 dims: 3 }
+ location: APPENDED
+ }
+ }
+ }
+ )pb");
+ std::vector output_packets;
+ tool::AddVectorSink("output", &graph_config, &output_packets);
+
+ CalculatorGraph graph;
+ MP_ASSERT_OK(graph.Initialize(graph_config));
+ MP_ASSERT_OK(graph.StartRun({}));
+
+ auto initial_input_tensors = std::make_unique();
+ initial_input_tensors->push_back(
+ MakeTensor({2, 4}, {1, 2, 3, 4, 5, 6, 7, 8}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "input", Adopt(initial_input_tensors.release()).At(Timestamp(1))));
+ // At the beginning, the loopback packet with the model feedback is missing.
+ // The calculator has to assume it's all-zero with the shape from the options.
+
+ auto later_input_tensors = std::make_unique();
+ later_input_tensors->push_back(
+ MakeTensor({2, 4}, {8, 7, 6, 5, 4, 3, 2, 1}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "input", Adopt(later_input_tensors.release()).At(Timestamp(2))));
+ auto later_feedback_tensors = std::make_unique();
+ later_feedback_tensors->push_back(
+ MakeTensor({2, 3}, {-1.f, -2.f, -3.f, -4.f, -5.f, -6.f}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "feedback", Adopt(later_feedback_tensors.release()).At(Timestamp(2))));
+
+ MP_ASSERT_OK(graph.CloseAllInputStreams())
+ << "Couldn't close the graph inputs";
+ MP_ASSERT_OK(graph.WaitUntilDone()) << "Couldn't finalize the graph run";
+
+ ASSERT_EQ(output_packets.size(), 2);
+
+ const Tensors& initial_combined_tensors = output_packets[0].Get();
+ ASSERT_EQ(initial_combined_tensors.size(), 2);
+ ValidateTensor(initial_combined_tensors[0],
+ /*expected_shape=*/{2, 4},
+ /*expected_values=*/{1, 2, 3, 4, 5, 6, 7, 8});
+ // The initial feedback is zero.
+ ValidateTensor(initial_combined_tensors[1], /*expected_shape=*/{2, 3},
+ /*expected_values=*/{0.f, 0.f, 0.f, 0.f, 0.f, 0.f});
+
+ const Tensors& later_combined_tensors = output_packets[1].Get();
+ ASSERT_EQ(later_combined_tensors.size(), 2);
+ ValidateTensor(later_combined_tensors[0],
+ /*expected_shape=*/{2, 4},
+ /*expected_values=*/{8, 7, 6, 5, 4, 3, 2, 1});
+ // Afterwards, the provided feedback is passed through.
+ ValidateTensor(
+ later_combined_tensors[1], /*expected_shape=*/{2, 3},
+ /*expected_values=*/{-1.f, -2.f, -3.f, -4.f, -5.f, -6.f});
+}
+
+TEST(FeedbackTensorsCalculatorTest, PrependsFeedback) {
+ auto graph_config = ParseTextProtoOrDie(R"pb(
+ input_stream: "input"
+ input_stream: "feedback"
+ node {
+ calculator: "FeedbackTensorsCalculator"
+ input_stream: "INPUT_TENSORS:input"
+ input_stream: "FEEDBACK_TENSORS:feedback"
+ output_stream: "TENSORS:output"
+ options: {
+ [mediapipe.FeedbackTensorsCalculatorOptions.ext] {
+ feedback_tensor_shape: { dims: 3 dims: 2 }
+ location: PREPENDED
+ }
+ }
+ }
+ )pb");
+ std::vector output_packets;
+ tool::AddVectorSink("output", &graph_config, &output_packets);
+
+ CalculatorGraph graph;
+ MP_ASSERT_OK(graph.Initialize(graph_config));
+ MP_ASSERT_OK(graph.StartRun({}));
+
+ auto initial_input_tensors = std::make_unique();
+ initial_input_tensors->push_back(
+ MakeTensor({2, 4}, {1, 2, 3, 4, 5, 6, 7, 8}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "input", Adopt(initial_input_tensors.release()).At(Timestamp(1))));
+ // At the beginning, the loopback packet with the model feedback is missing.
+ // The calculator has to assume it's all-zero with the shape from the options.
+
+ auto later_input_tensors = std::make_unique();
+ later_input_tensors->push_back(
+ MakeTensor({2, 4}, {8, 7, 6, 5, 4, 3, 2, 1}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "input", Adopt(later_input_tensors.release()).At(Timestamp(2))));
+ auto later_feedback_tensors = std::make_unique();
+ later_feedback_tensors->push_back(
+ MakeTensor({3, 2}, {-1.f, -2.f, -3.f, -4.f, -5.f, -6.f}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "feedback", Adopt(later_feedback_tensors.release()).At(Timestamp(2))));
+
+ MP_ASSERT_OK(graph.CloseAllInputStreams())
+ << "Couldn't close the graph inputs";
+ MP_ASSERT_OK(graph.WaitUntilDone()) << "Couldn't finalize the graph run";
+
+ ASSERT_EQ(output_packets.size(), 2);
+
+ const Tensors& initial_combined_tensors = output_packets[0].Get();
+ ASSERT_EQ(initial_combined_tensors.size(), 2);
+ // The initial feedback is zero.
+ ValidateTensor(initial_combined_tensors[0], /*expected_shape=*/{3, 2},
+ /*expected_values=*/{0.f, 0.f, 0.f, 0.f, 0.f, 0.f});
+ ValidateTensor(initial_combined_tensors[1],
+ /*expected_shape=*/{2, 4},
+ /*expected_values=*/{1, 2, 3, 4, 5, 6, 7, 8});
+
+ const Tensors& later_combined_tensors = output_packets[1].Get();
+ ASSERT_EQ(later_combined_tensors.size(), 2);
+ // Afterwards, the provided feedback is passed through.
+ ValidateTensor(
+ later_combined_tensors[0], /*expected_shape=*/{3, 2},
+ /*expected_values=*/{-1.f, -2.f, -3.f, -4.f, -5.f, -6.f});
+ ValidateTensor(later_combined_tensors[1],
+ /*expected_shape=*/{2, 4},
+ /*expected_values=*/{8, 7, 6, 5, 4, 3, 2, 1});
+}
+
+TEST(FeedbackTensorsCalculatorTest, NoFeedback) {
+ auto graph_config = ParseTextProtoOrDie(R"pb(
+ input_stream: "input"
+ input_stream: "feedback"
+ node {
+ calculator: "FeedbackTensorsCalculator"
+ input_stream: "INPUT_TENSORS:input"
+ input_stream: "FEEDBACK_TENSORS:feedback"
+ output_stream: "TENSORS:output"
+ options: {
+ [mediapipe.FeedbackTensorsCalculatorOptions.ext] {
+ feedback_tensor_shape: { dims: 3 dims: 4 }
+ location: NONE
+ }
+ }
+ }
+ )pb");
+ std::vector output_packets;
+ tool::AddVectorSink("output", &graph_config, &output_packets);
+
+ CalculatorGraph graph;
+ MP_ASSERT_OK(graph.Initialize(graph_config));
+ MP_ASSERT_OK(graph.StartRun({}));
+
+ auto initial_input_tensors = std::make_unique();
+ initial_input_tensors->push_back(
+ MakeTensor({2, 4}, {1, 2, 3, 4, 5, 6, 7, 8}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "input", Adopt(initial_input_tensors.release()).At(Timestamp(1))));
+ // At the beginning, the loopback packet with the model feedback is missing.
+
+ auto later_input_tensors = std::make_unique();
+ later_input_tensors->push_back(
+ MakeTensor({2, 4}, {8, 7, 6, 5, 4, 3, 2, 1}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "input", Adopt(later_input_tensors.release()).At(Timestamp(2))));
+ // This feedback should be ignored due to `location: NONE`.
+ auto later_feedback_tensors = std::make_unique();
+ later_feedback_tensors->push_back(
+ MakeTensor({2, 3}, {-1.f, -2.f, -3.f, -4.f, -5.f, -6.f}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "feedback", Adopt(later_feedback_tensors.release()).At(Timestamp(2))));
+
+ MP_ASSERT_OK(graph.CloseAllInputStreams())
+ << "Couldn't close the graph inputs";
+ MP_ASSERT_OK(graph.WaitUntilDone()) << "Couldn't finalize the graph run";
+
+ ASSERT_EQ(output_packets.size(), 2);
+
+ const Tensors& initial_combined_tensors = output_packets[0].Get();
+ ASSERT_EQ(initial_combined_tensors.size(), 1);
+ ValidateTensor(initial_combined_tensors[0],
+ /*expected_shape=*/{2, 4},
+ /*expected_values=*/{1, 2, 3, 4, 5, 6, 7, 8});
+ // No feedback due to `location: NONE`.
+
+ const Tensors& later_combined_tensors = output_packets[1].Get();
+ ASSERT_EQ(later_combined_tensors.size(), 1);
+ ValidateTensor(later_combined_tensors[0],
+ /*expected_shape=*/{2, 4},
+ /*expected_values=*/{8, 7, 6, 5, 4, 3, 2, 1});
+}
+
+TEST(FeedbackTensorsCalculatorTest, ChecksTensorNumber) {
+ auto graph_config = ParseTextProtoOrDie(R"pb(
+ input_stream: "input"
+ input_stream: "feedback"
+ node {
+ calculator: "FeedbackTensorsCalculator"
+ input_stream: "INPUT_TENSORS:input"
+ input_stream: "FEEDBACK_TENSORS:feedback"
+ output_stream: "TENSORS:output"
+ options: {
+ [mediapipe.FeedbackTensorsCalculatorOptions.ext] {
+ num_feedback_tensors: 2
+ feedback_tensor_shape: { dims: 2 dims: 3 }
+ location: PREPENDED
+ }
+ }
+ }
+ )pb");
+ std::vector output_packets;
+ tool::AddVectorSink("output", &graph_config, &output_packets);
+
+ CalculatorGraph graph;
+ MP_ASSERT_OK(graph.Initialize(graph_config));
+ MP_ASSERT_OK(graph.StartRun({}));
+
+ auto initial_input_tensors = std::make_unique();
+ initial_input_tensors->push_back(
+ MakeTensor({2, 4}, {1, 2, 3, 4, 5, 6, 7, 8}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "input", Adopt(initial_input_tensors.release()).At(Timestamp(1))));
+ // At the beginning, the loopback packet with the model feedback is missing.
+
+ auto later_input_tensors = std::make_unique();
+ later_input_tensors->push_back(
+ MakeTensor({2, 4}, {8, 7, 6, 5, 4, 3, 2, 1}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "input", Adopt(later_input_tensors.release()).At(Timestamp(2))));
+ // This feedback should be ignored due to `location: NONE`.
+ auto later_feedback_tensors = std::make_unique();
+ later_feedback_tensors->push_back(
+ MakeTensor({2, 3}, {-1.f, -2.f, -3.f, -4.f, -5.f, -6.f}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "feedback", Adopt(later_feedback_tensors.release()).At(Timestamp(2))));
+
+ MP_ASSERT_OK(graph.CloseAllInputStreams())
+ << "Couldn't close the graph inputs";
+ EXPECT_THAT(graph.WaitUntilDone(), Not(IsOk()))
+ << "Tensor number mismatch missed";
+}
+
+TEST(FeedbackTensorsCalculatorTest, ChecksShape) {
+ auto graph_config = ParseTextProtoOrDie(R"pb(
+ input_stream: "input"
+ input_stream: "feedback"
+ node {
+ calculator: "FeedbackTensorsCalculator"
+ input_stream: "INPUT_TENSORS:input"
+ input_stream: "FEEDBACK_TENSORS:feedback"
+ output_stream: "TENSORS:output"
+ options: {
+ [mediapipe.FeedbackTensorsCalculatorOptions.ext] {
+ feedback_tensor_shape: { dims: 3 dims: 4 }
+ location: APPENDED
+ }
+ }
+ }
+ )pb");
+ std::vector output_packets;
+ tool::AddVectorSink("output", &graph_config, &output_packets);
+
+ CalculatorGraph graph;
+ MP_ASSERT_OK(graph.Initialize(graph_config));
+ MP_ASSERT_OK(graph.StartRun({}));
+
+ auto initial_input_tensors = std::make_unique();
+ initial_input_tensors->push_back(
+ MakeTensor({2, 4}, {1, 2, 3, 4, 5, 6, 7, 8}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "input", Adopt(initial_input_tensors.release()).At(Timestamp(1))));
+ // At the beginning, the loopback packet with the model feedback is missing.
+
+ auto later_input_tensors = std::make_unique();
+ later_input_tensors->push_back(
+ MakeTensor({2, 4}, {8, 7, 6, 5, 4, 3, 2, 1}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "input", Adopt(later_input_tensors.release()).At(Timestamp(2))));
+ // This feedback should be ignored due to `location: NONE`.
+ auto later_feedback_tensors = std::make_unique();
+ later_feedback_tensors->push_back(
+ MakeTensor({2, 3}, {-1.f, -2.f, -3.f, -4.f, -5.f, -6.f}));
+ MP_ASSERT_OK(graph.AddPacketToInputStream(
+ "feedback", Adopt(later_feedback_tensors.release()).At(Timestamp(2))));
+
+ MP_ASSERT_OK(graph.CloseAllInputStreams())
+ << "Couldn't close the graph inputs";
+ EXPECT_THAT(graph.WaitUntilDone(), Not(IsOk()))
+ << "Tensor shape mismatch missed";
+}
+
+} // namespace
+} // namespace mediapipe
diff --git a/mediapipe/calculators/tensor/text_to_tensor_calculator.cc b/mediapipe/calculators/tensor/text_to_tensor_calculator.cc
new file mode 100644
index 000000000..a2f191995
--- /dev/null
+++ b/mediapipe/calculators/tensor/text_to_tensor_calculator.cc
@@ -0,0 +1,74 @@
+// Copyright 2022 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
+#include
+#include
+
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "mediapipe/framework/api2/node.h"
+#include "mediapipe/framework/api2/port.h"
+#include "mediapipe/framework/calculator_context.h"
+#include "mediapipe/framework/calculator_framework.h"
+#include "mediapipe/framework/formats/tensor.h"
+
+namespace mediapipe {
+namespace api2 {
+
+// Trivially converts an input string into a Tensor that stores a copy of
+// the string.
+//
+// Inputs:
+// TEXT - std::string
+//
+// Outputs:
+// TENSORS - std::vector
+// Vector containing a single Tensor storing a copy of the input string.
+// Note that the underlying buffer of the Tensor is not necessarily
+// null-terminated. It is the graph writer's responsibility to copy the
+// correct number of characters when copying from this Tensor's buffer.
+//
+// Example:
+// node {
+// calculator: "TextToTensorCalculator"
+// input_stream: "TEXT:text"
+// output_stream: "TENSORS:tensors"
+// }
+class TextToTensorCalculator : public Node {
+ public:
+ static constexpr Input kTextIn{"TEXT"};
+ static constexpr Output> kTensorsOut{"TENSORS"};
+
+ MEDIAPIPE_NODE_CONTRACT(kTextIn, kTensorsOut);
+
+ absl::Status Process(CalculatorContext* cc) override;
+};
+
+absl::Status TextToTensorCalculator::Process(CalculatorContext* cc) {
+ absl::string_view text = kTextIn(cc).Get();
+ int input_len = static_cast(text.length());
+
+ std::vector result;
+ result.push_back({Tensor::ElementType::kChar, Tensor::Shape({input_len})});
+ std::memcpy(result[0].GetCpuWriteView().buffer(), text.data(),
+ input_len * sizeof(char));
+ kTensorsOut(cc).Send(std::move(result));
+ return absl::OkStatus();
+}
+
+MEDIAPIPE_REGISTER_NODE(TextToTensorCalculator);
+
+} // namespace api2
+} // namespace mediapipe
diff --git a/mediapipe/calculators/tensor/text_to_tensor_calculator_test.cc b/mediapipe/calculators/tensor/text_to_tensor_calculator_test.cc
new file mode 100644
index 000000000..51c3a9a09
--- /dev/null
+++ b/mediapipe/calculators/tensor/text_to_tensor_calculator_test.cc
@@ -0,0 +1,88 @@
+// Copyright 2022 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
+#include
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "absl/strings/substitute.h"
+#include "mediapipe/framework/calculator_framework.h"
+#include "mediapipe/framework/calculator_graph.h"
+#include "mediapipe/framework/formats/tensor.h"
+#include "mediapipe/framework/packet.h"
+#include "mediapipe/framework/port/gmock.h"
+#include "mediapipe/framework/port/gtest.h"
+#include "mediapipe/framework/port/parse_text_proto.h"
+#include "mediapipe/framework/port/status_matchers.h"
+#include "mediapipe/framework/tool/options_map.h"
+
+namespace mediapipe {
+namespace {
+
+using ::testing::StrEq;
+
+absl::StatusOr RunTextToTensorCalculator(absl::string_view text) {
+ auto graph_config = ParseTextProtoOrDie(
+ R"pb(
+ input_stream: "text"
+ output_stream: "tensors"
+ node {
+ calculator: "TextToTensorCalculator"
+ input_stream: "TEXT:text"
+ output_stream: "TENSORS:tensors"
+ }
+ )pb");
+ std::vector output_packets;
+ tool::AddVectorSink("tensors", &graph_config, &output_packets);
+
+ // Run the graph.
+ CalculatorGraph graph;
+ MP_RETURN_IF_ERROR(graph.Initialize(graph_config));
+ MP_RETURN_IF_ERROR(graph.StartRun({}));
+ MP_RETURN_IF_ERROR(graph.AddPacketToInputStream(
+ "text", MakePacket(text).At(Timestamp(0))));
+ MP_RETURN_IF_ERROR(graph.WaitUntilIdle());
+
+ if (output_packets.size() != 1) {
+ return absl::InvalidArgumentError(absl::Substitute(
+ "output_packets has size $0, expected 1", output_packets.size()));
+ }
+ const std::vector& tensor_vec =
+ output_packets[0].Get>();
+ if (tensor_vec.size() != 1) {
+ return absl::InvalidArgumentError(absl::Substitute(
+ "tensor_vec has size $0, expected 1", tensor_vec.size()));
+ }
+ if (tensor_vec[0].element_type() != Tensor::ElementType::kChar) {
+ return absl::InvalidArgumentError(absl::Substitute(
+ "tensor has element type $0, expected $1", tensor_vec[0].element_type(),
+ Tensor::ElementType::kChar));
+ }
+ const char* buffer = tensor_vec[0].GetCpuReadView().buffer();
+ return std::string(buffer, text.length());
+}
+
+TEST(TextToTensorCalculatorTest, FooBarBaz) {
+ EXPECT_THAT(RunTextToTensorCalculator("Foo. Bar? Baz!"),
+ IsOkAndHolds(StrEq("Foo. Bar? Baz!")));
+}
+
+TEST(TextToTensorCalculatorTest, Empty) {
+ EXPECT_THAT(RunTextToTensorCalculator(""), IsOkAndHolds(StrEq("")));
+}
+
+} // namespace
+} // namespace mediapipe
diff --git a/mediapipe/calculators/tensorflow/tensorflow_session_from_saved_model_calculator_test.cc b/mediapipe/calculators/tensorflow/tensorflow_session_from_saved_model_calculator_test.cc
index 52cd9e0bb..e1809a017 100644
--- a/mediapipe/calculators/tensorflow/tensorflow_session_from_saved_model_calculator_test.cc
+++ b/mediapipe/calculators/tensorflow/tensorflow_session_from_saved_model_calculator_test.cc
@@ -231,7 +231,7 @@ TEST_F(TensorFlowSessionFromSavedModelCalculatorTest,
// Session must be set.
ASSERT_NE(session.session, nullptr);
std::vector devices;
- ASSERT_EQ(session.session->ListDevices(&devices), tensorflow::Status::OK());
+ ASSERT_EQ(session.session->ListDevices(&devices), tensorflow::OkStatus());
EXPECT_THAT(devices.size(), 10);
}
diff --git a/mediapipe/calculators/tensorflow/tensorflow_session_from_saved_model_generator_test.cc b/mediapipe/calculators/tensorflow/tensorflow_session_from_saved_model_generator_test.cc
index 46cbf41cb..5c6de3e86 100644
--- a/mediapipe/calculators/tensorflow/tensorflow_session_from_saved_model_generator_test.cc
+++ b/mediapipe/calculators/tensorflow/tensorflow_session_from_saved_model_generator_test.cc
@@ -220,7 +220,7 @@ TEST_F(TensorFlowSessionFromSavedModelGeneratorTest,
// Session must be set.
ASSERT_NE(session.session, nullptr);
std::vector devices;
- ASSERT_EQ(session.session->ListDevices(&devices), tensorflow::Status::OK());
+ ASSERT_EQ(session.session->ListDevices(&devices), tensorflow::OkStatus());
EXPECT_THAT(devices.size(), 10);
}
diff --git a/mediapipe/calculators/tflite/BUILD b/mediapipe/calculators/tflite/BUILD
index b132db01d..1d2f279aa 100644
--- a/mediapipe/calculators/tflite/BUILD
+++ b/mediapipe/calculators/tflite/BUILD
@@ -135,6 +135,7 @@ filegroup(
srcs = [
"testdata/anchor_golden_file_0.txt",
"testdata/anchor_golden_file_1.txt",
+ "testdata/anchor_golden_file_2.txt",
],
)
diff --git a/mediapipe/calculators/tflite/ssd_anchors_calculator.cc b/mediapipe/calculators/tflite/ssd_anchors_calculator.cc
index f618b2f6a..1d8f6e3ea 100644
--- a/mediapipe/calculators/tflite/ssd_anchors_calculator.cc
+++ b/mediapipe/calculators/tflite/ssd_anchors_calculator.cc
@@ -13,6 +13,7 @@
// limitations under the License.
#include
+#include
#include
#include "mediapipe/calculators/tflite/ssd_anchors_calculator.pb.h"
@@ -24,6 +25,19 @@ namespace mediapipe {
namespace {
+struct MultiScaleAnchorInfo {
+ int32 level;
+ std::vector aspect_ratios;
+ std::vector scales;
+ std::pair base_anchor_size;
+ std::pair anchor_stride;
+};
+
+struct FeatureMapDim {
+ int height;
+ int width;
+};
+
float CalculateScale(float min_scale, float max_scale, int stride_index,
int num_strides) {
if (num_strides == 1) {
@@ -34,6 +48,71 @@ float CalculateScale(float min_scale, float max_scale, int stride_index,
}
}
+int GetNumLayers(const SsdAnchorsCalculatorOptions& options) {
+ if (options.multiscale_anchor_generation()) {
+ return (options.max_level() - options.min_level() + 1);
+ }
+ return options.num_layers();
+}
+
+FeatureMapDim GetFeatureMapDimensions(
+ const SsdAnchorsCalculatorOptions& options, int index) {
+ FeatureMapDim feature_map_dims;
+ if (options.feature_map_height_size()) {
+ feature_map_dims.height = options.feature_map_height(index);
+ feature_map_dims.width = options.feature_map_width(index);
+ } else {
+ const int stride = options.strides(index);
+ feature_map_dims.height =
+ std::ceil(1.0f * options.input_size_height() / stride);
+ feature_map_dims.width =
+ std::ceil(1.0f * options.input_size_width() / stride);
+ }
+ return feature_map_dims;
+}
+
+// Although we have stride for both x and y, only one value is used for offset
+// calculation. See
+// tensorflow_models/object_detection/anchor_generators/multiscale_grid_anchor_generator.py;l=121
+std::pair GetMultiScaleAnchorOffset(
+ const SsdAnchorsCalculatorOptions& options, const float stride,
+ const int level) {
+ std::pair result(0., 0.);
+ int denominator = std::pow(2, level);
+ if (options.input_size_height() % denominator == 0 ||
+ options.input_size_height() == 1) {
+ result.first = stride / 2.0;
+ }
+ if (options.input_size_width() % denominator == 0 ||
+ options.input_size_width() == 1) {
+ result.second = stride / 2.0;
+ }
+ return result;
+}
+
+void NormalizeAnchor(const int input_height, const int input_width,
+ Anchor* anchor) {
+ anchor->set_h(anchor->h() / (float)input_height);
+ anchor->set_w(anchor->w() / (float)input_width);
+ anchor->set_y_center(anchor->y_center() / (float)input_height);
+ anchor->set_x_center(anchor->x_center() / (float)input_width);
+}
+
+Anchor CalculateAnchorBox(const int y_center, const int x_center,
+ const float scale, const float aspect_ratio,
+ const std::pair base_anchor_size,
+ // y-height first
+ const std::pair anchor_stride,
+ const std::pair anchor_offset) {
+ Anchor result;
+ float ratio_sqrt = std::sqrt(aspect_ratio);
+ result.set_h(scale * base_anchor_size.first / ratio_sqrt);
+ result.set_w(scale * ratio_sqrt * base_anchor_size.second);
+ result.set_y_center(y_center * anchor_stride.first + anchor_offset.first);
+ result.set_x_center(x_center * anchor_stride.second + anchor_offset.second);
+ return result;
+}
+
} // namespace
// Generate anchors for SSD object detection model.
@@ -95,9 +174,77 @@ class SsdAnchorsCalculator : public CalculatorBase {
private:
static absl::Status GenerateAnchors(
std::vector* anchors, const SsdAnchorsCalculatorOptions& options);
+
+ static absl::Status GenerateMultiScaleAnchors(
+ std::vector* anchors, const SsdAnchorsCalculatorOptions& options);
};
REGISTER_CALCULATOR(SsdAnchorsCalculator);
+// Generates grid anchors on the fly corresponding to multiple CNN layers as
+// described in:
+// "Focal Loss for Dense Object Detection" (https://arxiv.org/abs/1708.02002)
+// T.-Y. Lin, P. Goyal, R. Girshick, K. He, P. Dollar
+absl::Status SsdAnchorsCalculator::GenerateMultiScaleAnchors(
+ std::vector* anchors, const SsdAnchorsCalculatorOptions& options) {
+ std::vector anchor_infos;
+ for (int i = options.min_level(); i <= options.max_level(); ++i) {
+ MultiScaleAnchorInfo current_anchor_info;
+ // level
+ current_anchor_info.level = i;
+ // aspect_ratios
+ for (const float aspect_ratio : options.aspect_ratios()) {
+ current_anchor_info.aspect_ratios.push_back(aspect_ratio);
+ }
+
+ // scale
+ for (int i = 0; i < options.scales_per_octave(); ++i) {
+ current_anchor_info.scales.push_back(
+ std::pow(2.0, (double)i / (double)options.scales_per_octave()));
+ }
+
+ // anchor stride
+ float anchor_stride = std::pow(2.0, i);
+ current_anchor_info.anchor_stride =
+ std::make_pair(anchor_stride, anchor_stride);
+
+ // base_anchor_size
+ current_anchor_info.base_anchor_size =
+ std::make_pair(anchor_stride * options.anchor_scale(),
+ anchor_stride * options.anchor_scale());
+ anchor_infos.push_back(current_anchor_info);
+ }
+
+ for (unsigned int i = 0; i < anchor_infos.size(); ++i) {
+ FeatureMapDim dimensions = GetFeatureMapDimensions(options, i);
+ for (int y = 0; y < dimensions.height; ++y) {
+ for (int x = 0; x < dimensions.width; ++x) {
+ // loop over combination of scale and aspect ratio
+ for (unsigned int j = 0; j < anchor_infos[i].aspect_ratios.size();
+ ++j) {
+ for (unsigned int k = 0; k < anchor_infos[i].scales.size(); ++k) {
+ Anchor anchor = CalculateAnchorBox(
+ /*y_center=*/y, /*x_center=*/x, anchor_infos[i].scales[k],
+ anchor_infos[i].aspect_ratios[j],
+ anchor_infos[i].base_anchor_size,
+ /*anchor_stride=*/anchor_infos[i].anchor_stride,
+ /*anchor_offset=*/
+ GetMultiScaleAnchorOffset(options,
+ anchor_infos[i].anchor_stride.first,
+ anchor_infos[i].level));
+ if (options.normalize_coordinates()) {
+ NormalizeAnchor(options.input_size_height(),
+ options.input_size_width(), &anchor);
+ }
+ anchors->push_back(anchor);
+ }
+ }
+ }
+ }
+ }
+
+ return absl::OkStatus();
+}
+
absl::Status SsdAnchorsCalculator::GenerateAnchors(
std::vector* anchors, const SsdAnchorsCalculatorOptions& options) {
// Verify the options.
@@ -106,15 +253,21 @@ absl::Status SsdAnchorsCalculator::GenerateAnchors(
"Both feature map shape and strides are missing. Must provide either "
"one.");
}
+ const int kNumLayers = GetNumLayers(options);
+
if (options.feature_map_height_size()) {
if (options.strides_size()) {
LOG(ERROR) << "Found feature map shapes. Strides will be ignored.";
}
- CHECK_EQ(options.feature_map_height_size(), options.num_layers());
+ CHECK_EQ(options.feature_map_height_size(), kNumLayers);
CHECK_EQ(options.feature_map_height_size(),
options.feature_map_width_size());
} else {
- CHECK_EQ(options.strides_size(), options.num_layers());
+ CHECK_EQ(options.strides_size(), kNumLayers);
+ }
+
+ if (options.multiscale_anchor_generation()) {
+ return GenerateMultiScaleAnchors(anchors, options);
}
int layer_id = 0;
diff --git a/mediapipe/calculators/tflite/ssd_anchors_calculator.proto b/mediapipe/calculators/tflite/ssd_anchors_calculator.proto
index 911e4ac92..3b1e36700 100644
--- a/mediapipe/calculators/tflite/ssd_anchors_calculator.proto
+++ b/mediapipe/calculators/tflite/ssd_anchors_calculator.proto
@@ -60,4 +60,30 @@ message SsdAnchorsCalculatorOptions {
// This option can be used when the predicted anchor width and height are in
// pixels.
optional bool fixed_anchor_size = 14 [default = false];
+
+ // Generates grid anchors on the fly corresponding to multiple CNN layers as
+ // described in:
+ // "Focal Loss for Dense Object Detection" (https://arxiv.org/abs/1708.02002)
+ // T.-Y. Lin, P. Goyal, R. Girshick, K. He, P. Dollar
+ optional bool multiscale_anchor_generation = 15 [default = false];
+
+ // minimum level in feature pyramid
+ // for multiscale_anchor_generation only!
+ optional int32 min_level = 16 [default = 3];
+
+ // maximum level in feature pyramid
+ // for multiscale_anchor_generation only!
+ optional int32 max_level = 17 [default = 7];
+
+ // Scale of anchor to feature stride
+ // for multiscale_anchor_generation only!
+ optional float anchor_scale = 18 [default = 4.0];
+
+ // Number of intermediate scale each scale octave
+ // for multiscale_anchor_generation only!
+ optional int32 scales_per_octave = 19 [default = 2];
+
+ // Whether to produce anchors in normalized coordinates.
+ // for multiscale_anchor_generation only!
+ optional bool normalize_coordinates = 20 [default = true];
}
diff --git a/mediapipe/calculators/tflite/ssd_anchors_calculator_test.cc b/mediapipe/calculators/tflite/ssd_anchors_calculator_test.cc
index 3b72a287e..595dda8bc 100644
--- a/mediapipe/calculators/tflite/ssd_anchors_calculator_test.cc
+++ b/mediapipe/calculators/tflite/ssd_anchors_calculator_test.cc
@@ -33,9 +33,6 @@ std::string GetGoldenFilePath(const std::string& filename) {
void ParseAnchorsFromText(const std::string& text,
std::vector* anchors) {
- const std::string line_delimiter = "\n";
- const std::string number_delimiter = ",";
-
std::istringstream stream(text);
std::string line;
while (std::getline(stream, line)) {
@@ -64,6 +61,8 @@ void CompareAnchors(const std::vector& anchors_0,
testing::FloatNear(anchor_1.x_center(), 1e-5));
EXPECT_THAT(anchor_0.y_center(),
testing::FloatNear(anchor_1.y_center(), 1e-5));
+ EXPECT_THAT(anchor_0.h(), testing::FloatNear(anchor_1.h(), 1e-5));
+ EXPECT_THAT(anchor_0.w(), testing::FloatNear(anchor_1.w(), 1e-5));
}
}
@@ -148,4 +147,40 @@ TEST(SsdAnchorCalculatorTest, MobileSSDConfig) {
CompareAnchors(anchors, anchors_golden);
}
+TEST(SsdAnchorCalculatorTest, RetinaNetSSDConfig) {
+ CalculatorRunner runner(ParseTextProtoOrDie(R"pb(
+ calculator: "SsdAnchorsCalculator"
+ output_side_packet: "anchors"
+ options {
+ [mediapipe.SsdAnchorsCalculatorOptions.ext] {
+ input_size_height: 640
+ input_size_width: 640
+ strides: 64
+ strides: 128
+ aspect_ratios: 1.0
+ aspect_ratios: 2.0
+ aspect_ratios: 0.5
+ multiscale_anchor_generation: true
+ min_level: 6
+ max_level: 7
+ anchor_scale: 3.0
+ scales_per_octave: 3
+ }
+ }
+ )pb"));
+
+ MP_ASSERT_OK(runner.Run()) << "Calculator execution failed.";
+ const auto& anchors =
+ runner.OutputSidePackets().Index(0).Get>();
+
+ std::string anchors_string;
+ MP_EXPECT_OK(mediapipe::file::GetContents(
+ GetGoldenFilePath("anchor_golden_file_2.txt"), &anchors_string));
+
+ std::vector anchors_golden;
+ ParseAnchorsFromText(anchors_string, &anchors_golden);
+
+ CompareAnchors(anchors, anchors_golden);
+}
+
} // namespace mediapipe
diff --git a/mediapipe/calculators/tflite/testdata/anchor_golden_file_2.txt b/mediapipe/calculators/tflite/testdata/anchor_golden_file_2.txt
new file mode 100644
index 000000000..651d977f5
--- /dev/null
+++ b/mediapipe/calculators/tflite/testdata/anchor_golden_file_2.txt
@@ -0,0 +1,1125 @@
+0.05 0.05 0.3 0.3
+0.05 0.05 0.377976 0.377976
+0.05 0.05 0.47622 0.47622
+0.05 0.05 0.424264 0.212132
+0.05 0.05 0.534539 0.26727
+0.05 0.05 0.673477 0.336739
+0.05 0.05 0.212132 0.424264
+0.05 0.05 0.26727 0.534539
+0.05 0.05 0.336739 0.673477
+0.15 0.05 0.3 0.3
+0.15 0.05 0.377976 0.377976
+0.15 0.05 0.47622 0.47622
+0.15 0.05 0.424264 0.212132
+0.15 0.05 0.534539 0.26727
+0.15 0.05 0.673477 0.336739
+0.15 0.05 0.212132 0.424264
+0.15 0.05 0.26727 0.534539
+0.15 0.05 0.336739 0.673477
+0.25 0.05 0.3 0.3
+0.25 0.05 0.377976 0.377976
+0.25 0.05 0.47622 0.47622
+0.25 0.05 0.424264 0.212132
+0.25 0.05 0.534539 0.26727
+0.25 0.05 0.673477 0.336739
+0.25 0.05 0.212132 0.424264
+0.25 0.05 0.26727 0.534539
+0.25 0.05 0.336739 0.673477
+0.35 0.05 0.3 0.3
+0.35 0.05 0.377976 0.377976
+0.35 0.05 0.47622 0.47622
+0.35 0.05 0.424264 0.212132
+0.35 0.05 0.534539 0.26727
+0.35 0.05 0.673477 0.336739
+0.35 0.05 0.212132 0.424264
+0.35 0.05 0.26727 0.534539
+0.35 0.05 0.336739 0.673477
+0.45 0.05 0.3 0.3
+0.45 0.05 0.377976 0.377976
+0.45 0.05 0.47622 0.47622
+0.45 0.05 0.424264 0.212132
+0.45 0.05 0.534539 0.26727
+0.45 0.05 0.673477 0.336739
+0.45 0.05 0.212132 0.424264
+0.45 0.05 0.26727 0.534539
+0.45 0.05 0.336739 0.673477
+0.55 0.05 0.3 0.3
+0.55 0.05 0.377976 0.377976
+0.55 0.05 0.47622 0.47622
+0.55 0.05 0.424264 0.212132
+0.55 0.05 0.534539 0.26727
+0.55 0.05 0.673477 0.336739
+0.55 0.05 0.212132 0.424264
+0.55 0.05 0.26727 0.534539
+0.55 0.05 0.336739 0.673477
+0.65 0.05 0.3 0.3
+0.65 0.05 0.377976 0.377976
+0.65 0.05 0.47622 0.47622
+0.65 0.05 0.424264 0.212132
+0.65 0.05 0.534539 0.26727
+0.65 0.05 0.673477 0.336739
+0.65 0.05 0.212132 0.424264
+0.65 0.05 0.26727 0.534539
+0.65 0.05 0.336739 0.673477
+0.75 0.05 0.3 0.3
+0.75 0.05 0.377976 0.377976
+0.75 0.05 0.47622 0.47622
+0.75 0.05 0.424264 0.212132
+0.75 0.05 0.534539 0.26727
+0.75 0.05 0.673477 0.336739
+0.75 0.05 0.212132 0.424264
+0.75 0.05 0.26727 0.534539
+0.75 0.05 0.336739 0.673477
+0.85 0.05 0.3 0.3
+0.85 0.05 0.377976 0.377976
+0.85 0.05 0.47622 0.47622
+0.85 0.05 0.424264 0.212132
+0.85 0.05 0.534539 0.26727
+0.85 0.05 0.673477 0.336739
+0.85 0.05 0.212132 0.424264
+0.85 0.05 0.26727 0.534539
+0.85 0.05 0.336739 0.673477
+0.95 0.05 0.3 0.3
+0.95 0.05 0.377976 0.377976
+0.95 0.05 0.47622 0.47622
+0.95 0.05 0.424264 0.212132
+0.95 0.05 0.534539 0.26727
+0.95 0.05 0.673477 0.336739
+0.95 0.05 0.212132 0.424264
+0.95 0.05 0.267269 0.534539
+0.95 0.05 0.336739 0.673477
+0.05 0.15 0.3 0.3
+0.05 0.15 0.377976 0.377976
+0.05 0.15 0.47622 0.47622
+0.05 0.15 0.424264 0.212132
+0.05 0.15 0.534539 0.26727
+0.05 0.15 0.673477 0.336739
+0.05 0.15 0.212132 0.424264
+0.05 0.15 0.26727 0.534539
+0.05 0.15 0.336739 0.673477
+0.15 0.15 0.3 0.3
+0.15 0.15 0.377976 0.377976
+0.15 0.15 0.47622 0.47622
+0.15 0.15 0.424264 0.212132
+0.15 0.15 0.534539 0.26727
+0.15 0.15 0.673477 0.336739
+0.15 0.15 0.212132 0.424264
+0.15 0.15 0.26727 0.534539
+0.15 0.15 0.336739 0.673477
+0.25 0.15 0.3 0.3
+0.25 0.15 0.377976 0.377976
+0.25 0.15 0.47622 0.47622
+0.25 0.15 0.424264 0.212132
+0.25 0.15 0.534539 0.26727
+0.25 0.15 0.673477 0.336739
+0.25 0.15 0.212132 0.424264
+0.25 0.15 0.26727 0.534539
+0.25 0.15 0.336739 0.673477
+0.35 0.15 0.3 0.3
+0.35 0.15 0.377976 0.377976
+0.35 0.15 0.47622 0.47622
+0.35 0.15 0.424264 0.212132
+0.35 0.15 0.534539 0.26727
+0.35 0.15 0.673477 0.336739
+0.35 0.15 0.212132 0.424264
+0.35 0.15 0.26727 0.534539
+0.35 0.15 0.336739 0.673477
+0.45 0.15 0.3 0.3
+0.45 0.15 0.377976 0.377976
+0.45 0.15 0.47622 0.47622
+0.45 0.15 0.424264 0.212132
+0.45 0.15 0.534539 0.26727
+0.45 0.15 0.673477 0.336739
+0.45 0.15 0.212132 0.424264
+0.45 0.15 0.26727 0.534539
+0.45 0.15 0.336739 0.673477
+0.55 0.15 0.3 0.3
+0.55 0.15 0.377976 0.377976
+0.55 0.15 0.47622 0.47622
+0.55 0.15 0.424264 0.212132
+0.55 0.15 0.534539 0.26727
+0.55 0.15 0.673477 0.336739
+0.55 0.15 0.212132 0.424264
+0.55 0.15 0.26727 0.534539
+0.55 0.15 0.336739 0.673477
+0.65 0.15 0.3 0.3
+0.65 0.15 0.377976 0.377976
+0.65 0.15 0.47622 0.47622
+0.65 0.15 0.424264 0.212132
+0.65 0.15 0.534539 0.26727
+0.65 0.15 0.673477 0.336739
+0.65 0.15 0.212132 0.424264
+0.65 0.15 0.26727 0.534539
+0.65 0.15 0.336739 0.673477
+0.75 0.15 0.3 0.3
+0.75 0.15 0.377976 0.377976
+0.75 0.15 0.47622 0.47622
+0.75 0.15 0.424264 0.212132
+0.75 0.15 0.534539 0.26727
+0.75 0.15 0.673477 0.336739
+0.75 0.15 0.212132 0.424264
+0.75 0.15 0.26727 0.534539
+0.75 0.15 0.336739 0.673477
+0.85 0.15 0.3 0.3
+0.85 0.15 0.377976 0.377976
+0.85 0.15 0.47622 0.47622
+0.85 0.15 0.424264 0.212132
+0.85 0.15 0.534539 0.26727
+0.85 0.15 0.673477 0.336739
+0.85 0.15 0.212132 0.424264
+0.85 0.15 0.26727 0.534539
+0.85 0.15 0.336739 0.673477
+0.95 0.15 0.3 0.3
+0.95 0.15 0.377976 0.377976
+0.95 0.15 0.47622 0.47622
+0.95 0.15 0.424264 0.212132
+0.95 0.15 0.534539 0.26727
+0.95 0.15 0.673477 0.336739
+0.95 0.15 0.212132 0.424264
+0.95 0.15 0.267269 0.534539
+0.95 0.15 0.336739 0.673477
+0.05 0.25 0.3 0.3
+0.05 0.25 0.377976 0.377976
+0.05 0.25 0.47622 0.47622
+0.05 0.25 0.424264 0.212132
+0.05 0.25 0.534539 0.26727
+0.05 0.25 0.673477 0.336739
+0.05 0.25 0.212132 0.424264
+0.05 0.25 0.26727 0.534539
+0.05 0.25 0.336739 0.673477
+0.15 0.25 0.3 0.3
+0.15 0.25 0.377976 0.377976
+0.15 0.25 0.47622 0.47622
+0.15 0.25 0.424264 0.212132
+0.15 0.25 0.534539 0.26727
+0.15 0.25 0.673477 0.336739
+0.15 0.25 0.212132 0.424264
+0.15 0.25 0.26727 0.534539
+0.15 0.25 0.336739 0.673477
+0.25 0.25 0.3 0.3
+0.25 0.25 0.377976 0.377976
+0.25 0.25 0.47622 0.47622
+0.25 0.25 0.424264 0.212132
+0.25 0.25 0.534539 0.26727
+0.25 0.25 0.673477 0.336739
+0.25 0.25 0.212132 0.424264
+0.25 0.25 0.26727 0.534539
+0.25 0.25 0.336739 0.673477
+0.35 0.25 0.3 0.3
+0.35 0.25 0.377976 0.377976
+0.35 0.25 0.47622 0.47622
+0.35 0.25 0.424264 0.212132
+0.35 0.25 0.534539 0.26727
+0.35 0.25 0.673477 0.336739
+0.35 0.25 0.212132 0.424264
+0.35 0.25 0.26727 0.534539
+0.35 0.25 0.336739 0.673477
+0.45 0.25 0.3 0.3
+0.45 0.25 0.377976 0.377976
+0.45 0.25 0.47622 0.47622
+0.45 0.25 0.424264 0.212132
+0.45 0.25 0.534539 0.26727
+0.45 0.25 0.673477 0.336739
+0.45 0.25 0.212132 0.424264
+0.45 0.25 0.26727 0.534539
+0.45 0.25 0.336739 0.673477
+0.55 0.25 0.3 0.3
+0.55 0.25 0.377976 0.377976
+0.55 0.25 0.47622 0.47622
+0.55 0.25 0.424264 0.212132
+0.55 0.25 0.534539 0.26727
+0.55 0.25 0.673477 0.336739
+0.55 0.25 0.212132 0.424264
+0.55 0.25 0.26727 0.534539
+0.55 0.25 0.336739 0.673477
+0.65 0.25 0.3 0.3
+0.65 0.25 0.377976 0.377976
+0.65 0.25 0.47622 0.47622
+0.65 0.25 0.424264 0.212132
+0.65 0.25 0.534539 0.26727
+0.65 0.25 0.673477 0.336739
+0.65 0.25 0.212132 0.424264
+0.65 0.25 0.26727 0.534539
+0.65 0.25 0.336739 0.673477
+0.75 0.25 0.3 0.3
+0.75 0.25 0.377976 0.377976
+0.75 0.25 0.47622 0.47622
+0.75 0.25 0.424264 0.212132
+0.75 0.25 0.534539 0.26727
+0.75 0.25 0.673477 0.336739
+0.75 0.25 0.212132 0.424264
+0.75 0.25 0.26727 0.534539
+0.75 0.25 0.336739 0.673477
+0.85 0.25 0.3 0.3
+0.85 0.25 0.377976 0.377976
+0.85 0.25 0.47622 0.47622
+0.85 0.25 0.424264 0.212132
+0.85 0.25 0.534539 0.26727
+0.85 0.25 0.673477 0.336739
+0.85 0.25 0.212132 0.424264
+0.85 0.25 0.26727 0.534539
+0.85 0.25 0.336739 0.673477
+0.95 0.25 0.3 0.3
+0.95 0.25 0.377976 0.377976
+0.95 0.25 0.47622 0.47622
+0.95 0.25 0.424264 0.212132
+0.95 0.25 0.534539 0.26727
+0.95 0.25 0.673477 0.336739
+0.95 0.25 0.212132 0.424264
+0.95 0.25 0.267269 0.534539
+0.95 0.25 0.336739 0.673477
+0.05 0.35 0.3 0.3
+0.05 0.35 0.377976 0.377976
+0.05 0.35 0.47622 0.47622
+0.05 0.35 0.424264 0.212132
+0.05 0.35 0.534539 0.26727
+0.05 0.35 0.673477 0.336739
+0.05 0.35 0.212132 0.424264
+0.05 0.35 0.26727 0.534539
+0.05 0.35 0.336739 0.673477
+0.15 0.35 0.3 0.3
+0.15 0.35 0.377976 0.377976
+0.15 0.35 0.47622 0.47622
+0.15 0.35 0.424264 0.212132
+0.15 0.35 0.534539 0.26727
+0.15 0.35 0.673477 0.336739
+0.15 0.35 0.212132 0.424264
+0.15 0.35 0.26727 0.534539
+0.15 0.35 0.336739 0.673477
+0.25 0.35 0.3 0.3
+0.25 0.35 0.377976 0.377976
+0.25 0.35 0.47622 0.47622
+0.25 0.35 0.424264 0.212132
+0.25 0.35 0.534539 0.26727
+0.25 0.35 0.673477 0.336739
+0.25 0.35 0.212132 0.424264
+0.25 0.35 0.26727 0.534539
+0.25 0.35 0.336739 0.673477
+0.35 0.35 0.3 0.3
+0.35 0.35 0.377976 0.377976
+0.35 0.35 0.47622 0.47622
+0.35 0.35 0.424264 0.212132
+0.35 0.35 0.534539 0.26727
+0.35 0.35 0.673477 0.336739
+0.35 0.35 0.212132 0.424264
+0.35 0.35 0.26727 0.534539
+0.35 0.35 0.336739 0.673477
+0.45 0.35 0.3 0.3
+0.45 0.35 0.377976 0.377976
+0.45 0.35 0.47622 0.47622
+0.45 0.35 0.424264 0.212132
+0.45 0.35 0.534539 0.26727
+0.45 0.35 0.673477 0.336739
+0.45 0.35 0.212132 0.424264
+0.45 0.35 0.26727 0.534539
+0.45 0.35 0.336739 0.673477
+0.55 0.35 0.3 0.3
+0.55 0.35 0.377976 0.377976
+0.55 0.35 0.47622 0.47622
+0.55 0.35 0.424264 0.212132
+0.55 0.35 0.534539 0.26727
+0.55 0.35 0.673477 0.336739
+0.55 0.35 0.212132 0.424264
+0.55 0.35 0.26727 0.534539
+0.55 0.35 0.336739 0.673477
+0.65 0.35 0.3 0.3
+0.65 0.35 0.377976 0.377976
+0.65 0.35 0.47622 0.47622
+0.65 0.35 0.424264 0.212132
+0.65 0.35 0.534539 0.26727
+0.65 0.35 0.673477 0.336739
+0.65 0.35 0.212132 0.424264
+0.65 0.35 0.26727 0.534539
+0.65 0.35 0.336739 0.673477
+0.75 0.35 0.3 0.3
+0.75 0.35 0.377976 0.377976
+0.75 0.35 0.47622 0.47622
+0.75 0.35 0.424264 0.212132
+0.75 0.35 0.534539 0.26727
+0.75 0.35 0.673477 0.336739
+0.75 0.35 0.212132 0.424264
+0.75 0.35 0.26727 0.534539
+0.75 0.35 0.336739 0.673477
+0.85 0.35 0.3 0.3
+0.85 0.35 0.377976 0.377976
+0.85 0.35 0.47622 0.47622
+0.85 0.35 0.424264 0.212132
+0.85 0.35 0.534539 0.26727
+0.85 0.35 0.673477 0.336739
+0.85 0.35 0.212132 0.424264
+0.85 0.35 0.26727 0.534539
+0.85 0.35 0.336739 0.673477
+0.95 0.35 0.3 0.3
+0.95 0.35 0.377976 0.377976
+0.95 0.35 0.47622 0.47622
+0.95 0.35 0.424264 0.212132
+0.95 0.35 0.534539 0.26727
+0.95 0.35 0.673477 0.336739
+0.95 0.35 0.212132 0.424264
+0.95 0.35 0.267269 0.534539
+0.95 0.35 0.336739 0.673477
+0.05 0.45 0.3 0.3
+0.05 0.45 0.377976 0.377976
+0.05 0.45 0.47622 0.47622
+0.05 0.45 0.424264 0.212132
+0.05 0.45 0.534539 0.26727
+0.05 0.45 0.673477 0.336739
+0.05 0.45 0.212132 0.424264
+0.05 0.45 0.26727 0.534539
+0.05 0.45 0.336739 0.673477
+0.15 0.45 0.3 0.3
+0.15 0.45 0.377976 0.377976
+0.15 0.45 0.47622 0.47622
+0.15 0.45 0.424264 0.212132
+0.15 0.45 0.534539 0.26727
+0.15 0.45 0.673477 0.336739
+0.15 0.45 0.212132 0.424264
+0.15 0.45 0.26727 0.534539
+0.15 0.45 0.336739 0.673477
+0.25 0.45 0.3 0.3
+0.25 0.45 0.377976 0.377976
+0.25 0.45 0.47622 0.47622
+0.25 0.45 0.424264 0.212132
+0.25 0.45 0.534539 0.26727
+0.25 0.45 0.673477 0.336739
+0.25 0.45 0.212132 0.424264
+0.25 0.45 0.26727 0.534539
+0.25 0.45 0.336739 0.673477
+0.35 0.45 0.3 0.3
+0.35 0.45 0.377976 0.377976
+0.35 0.45 0.47622 0.47622
+0.35 0.45 0.424264 0.212132
+0.35 0.45 0.534539 0.26727
+0.35 0.45 0.673477 0.336739
+0.35 0.45 0.212132 0.424264
+0.35 0.45 0.26727 0.534539
+0.35 0.45 0.336739 0.673477
+0.45 0.45 0.3 0.3
+0.45 0.45 0.377976 0.377976
+0.45 0.45 0.47622 0.47622
+0.45 0.45 0.424264 0.212132
+0.45 0.45 0.534539 0.26727
+0.45 0.45 0.673477 0.336739
+0.45 0.45 0.212132 0.424264
+0.45 0.45 0.26727 0.534539
+0.45 0.45 0.336739 0.673477
+0.55 0.45 0.3 0.3
+0.55 0.45 0.377976 0.377976
+0.55 0.45 0.47622 0.47622
+0.55 0.45 0.424264 0.212132
+0.55 0.45 0.534539 0.26727
+0.55 0.45 0.673477 0.336739
+0.55 0.45 0.212132 0.424264
+0.55 0.45 0.26727 0.534539
+0.55 0.45 0.336739 0.673477
+0.65 0.45 0.3 0.3
+0.65 0.45 0.377976 0.377976
+0.65 0.45 0.47622 0.47622
+0.65 0.45 0.424264 0.212132
+0.65 0.45 0.534539 0.26727
+0.65 0.45 0.673477 0.336739
+0.65 0.45 0.212132 0.424264
+0.65 0.45 0.26727 0.534539
+0.65 0.45 0.336739 0.673477
+0.75 0.45 0.3 0.3
+0.75 0.45 0.377976 0.377976
+0.75 0.45 0.47622 0.47622
+0.75 0.45 0.424264 0.212132
+0.75 0.45 0.534539 0.26727
+0.75 0.45 0.673477 0.336739
+0.75 0.45 0.212132 0.424264
+0.75 0.45 0.26727 0.534539
+0.75 0.45 0.336739 0.673477
+0.85 0.45 0.3 0.3
+0.85 0.45 0.377976 0.377976
+0.85 0.45 0.47622 0.47622
+0.85 0.45 0.424264 0.212132
+0.85 0.45 0.534539 0.26727
+0.85 0.45 0.673477 0.336739
+0.85 0.45 0.212132 0.424264
+0.85 0.45 0.26727 0.534539
+0.85 0.45 0.336739 0.673477
+0.95 0.45 0.3 0.3
+0.95 0.45 0.377976 0.377976
+0.95 0.45 0.47622 0.47622
+0.95 0.45 0.424264 0.212132
+0.95 0.45 0.534539 0.26727
+0.95 0.45 0.673477 0.336739
+0.95 0.45 0.212132 0.424264
+0.95 0.45 0.267269 0.534539
+0.95 0.45 0.336739 0.673477
+0.05 0.55 0.3 0.3
+0.05 0.55 0.377976 0.377976
+0.05 0.55 0.47622 0.47622
+0.05 0.55 0.424264 0.212132
+0.05 0.55 0.534539 0.26727
+0.05 0.55 0.673477 0.336739
+0.05 0.55 0.212132 0.424264
+0.05 0.55 0.26727 0.534539
+0.05 0.55 0.336739 0.673477
+0.15 0.55 0.3 0.3
+0.15 0.55 0.377976 0.377976
+0.15 0.55 0.47622 0.47622
+0.15 0.55 0.424264 0.212132
+0.15 0.55 0.534539 0.26727
+0.15 0.55 0.673477 0.336739
+0.15 0.55 0.212132 0.424264
+0.15 0.55 0.26727 0.534539
+0.15 0.55 0.336739 0.673477
+0.25 0.55 0.3 0.3
+0.25 0.55 0.377976 0.377976
+0.25 0.55 0.47622 0.47622
+0.25 0.55 0.424264 0.212132
+0.25 0.55 0.534539 0.26727
+0.25 0.55 0.673477 0.336739
+0.25 0.55 0.212132 0.424264
+0.25 0.55 0.26727 0.534539
+0.25 0.55 0.336739 0.673477
+0.35 0.55 0.3 0.3
+0.35 0.55 0.377976 0.377976
+0.35 0.55 0.47622 0.47622
+0.35 0.55 0.424264 0.212132
+0.35 0.55 0.534539 0.26727
+0.35 0.55 0.673477 0.336739
+0.35 0.55 0.212132 0.424264
+0.35 0.55 0.26727 0.534539
+0.35 0.55 0.336739 0.673477
+0.45 0.55 0.3 0.3
+0.45 0.55 0.377976 0.377976
+0.45 0.55 0.47622 0.47622
+0.45 0.55 0.424264 0.212132
+0.45 0.55 0.534539 0.26727
+0.45 0.55 0.673477 0.336739
+0.45 0.55 0.212132 0.424264
+0.45 0.55 0.26727 0.534539
+0.45 0.55 0.336739 0.673477
+0.55 0.55 0.3 0.3
+0.55 0.55 0.377976 0.377976
+0.55 0.55 0.47622 0.47622
+0.55 0.55 0.424264 0.212132
+0.55 0.55 0.534539 0.26727
+0.55 0.55 0.673477 0.336739
+0.55 0.55 0.212132 0.424264
+0.55 0.55 0.26727 0.534539
+0.55 0.55 0.336739 0.673477
+0.65 0.55 0.3 0.3
+0.65 0.55 0.377976 0.377976
+0.65 0.55 0.47622 0.47622
+0.65 0.55 0.424264 0.212132
+0.65 0.55 0.534539 0.26727
+0.65 0.55 0.673477 0.336739
+0.65 0.55 0.212132 0.424264
+0.65 0.55 0.26727 0.534539
+0.65 0.55 0.336739 0.673477
+0.75 0.55 0.3 0.3
+0.75 0.55 0.377976 0.377976
+0.75 0.55 0.47622 0.47622
+0.75 0.55 0.424264 0.212132
+0.75 0.55 0.534539 0.26727
+0.75 0.55 0.673477 0.336739
+0.75 0.55 0.212132 0.424264
+0.75 0.55 0.26727 0.534539
+0.75 0.55 0.336739 0.673477
+0.85 0.55 0.3 0.3
+0.85 0.55 0.377976 0.377976
+0.85 0.55 0.47622 0.47622
+0.85 0.55 0.424264 0.212132
+0.85 0.55 0.534539 0.26727
+0.85 0.55 0.673477 0.336739
+0.85 0.55 0.212132 0.424264
+0.85 0.55 0.26727 0.534539
+0.85 0.55 0.336739 0.673477
+0.95 0.55 0.3 0.3
+0.95 0.55 0.377976 0.377976
+0.95 0.55 0.47622 0.47622
+0.95 0.55 0.424264 0.212132
+0.95 0.55 0.534539 0.26727
+0.95 0.55 0.673477 0.336739
+0.95 0.55 0.212132 0.424264
+0.95 0.55 0.267269 0.534539
+0.95 0.55 0.336739 0.673477
+0.05 0.65 0.3 0.3
+0.05 0.65 0.377976 0.377976
+0.05 0.65 0.47622 0.47622
+0.05 0.65 0.424264 0.212132
+0.05 0.65 0.534539 0.26727
+0.05 0.65 0.673477 0.336739
+0.05 0.65 0.212132 0.424264
+0.05 0.65 0.26727 0.534539
+0.05 0.65 0.336739 0.673477
+0.15 0.65 0.3 0.3
+0.15 0.65 0.377976 0.377976
+0.15 0.65 0.47622 0.47622
+0.15 0.65 0.424264 0.212132
+0.15 0.65 0.534539 0.26727
+0.15 0.65 0.673477 0.336739
+0.15 0.65 0.212132 0.424264
+0.15 0.65 0.26727 0.534539
+0.15 0.65 0.336739 0.673477
+0.25 0.65 0.3 0.3
+0.25 0.65 0.377976 0.377976
+0.25 0.65 0.47622 0.47622
+0.25 0.65 0.424264 0.212132
+0.25 0.65 0.534539 0.26727
+0.25 0.65 0.673477 0.336739
+0.25 0.65 0.212132 0.424264
+0.25 0.65 0.26727 0.534539
+0.25 0.65 0.336739 0.673477
+0.35 0.65 0.3 0.3
+0.35 0.65 0.377976 0.377976
+0.35 0.65 0.47622 0.47622
+0.35 0.65 0.424264 0.212132
+0.35 0.65 0.534539 0.26727
+0.35 0.65 0.673477 0.336739
+0.35 0.65 0.212132 0.424264
+0.35 0.65 0.26727 0.534539
+0.35 0.65 0.336739 0.673477
+0.45 0.65 0.3 0.3
+0.45 0.65 0.377976 0.377976
+0.45 0.65 0.47622 0.47622
+0.45 0.65 0.424264 0.212132
+0.45 0.65 0.534539 0.26727
+0.45 0.65 0.673477 0.336739
+0.45 0.65 0.212132 0.424264
+0.45 0.65 0.26727 0.534539
+0.45 0.65 0.336739 0.673477
+0.55 0.65 0.3 0.3
+0.55 0.65 0.377976 0.377976
+0.55 0.65 0.47622 0.47622
+0.55 0.65 0.424264 0.212132
+0.55 0.65 0.534539 0.26727
+0.55 0.65 0.673477 0.336739
+0.55 0.65 0.212132 0.424264
+0.55 0.65 0.26727 0.534539
+0.55 0.65 0.336739 0.673477
+0.65 0.65 0.3 0.3
+0.65 0.65 0.377976 0.377976
+0.65 0.65 0.47622 0.47622
+0.65 0.65 0.424264 0.212132
+0.65 0.65 0.534539 0.26727
+0.65 0.65 0.673477 0.336739
+0.65 0.65 0.212132 0.424264
+0.65 0.65 0.26727 0.534539
+0.65 0.65 0.336739 0.673477
+0.75 0.65 0.3 0.3
+0.75 0.65 0.377976 0.377976
+0.75 0.65 0.47622 0.47622
+0.75 0.65 0.424264 0.212132
+0.75 0.65 0.534539 0.26727
+0.75 0.65 0.673477 0.336739
+0.75 0.65 0.212132 0.424264
+0.75 0.65 0.26727 0.534539
+0.75 0.65 0.336739 0.673477
+0.85 0.65 0.3 0.3
+0.85 0.65 0.377976 0.377976
+0.85 0.65 0.47622 0.47622
+0.85 0.65 0.424264 0.212132
+0.85 0.65 0.534539 0.26727
+0.85 0.65 0.673477 0.336739
+0.85 0.65 0.212132 0.424264
+0.85 0.65 0.26727 0.534539
+0.85 0.65 0.336739 0.673477
+0.95 0.65 0.3 0.3
+0.95 0.65 0.377976 0.377976
+0.95 0.65 0.47622 0.47622
+0.95 0.65 0.424264 0.212132
+0.95 0.65 0.534539 0.26727
+0.95 0.65 0.673477 0.336739
+0.95 0.65 0.212132 0.424264
+0.95 0.65 0.267269 0.534539
+0.95 0.65 0.336739 0.673477
+0.05 0.75 0.3 0.3
+0.05 0.75 0.377976 0.377976
+0.05 0.75 0.47622 0.47622
+0.05 0.75 0.424264 0.212132
+0.05 0.75 0.534539 0.26727
+0.05 0.75 0.673477 0.336739
+0.05 0.75 0.212132 0.424264
+0.05 0.75 0.26727 0.534539
+0.05 0.75 0.336739 0.673477
+0.15 0.75 0.3 0.3
+0.15 0.75 0.377976 0.377976
+0.15 0.75 0.47622 0.47622
+0.15 0.75 0.424264 0.212132
+0.15 0.75 0.534539 0.26727
+0.15 0.75 0.673477 0.336739
+0.15 0.75 0.212132 0.424264
+0.15 0.75 0.26727 0.534539
+0.15 0.75 0.336739 0.673477
+0.25 0.75 0.3 0.3
+0.25 0.75 0.377976 0.377976
+0.25 0.75 0.47622 0.47622
+0.25 0.75 0.424264 0.212132
+0.25 0.75 0.534539 0.26727
+0.25 0.75 0.673477 0.336739
+0.25 0.75 0.212132 0.424264
+0.25 0.75 0.26727 0.534539
+0.25 0.75 0.336739 0.673477
+0.35 0.75 0.3 0.3
+0.35 0.75 0.377976 0.377976
+0.35 0.75 0.47622 0.47622
+0.35 0.75 0.424264 0.212132
+0.35 0.75 0.534539 0.26727
+0.35 0.75 0.673477 0.336739
+0.35 0.75 0.212132 0.424264
+0.35 0.75 0.26727 0.534539
+0.35 0.75 0.336739 0.673477
+0.45 0.75 0.3 0.3
+0.45 0.75 0.377976 0.377976
+0.45 0.75 0.47622 0.47622
+0.45 0.75 0.424264 0.212132
+0.45 0.75 0.534539 0.26727
+0.45 0.75 0.673477 0.336739
+0.45 0.75 0.212132 0.424264
+0.45 0.75 0.26727 0.534539
+0.45 0.75 0.336739 0.673477
+0.55 0.75 0.3 0.3
+0.55 0.75 0.377976 0.377976
+0.55 0.75 0.47622 0.47622
+0.55 0.75 0.424264 0.212132
+0.55 0.75 0.534539 0.26727
+0.55 0.75 0.673477 0.336739
+0.55 0.75 0.212132 0.424264
+0.55 0.75 0.26727 0.534539
+0.55 0.75 0.336739 0.673477
+0.65 0.75 0.3 0.3
+0.65 0.75 0.377976 0.377976
+0.65 0.75 0.47622 0.47622
+0.65 0.75 0.424264 0.212132
+0.65 0.75 0.534539 0.26727
+0.65 0.75 0.673477 0.336739
+0.65 0.75 0.212132 0.424264
+0.65 0.75 0.26727 0.534539
+0.65 0.75 0.336739 0.673477
+0.75 0.75 0.3 0.3
+0.75 0.75 0.377976 0.377976
+0.75 0.75 0.47622 0.47622
+0.75 0.75 0.424264 0.212132
+0.75 0.75 0.534539 0.26727
+0.75 0.75 0.673477 0.336739
+0.75 0.75 0.212132 0.424264
+0.75 0.75 0.26727 0.534539
+0.75 0.75 0.336739 0.673477
+0.85 0.75 0.3 0.3
+0.85 0.75 0.377976 0.377976
+0.85 0.75 0.47622 0.47622
+0.85 0.75 0.424264 0.212132
+0.85 0.75 0.534539 0.26727
+0.85 0.75 0.673477 0.336739
+0.85 0.75 0.212132 0.424264
+0.85 0.75 0.26727 0.534539
+0.85 0.75 0.336739 0.673477
+0.95 0.75 0.3 0.3
+0.95 0.75 0.377976 0.377976
+0.95 0.75 0.47622 0.47622
+0.95 0.75 0.424264 0.212132
+0.95 0.75 0.534539 0.26727
+0.95 0.75 0.673477 0.336739
+0.95 0.75 0.212132 0.424264
+0.95 0.75 0.267269 0.534539
+0.95 0.75 0.336739 0.673477
+0.05 0.85 0.3 0.3
+0.05 0.85 0.377976 0.377976
+0.05 0.85 0.47622 0.47622
+0.05 0.85 0.424264 0.212132
+0.05 0.85 0.534539 0.26727
+0.05 0.85 0.673477 0.336739
+0.05 0.85 0.212132 0.424264
+0.05 0.85 0.26727 0.534539
+0.05 0.85 0.336739 0.673477
+0.15 0.85 0.3 0.3
+0.15 0.85 0.377976 0.377976
+0.15 0.85 0.47622 0.47622
+0.15 0.85 0.424264 0.212132
+0.15 0.85 0.534539 0.26727
+0.15 0.85 0.673477 0.336739
+0.15 0.85 0.212132 0.424264
+0.15 0.85 0.26727 0.534539
+0.15 0.85 0.336739 0.673477
+0.25 0.85 0.3 0.3
+0.25 0.85 0.377976 0.377976
+0.25 0.85 0.47622 0.47622
+0.25 0.85 0.424264 0.212132
+0.25 0.85 0.534539 0.26727
+0.25 0.85 0.673477 0.336739
+0.25 0.85 0.212132 0.424264
+0.25 0.85 0.26727 0.534539
+0.25 0.85 0.336739 0.673477
+0.35 0.85 0.3 0.3
+0.35 0.85 0.377976 0.377976
+0.35 0.85 0.47622 0.47622
+0.35 0.85 0.424264 0.212132
+0.35 0.85 0.534539 0.26727
+0.35 0.85 0.673477 0.336739
+0.35 0.85 0.212132 0.424264
+0.35 0.85 0.26727 0.534539
+0.35 0.85 0.336739 0.673477
+0.45 0.85 0.3 0.3
+0.45 0.85 0.377976 0.377976
+0.45 0.85 0.47622 0.47622
+0.45 0.85 0.424264 0.212132
+0.45 0.85 0.534539 0.26727
+0.45 0.85 0.673477 0.336739
+0.45 0.85 0.212132 0.424264
+0.45 0.85 0.26727 0.534539
+0.45 0.85 0.336739 0.673477
+0.55 0.85 0.3 0.3
+0.55 0.85 0.377976 0.377976
+0.55 0.85 0.47622 0.47622
+0.55 0.85 0.424264 0.212132
+0.55 0.85 0.534539 0.26727
+0.55 0.85 0.673477 0.336739
+0.55 0.85 0.212132 0.424264
+0.55 0.85 0.26727 0.534539
+0.55 0.85 0.336739 0.673477
+0.65 0.85 0.3 0.3
+0.65 0.85 0.377976 0.377976
+0.65 0.85 0.47622 0.47622
+0.65 0.85 0.424264 0.212132
+0.65 0.85 0.534539 0.26727
+0.65 0.85 0.673477 0.336739
+0.65 0.85 0.212132 0.424264
+0.65 0.85 0.26727 0.534539
+0.65 0.85 0.336739 0.673477
+0.75 0.85 0.3 0.3
+0.75 0.85 0.377976 0.377976
+0.75 0.85 0.47622 0.47622
+0.75 0.85 0.424264 0.212132
+0.75 0.85 0.534539 0.26727
+0.75 0.85 0.673477 0.336739
+0.75 0.85 0.212132 0.424264
+0.75 0.85 0.26727 0.534539
+0.75 0.85 0.336739 0.673477
+0.85 0.85 0.3 0.3
+0.85 0.85 0.377976 0.377976
+0.85 0.85 0.47622 0.47622
+0.85 0.85 0.424264 0.212132
+0.85 0.85 0.534539 0.26727
+0.85 0.85 0.673477 0.336739
+0.85 0.85 0.212132 0.424264
+0.85 0.85 0.26727 0.534539
+0.85 0.85 0.336739 0.673477
+0.95 0.85 0.3 0.3
+0.95 0.85 0.377976 0.377976
+0.95 0.85 0.47622 0.47622
+0.95 0.85 0.424264 0.212132
+0.95 0.85 0.534539 0.26727
+0.95 0.85 0.673477 0.336739
+0.95 0.85 0.212132 0.424264
+0.95 0.85 0.267269 0.534539
+0.95 0.85 0.336739 0.673477
+0.05 0.95 0.3 0.3
+0.05 0.95 0.377976 0.377976
+0.05 0.95 0.47622 0.47622
+0.05 0.95 0.424264 0.212132
+0.05 0.95 0.534539 0.26727
+0.05 0.95 0.673477 0.336739
+0.05 0.95 0.212132 0.424264
+0.05 0.95 0.26727 0.534539
+0.05 0.95 0.336739 0.673477
+0.15 0.95 0.3 0.3
+0.15 0.95 0.377976 0.377976
+0.15 0.95 0.47622 0.47622
+0.15 0.95 0.424264 0.212132
+0.15 0.95 0.534539 0.26727
+0.15 0.95 0.673477 0.336739
+0.15 0.95 0.212132 0.424264
+0.15 0.95 0.26727 0.534539
+0.15 0.95 0.336739 0.673477
+0.25 0.95 0.3 0.3
+0.25 0.95 0.377976 0.377976
+0.25 0.95 0.47622 0.47622
+0.25 0.95 0.424264 0.212132
+0.25 0.95 0.534539 0.26727
+0.25 0.95 0.673477 0.336739
+0.25 0.95 0.212132 0.424264
+0.25 0.95 0.26727 0.534539
+0.25 0.95 0.336739 0.673477
+0.35 0.95 0.3 0.3
+0.35 0.95 0.377976 0.377976
+0.35 0.95 0.47622 0.47622
+0.35 0.95 0.424264 0.212132
+0.35 0.95 0.534539 0.26727
+0.35 0.95 0.673477 0.336739
+0.35 0.95 0.212132 0.424264
+0.35 0.95 0.26727 0.534539
+0.35 0.95 0.336739 0.673477
+0.45 0.95 0.3 0.3
+0.45 0.95 0.377976 0.377976
+0.45 0.95 0.47622 0.47622
+0.45 0.95 0.424264 0.212132
+0.45 0.95 0.534539 0.26727
+0.45 0.95 0.673477 0.336739
+0.45 0.95 0.212132 0.424264
+0.45 0.95 0.26727 0.534539
+0.45 0.95 0.336739 0.673477
+0.55 0.95 0.3 0.3
+0.55 0.95 0.377976 0.377976
+0.55 0.95 0.47622 0.47622
+0.55 0.95 0.424264 0.212132
+0.55 0.95 0.534539 0.26727
+0.55 0.95 0.673477 0.336739
+0.55 0.95 0.212132 0.424264
+0.55 0.95 0.26727 0.534539
+0.55 0.95 0.336739 0.673477
+0.65 0.95 0.3 0.3
+0.65 0.95 0.377976 0.377976
+0.65 0.95 0.47622 0.47622
+0.65 0.95 0.424264 0.212132
+0.65 0.95 0.534539 0.26727
+0.65 0.95 0.673477 0.336739
+0.65 0.95 0.212132 0.424264
+0.65 0.95 0.26727 0.534539
+0.65 0.95 0.336739 0.673477
+0.75 0.95 0.3 0.3
+0.75 0.95 0.377976 0.377976
+0.75 0.95 0.47622 0.47622
+0.75 0.95 0.424264 0.212132
+0.75 0.95 0.534539 0.26727
+0.75 0.95 0.673477 0.336739
+0.75 0.95 0.212132 0.424264
+0.75 0.95 0.26727 0.534539
+0.75 0.95 0.336739 0.673477
+0.85 0.95 0.3 0.3
+0.85 0.95 0.377976 0.377976
+0.85 0.95 0.47622 0.47622
+0.85 0.95 0.424264 0.212132
+0.85 0.95 0.534539 0.26727
+0.85 0.95 0.673477 0.336739
+0.85 0.95 0.212132 0.424264
+0.85 0.95 0.26727 0.534539
+0.85 0.95 0.336739 0.673477
+0.95 0.95 0.3 0.3
+0.95 0.95 0.377976 0.377976
+0.95 0.95 0.47622 0.47622
+0.95 0.95 0.424264 0.212132
+0.95 0.95 0.534539 0.26727
+0.95 0.95 0.673477 0.336739
+0.95 0.95 0.212132 0.424264
+0.95 0.95 0.267269 0.534539
+0.95 0.95 0.336739 0.673477
+0.1 0.1 0.6 0.6
+0.1 0.1 0.755953 0.755953
+0.1 0.1 0.952441 0.952441
+0.1 0.1 0.848528 0.424264
+0.1 0.1 1.06908 0.534539
+0.1 0.1 1.34695 0.673477
+0.1 0.1 0.424264 0.848528
+0.1 0.1 0.534539 1.06908
+0.1 0.1 0.673477 1.34695
+0.3 0.1 0.6 0.6
+0.3 0.1 0.755953 0.755953
+0.3 0.1 0.952441 0.952441
+0.3 0.1 0.848528 0.424264
+0.3 0.1 1.06908 0.534539
+0.3 0.1 1.34695 0.673477
+0.3 0.1 0.424264 0.848528
+0.3 0.1 0.534539 1.06908
+0.3 0.1 0.673477 1.34695
+0.5 0.1 0.6 0.6
+0.5 0.1 0.755953 0.755953
+0.5 0.1 0.952441 0.952441
+0.5 0.1 0.848528 0.424264
+0.5 0.1 1.06908 0.534539
+0.5 0.1 1.34695 0.673477
+0.5 0.1 0.424264 0.848528
+0.5 0.1 0.534539 1.06908
+0.5 0.1 0.673477 1.34695
+0.7 0.1 0.6 0.6
+0.7 0.1 0.755953 0.755953
+0.7 0.1 0.952441 0.952441
+0.7 0.1 0.848528 0.424264
+0.7 0.1 1.06908 0.534539
+0.7 0.1 1.34695 0.673477
+0.7 0.1 0.424264 0.848528
+0.7 0.1 0.534539 1.06908
+0.7 0.1 0.673477 1.34695
+0.9 0.1 0.6 0.6
+0.9 0.1 0.755953 0.755953
+0.9 0.1 0.952441 0.952441
+0.9 0.1 0.848528 0.424264
+0.9 0.1 1.06908 0.534539
+0.9 0.1 1.34695 0.673477
+0.9 0.1 0.424264 0.848528
+0.9 0.1 0.534539 1.06908
+0.9 0.1 0.673477 1.34695
+0.1 0.3 0.6 0.6
+0.1 0.3 0.755953 0.755953
+0.1 0.3 0.952441 0.952441
+0.1 0.3 0.848528 0.424264
+0.1 0.3 1.06908 0.534539
+0.1 0.3 1.34695 0.673477
+0.1 0.3 0.424264 0.848528
+0.1 0.3 0.534539 1.06908
+0.1 0.3 0.673477 1.34695
+0.3 0.3 0.6 0.6
+0.3 0.3 0.755953 0.755953
+0.3 0.3 0.952441 0.952441
+0.3 0.3 0.848528 0.424264
+0.3 0.3 1.06908 0.534539
+0.3 0.3 1.34695 0.673477
+0.3 0.3 0.424264 0.848528
+0.3 0.3 0.534539 1.06908
+0.3 0.3 0.673477 1.34695
+0.5 0.3 0.6 0.6
+0.5 0.3 0.755953 0.755953
+0.5 0.3 0.952441 0.952441
+0.5 0.3 0.848528 0.424264
+0.5 0.3 1.06908 0.534539
+0.5 0.3 1.34695 0.673477
+0.5 0.3 0.424264 0.848528
+0.5 0.3 0.534539 1.06908
+0.5 0.3 0.673477 1.34695
+0.7 0.3 0.6 0.6
+0.7 0.3 0.755953 0.755953
+0.7 0.3 0.952441 0.952441
+0.7 0.3 0.848528 0.424264
+0.7 0.3 1.06908 0.534539
+0.7 0.3 1.34695 0.673477
+0.7 0.3 0.424264 0.848528
+0.7 0.3 0.534539 1.06908
+0.7 0.3 0.673477 1.34695
+0.9 0.3 0.6 0.6
+0.9 0.3 0.755953 0.755953
+0.9 0.3 0.952441 0.952441
+0.9 0.3 0.848528 0.424264
+0.9 0.3 1.06908 0.534539
+0.9 0.3 1.34695 0.673477
+0.9 0.3 0.424264 0.848528
+0.9 0.3 0.534539 1.06908
+0.9 0.3 0.673477 1.34695
+0.1 0.5 0.6 0.6
+0.1 0.5 0.755953 0.755953
+0.1 0.5 0.952441 0.952441
+0.1 0.5 0.848528 0.424264
+0.1 0.5 1.06908 0.534539
+0.1 0.5 1.34695 0.673477
+0.1 0.5 0.424264 0.848528
+0.1 0.5 0.534539 1.06908
+0.1 0.5 0.673477 1.34695
+0.3 0.5 0.6 0.6
+0.3 0.5 0.755953 0.755953
+0.3 0.5 0.952441 0.952441
+0.3 0.5 0.848528 0.424264
+0.3 0.5 1.06908 0.534539
+0.3 0.5 1.34695 0.673477
+0.3 0.5 0.424264 0.848528
+0.3 0.5 0.534539 1.06908
+0.3 0.5 0.673477 1.34695
+0.5 0.5 0.6 0.6
+0.5 0.5 0.755953 0.755953
+0.5 0.5 0.952441 0.952441
+0.5 0.5 0.848528 0.424264
+0.5 0.5 1.06908 0.534539
+0.5 0.5 1.34695 0.673477
+0.5 0.5 0.424264 0.848528
+0.5 0.5 0.534539 1.06908
+0.5 0.5 0.673477 1.34695
+0.7 0.5 0.6 0.6
+0.7 0.5 0.755953 0.755953
+0.7 0.5 0.952441 0.952441
+0.7 0.5 0.848528 0.424264
+0.7 0.5 1.06908 0.534539
+0.7 0.5 1.34695 0.673477
+0.7 0.5 0.424264 0.848528
+0.7 0.5 0.534539 1.06908
+0.7 0.5 0.673477 1.34695
+0.9 0.5 0.6 0.6
+0.9 0.5 0.755953 0.755953
+0.9 0.5 0.952441 0.952441
+0.9 0.5 0.848528 0.424264
+0.9 0.5 1.06908 0.534539
+0.9 0.5 1.34695 0.673477
+0.9 0.5 0.424264 0.848528
+0.9 0.5 0.534539 1.06908
+0.9 0.5 0.673477 1.34695
+0.1 0.7 0.6 0.6
+0.1 0.7 0.755953 0.755953
+0.1 0.7 0.952441 0.952441
+0.1 0.7 0.848528 0.424264
+0.1 0.7 1.06908 0.534539
+0.1 0.7 1.34695 0.673477
+0.1 0.7 0.424264 0.848528
+0.1 0.7 0.534539 1.06908
+0.1 0.7 0.673477 1.34695
+0.3 0.7 0.6 0.6
+0.3 0.7 0.755953 0.755953
+0.3 0.7 0.952441 0.952441
+0.3 0.7 0.848528 0.424264
+0.3 0.7 1.06908 0.534539
+0.3 0.7 1.34695 0.673477
+0.3 0.7 0.424264 0.848528
+0.3 0.7 0.534539 1.06908
+0.3 0.7 0.673477 1.34695
+0.5 0.7 0.6 0.6
+0.5 0.7 0.755953 0.755953
+0.5 0.7 0.952441 0.952441
+0.5 0.7 0.848528 0.424264
+0.5 0.7 1.06908 0.534539
+0.5 0.7 1.34695 0.673477
+0.5 0.7 0.424264 0.848528
+0.5 0.7 0.534539 1.06908
+0.5 0.7 0.673477 1.34695
+0.7 0.7 0.6 0.6
+0.7 0.7 0.755953 0.755953
+0.7 0.7 0.952441 0.952441
+0.7 0.7 0.848528 0.424264
+0.7 0.7 1.06908 0.534539
+0.7 0.7 1.34695 0.673477
+0.7 0.7 0.424264 0.848528
+0.7 0.7 0.534539 1.06908
+0.7 0.7 0.673477 1.34695
+0.9 0.7 0.6 0.6
+0.9 0.7 0.755953 0.755953
+0.9 0.7 0.952441 0.952441
+0.9 0.7 0.848528 0.424264
+0.9 0.7 1.06908 0.534539
+0.9 0.7 1.34695 0.673477
+0.9 0.7 0.424264 0.848528
+0.9 0.7 0.534539 1.06908
+0.9 0.7 0.673477 1.34695
+0.1 0.9 0.6 0.6
+0.1 0.9 0.755953 0.755953
+0.1 0.9 0.952441 0.952441
+0.1 0.9 0.848528 0.424264
+0.1 0.9 1.06908 0.534539
+0.1 0.9 1.34695 0.673477
+0.1 0.9 0.424264 0.848528
+0.1 0.9 0.534539 1.06908
+0.1 0.9 0.673477 1.34695
+0.3 0.9 0.6 0.6
+0.3 0.9 0.755953 0.755953
+0.3 0.9 0.952441 0.952441
+0.3 0.9 0.848528 0.424264
+0.3 0.9 1.06908 0.534539
+0.3 0.9 1.34695 0.673477
+0.3 0.9 0.424264 0.848528
+0.3 0.9 0.534539 1.06908
+0.3 0.9 0.673477 1.34695
+0.5 0.9 0.6 0.6
+0.5 0.9 0.755953 0.755953
+0.5 0.9 0.952441 0.952441
+0.5 0.9 0.848528 0.424264
+0.5 0.9 1.06908 0.534539
+0.5 0.9 1.34695 0.673477
+0.5 0.9 0.424264 0.848528
+0.5 0.9 0.534539 1.06908
+0.5 0.9 0.673477 1.34695
+0.7 0.9 0.6 0.6
+0.7 0.9 0.755953 0.755953
+0.7 0.9 0.952441 0.952441
+0.7 0.9 0.848528 0.424264
+0.7 0.9 1.06908 0.534539
+0.7 0.9 1.34695 0.673477
+0.7 0.9 0.424264 0.848528
+0.7 0.9 0.534539 1.06908
+0.7 0.9 0.673477 1.34695
+0.9 0.9 0.6 0.6
+0.9 0.9 0.755953 0.755953
+0.9 0.9 0.952441 0.952441
+0.9 0.9 0.848528 0.424264
+0.9 0.9 1.06908 0.534539
+0.9 0.9 1.34695 0.673477
+0.9 0.9 0.424264 0.848528
+0.9 0.9 0.534539 1.06908
+0.9 0.9 0.673477 1.34695
diff --git a/mediapipe/calculators/tflite/tflite_model_calculator.cc b/mediapipe/calculators/tflite/tflite_model_calculator.cc
index ca28910e5..d118e878c 100644
--- a/mediapipe/calculators/tflite/tflite_model_calculator.cc
+++ b/mediapipe/calculators/tflite/tflite_model_calculator.cc
@@ -19,6 +19,7 @@
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/packet.h"
#include "mediapipe/framework/port/ret_check.h"
+#include "tensorflow/lite/allocation.h"
#include "tensorflow/lite/model.h"
namespace mediapipe {
@@ -32,6 +33,8 @@ namespace mediapipe {
// 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.
+// MODEL_FD - Tflite model file descriptor std::tuple
+// containing (fd, offset, size).
//
// Output side packets:
// MODEL - TfLite model. (std::unique_ptr>;
static absl::Status GetContract(CalculatorContract* cc) {
- cc->InputSidePackets().Tag("MODEL_BLOB").Set();
+ if (cc->InputSidePackets().HasTag("MODEL_BLOB")) {
+ cc->InputSidePackets().Tag("MODEL_BLOB").Set();
+ }
+
+ if (cc->InputSidePackets().HasTag("MODEL_FD")) {
+ cc->InputSidePackets()
+ .Tag("MODEL_FD")
+ .Set>();
+ }
+
cc->OutputSidePackets().Tag("MODEL").Set();
return absl::OkStatus();
}
absl::Status Open(CalculatorContext* cc) override {
- const Packet& model_packet = cc->InputSidePackets().Tag("MODEL_BLOB");
- const std::string& model_blob = model_packet.Get();
- std::unique_ptr model =
- tflite::FlatBufferModel::BuildFromBuffer(model_blob.data(),
- model_blob.size());
+ Packet model_packet;
+ std::unique_ptr model;
+
+ if (cc->InputSidePackets().HasTag("MODEL_BLOB")) {
+ model_packet = cc->InputSidePackets().Tag("MODEL_BLOB");
+ const std::string& model_blob = model_packet.Get();
+ model = tflite::FlatBufferModel::BuildFromBuffer(model_blob.data(),
+ model_blob.size());
+ }
+
+ if (cc->InputSidePackets().HasTag("MODEL_FD")) {
+ model_packet = cc->InputSidePackets().Tag("MODEL_FD");
+ const auto& model_fd =
+ model_packet.Get>();
+ auto model_allocation = std::make_unique(
+ std::get<0>(model_fd), std::get<1>(model_fd), std::get<2>(model_fd),
+ tflite::DefaultErrorReporter());
+ model = tflite::FlatBufferModel::BuildFromAllocation(
+ std::move(model_allocation), tflite::DefaultErrorReporter());
+ }
+
RET_CHECK(model) << "Failed to load TfLite model from blob.";
cc->OutputSidePackets().Tag("MODEL").Set(
diff --git a/mediapipe/examples/ios/common/Assets.xcassets/AppIcon.appiconset/83.5_c_Ipad_2x.png b/mediapipe/examples/ios/common/Assets.xcassets/AppIcon.appiconset/83.5_c_Ipad_2x.png
new file mode 100644
index 000000000..e0a66942b
Binary files /dev/null and b/mediapipe/examples/ios/common/Assets.xcassets/AppIcon.appiconset/83.5_c_Ipad_2x.png differ
diff --git a/mediapipe/examples/ios/common/Assets.xcassets/AppIcon.appiconset/Contents.json b/mediapipe/examples/ios/common/Assets.xcassets/AppIcon.appiconset/Contents.json
index 3ed9f5238..f8a7311a9 100644
--- a/mediapipe/examples/ios/common/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/mediapipe/examples/ios/common/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -90,6 +90,7 @@
{
"idiom" : "ipad",
"size" : "83.5x83.5",
+ "filename" : "83.5_c_Ipad_2x.png",
"scale" : "2x"
},
{
diff --git a/mediapipe/framework/api2/BUILD b/mediapipe/framework/api2/BUILD
index 768fc86c8..6de444438 100644
--- a/mediapipe/framework/api2/BUILD
+++ b/mediapipe/framework/api2/BUILD
@@ -21,7 +21,9 @@ cc_library(
":port",
"//mediapipe/framework:calculator_base",
"//mediapipe/framework:calculator_contract",
+ "@com_google_absl//absl/container:btree",
"@com_google_absl//absl/container:flat_hash_map",
+ "@com_google_absl//absl/strings",
],
)
diff --git a/mediapipe/framework/api2/builder.h b/mediapipe/framework/api2/builder.h
index f238b653c..b78014155 100644
--- a/mediapipe/framework/api2/builder.h
+++ b/mediapipe/framework/api2/builder.h
@@ -4,7 +4,9 @@
#include
#include
+#include "absl/container/btree_map.h"
#include "absl/container/flat_hash_map.h"
+#include "absl/strings/string_view.h"
#include "mediapipe/framework/api2/const_str.h"
#include "mediapipe/framework/api2/contract.h"
#include "mediapipe/framework/api2/node.h"
@@ -46,7 +48,7 @@ struct TagIndexLocation {
template