diff --git a/mediapipe/calculators/tensor/BUILD b/mediapipe/calculators/tensor/BUILD index 92e786b6d..1a2a6b342 100644 --- a/mediapipe/calculators/tensor/BUILD +++ b/mediapipe/calculators/tensor/BUILD @@ -1324,6 +1324,7 @@ cc_test( name = "image_to_tensor_utils_test", srcs = ["image_to_tensor_utils_test.cc"], deps = [ + ":image_to_tensor_calculator_cc_proto", ":image_to_tensor_utils", "//mediapipe/framework/formats:rect_cc_proto", "//mediapipe/framework/port:gtest_main", diff --git a/mediapipe/calculators/tensor/image_to_tensor_converter_gl_buffer.cc b/mediapipe/calculators/tensor/image_to_tensor_converter_gl_buffer.cc index 14de410ff..a551e7f8d 100644 --- a/mediapipe/calculators/tensor/image_to_tensor_converter_gl_buffer.cc +++ b/mediapipe/calculators/tensor/image_to_tensor_converter_gl_buffer.cc @@ -330,9 +330,8 @@ class GlProcessor : public ImageToTensorConverter { absl::Status ValidateTensorShape(const Tensor::Shape& output_shape) { RET_CHECK_EQ(output_shape.dims.size(), 4) << "Wrong output dims size: " << output_shape.dims.size(); - RET_CHECK_EQ(output_shape.dims[0], 1) - << "Handling batch dimension not equal to 1 is not implemented in this " - "converter."; + RET_CHECK_GE(output_shape.dims[0], 1) + << "The batch dimension needs to be greater or equal to 1."; RET_CHECK_EQ(output_shape.dims[3], 3) << "Wrong output channel: " << output_shape.dims[3]; return absl::OkStatus(); diff --git a/mediapipe/calculators/tensor/image_to_tensor_utils_test.cc b/mediapipe/calculators/tensor/image_to_tensor_utils_test.cc index 9ba7d0138..450bcba31 100644 --- a/mediapipe/calculators/tensor/image_to_tensor_utils_test.cc +++ b/mediapipe/calculators/tensor/image_to_tensor_utils_test.cc @@ -172,7 +172,7 @@ constexpr char kValidIntProto[] = R"( output_tensor_height: 200 )"; -TEST(ValidateOptionOutputDims, ValidProtos) { +TEST(ValidateOptionOutputDims, ImageToTensorCalcOptions) { const auto float_options = mediapipe::ParseTextProtoOrDie( kValidFloatProto); @@ -202,7 +202,7 @@ TEST(ValidateOptionOutputDims, EmptyProto) { HasSubstr("Valid output tensor width is required"))); } -TEST(GetOutputTensorParams, SetValues) { +TEST(GetOutputTensorParams, ImageToTensorCalcOptionsSetValues) { // Test int range with ImageToTensorCalculatorOptions. const auto int_options = mediapipe::ParseTextProtoOrDie( diff --git a/mediapipe/calculators/tensor/testdata/image_to_tensor/crop_empty.png b/mediapipe/calculators/tensor/testdata/image_to_tensor/crop_empty.png new file mode 100644 index 000000000..02d1fe884 Binary files /dev/null and b/mediapipe/calculators/tensor/testdata/image_to_tensor/crop_empty.png differ diff --git a/mediapipe/calculators/tensor/testdata/image_to_tensor/crop_rect1.png b/mediapipe/calculators/tensor/testdata/image_to_tensor/crop_rect1.png new file mode 100644 index 000000000..1238464d5 Binary files /dev/null and b/mediapipe/calculators/tensor/testdata/image_to_tensor/crop_rect1.png differ diff --git a/mediapipe/calculators/tensor/testdata/image_to_tensor/crop_rect2.png b/mediapipe/calculators/tensor/testdata/image_to_tensor/crop_rect2.png new file mode 100644 index 000000000..f9dc58e56 Binary files /dev/null and b/mediapipe/calculators/tensor/testdata/image_to_tensor/crop_rect2.png differ diff --git a/mediapipe/calculators/tensor/testdata/image_to_tensor/crop_rect3.png b/mediapipe/calculators/tensor/testdata/image_to_tensor/crop_rect3.png new file mode 100644 index 000000000..6e451e3f7 Binary files /dev/null and b/mediapipe/calculators/tensor/testdata/image_to_tensor/crop_rect3.png differ diff --git a/mediapipe/framework/api2/builder.h b/mediapipe/framework/api2/builder.h index bf7f2b399..5af9ee5e0 100644 --- a/mediapipe/framework/api2/builder.h +++ b/mediapipe/framework/api2/builder.h @@ -106,6 +106,13 @@ class MultiPort : public Single { return Single{&GetWithAutoGrow(&vec_, index)}; } + template + auto Cast() { + using SingleCastT = + std::invoke_result_t), Single*>; + return MultiPort(&vec_); + } + private: std::vector>& vec_; }; diff --git a/mediapipe/framework/api2/builder_test.cc b/mediapipe/framework/api2/builder_test.cc index 810c52527..3bf3ec198 100644 --- a/mediapipe/framework/api2/builder_test.cc +++ b/mediapipe/framework/api2/builder_test.cc @@ -445,6 +445,57 @@ TEST(BuilderTest, AnyTypeCanBeCast) { EXPECT_THAT(graph.GetConfig(), EqualsProto(expected)); } +TEST(BuilderTest, MultiPortIsCastToMultiPort) { + builder::Graph graph; + builder::MultiSource any_input = graph.In("ANY_INPUT"); + builder::MultiSource int_input = any_input.Cast(); + builder::MultiDestination any_output = graph.Out("ANY_OUTPUT"); + builder::MultiDestination int_output = any_output.Cast(); + int_input >> int_output; + + CalculatorGraphConfig expected = + mediapipe::ParseTextProtoOrDie(R"pb( + input_stream: "ANY_INPUT:__stream_0" + output_stream: "ANY_OUTPUT:__stream_0" + )pb"); + EXPECT_THAT(graph.GetConfig(), EqualsProto(expected)); +} + +TEST(BuilderTest, MultiPortCanBeSlicedToSinglePort) { + builder::Graph graph; + builder::MultiSource any_multi_input = graph.In("ANY_INPUT"); + builder::Source any_input = any_multi_input; + builder::MultiDestination any_multi_output = graph.Out("ANY_OUTPUT"); + builder::Destination any_output = any_multi_output; + any_input >> any_output; + + CalculatorGraphConfig expected = + mediapipe::ParseTextProtoOrDie(R"pb( + input_stream: "ANY_INPUT:__stream_0" + output_stream: "ANY_OUTPUT:__stream_0" + )pb"); + EXPECT_THAT(graph.GetConfig(), EqualsProto(expected)); +} + +TEST(BuilderTest, SinglePortAccessWorksThroughSlicing) { + builder::Graph graph; + builder::Source int_input = graph.In("INT_INPUT").Cast(); + builder::Source any_input = graph.In("ANY_OUTPUT"); + builder::Destination int_output = graph.Out("INT_OUTPUT").Cast(); + builder::Destination any_output = graph.Out("ANY_OUTPUT"); + int_input >> int_output; + any_input >> any_output; + + CalculatorGraphConfig expected = + mediapipe::ParseTextProtoOrDie(R"pb( + input_stream: "ANY_OUTPUT:__stream_0" + input_stream: "INT_INPUT:__stream_1" + output_stream: "ANY_OUTPUT:__stream_0" + output_stream: "INT_OUTPUT:__stream_1" + )pb"); + EXPECT_THAT(graph.GetConfig(), EqualsProto(expected)); +} + } // namespace test } // namespace api2 } // namespace mediapipe diff --git a/mediapipe/gpu/gl_context_egl.cc b/mediapipe/gpu/gl_context_egl.cc index 78b196b08..f8784bbbc 100644 --- a/mediapipe/gpu/gl_context_egl.cc +++ b/mediapipe/gpu/gl_context_egl.cc @@ -38,20 +38,18 @@ static pthread_key_t egl_release_thread_key; static pthread_once_t egl_release_key_once = PTHREAD_ONCE_INIT; static void EglThreadExitCallback(void* key_value) { -#if defined(__ANDROID__) - eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); -#else - // Some implementations have chosen to allow EGL_NO_DISPLAY as a valid display - // parameter for eglMakeCurrent. This behavior is not portable to all EGL - // implementations, and should be considered as an undocumented vendor - // extension. - // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglMakeCurrent.xhtml - // - // NOTE: crashes on some Android devices (occurs with libGLES_meow.so). - eglMakeCurrent(eglGetDisplay(EGL_DEFAULT_DISPLAY), EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT); -#endif + EGLDisplay current_display = eglGetCurrentDisplay(); + if (current_display != EGL_NO_DISPLAY) { + // Some implementations have chosen to allow EGL_NO_DISPLAY as a valid + // display parameter for eglMakeCurrent. This behavior is not portable to + // all EGL implementations, and should be considered as an undocumented + // vendor extension. + // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglMakeCurrent.xhtml + // Instead, to release the current context, we pass the current display. + // If the current display is already EGL_NO_DISPLAY, no context is current. + eglMakeCurrent(current_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + } eglReleaseThread(); } diff --git a/mediapipe/model_maker/python/BUILD b/mediapipe/model_maker/python/BUILD index cb312072f..fe101f293 100644 --- a/mediapipe/model_maker/python/BUILD +++ b/mediapipe/model_maker/python/BUILD @@ -20,3 +20,10 @@ package_group( "//mediapipe/model_maker/...", ], ) + +package_group( + name = "1p_client", + packages = [ + "//research/privacy/learning/fl_eval/pcvr/...", + ], +) diff --git a/mediapipe/model_maker/python/core/hyperparameters.py b/mediapipe/model_maker/python/core/hyperparameters.py index 2a7a8678c..5cff30930 100644 --- a/mediapipe/model_maker/python/core/hyperparameters.py +++ b/mediapipe/model_maker/python/core/hyperparameters.py @@ -19,7 +19,6 @@ import tempfile from typing import Optional -# TODO: Integrate this class into ImageClassifier and other tasks. @dataclasses.dataclass class BaseHParams: """Hyperparameters used for training models. diff --git a/mediapipe/model_maker/python/core/tasks/BUILD b/mediapipe/model_maker/python/core/tasks/BUILD index 124de621a..8c5448556 100644 --- a/mediapipe/model_maker/python/core/tasks/BUILD +++ b/mediapipe/model_maker/python/core/tasks/BUILD @@ -45,7 +45,10 @@ py_library( srcs = ["classifier.py"], deps = [ ":custom_model", + "//mediapipe/model_maker/python/core:hyperparameters", + "//mediapipe/model_maker/python/core/data:classification_dataset", "//mediapipe/model_maker/python/core/data:dataset", + "//mediapipe/model_maker/python/core/utils:model_util", ], ) diff --git a/mediapipe/model_maker/python/core/tasks/classifier.py b/mediapipe/model_maker/python/core/tasks/classifier.py index 5d0fbd066..200726864 100644 --- a/mediapipe/model_maker/python/core/tasks/classifier.py +++ b/mediapipe/model_maker/python/core/tasks/classifier.py @@ -13,24 +13,24 @@ # limitations under the License. """Custom classifier.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os -from typing import Any, List +from typing import Any, Callable, Optional, Sequence, Union import tensorflow as tf +from mediapipe.model_maker.python.core import hyperparameters as hp +from mediapipe.model_maker.python.core.data import classification_dataset as classification_ds from mediapipe.model_maker.python.core.data import dataset from mediapipe.model_maker.python.core.tasks import custom_model +from mediapipe.model_maker.python.core.utils import model_util class Classifier(custom_model.CustomModel): """An abstract base class that represents a TensorFlow classifier.""" - def __init__(self, model_spec: Any, label_names: List[str], shuffle: bool): - """Initilizes a classifier with its specifications. + def __init__(self, model_spec: Any, label_names: Sequence[str], + shuffle: bool): + """Initializes a classifier with its specifications. Args: model_spec: Specification for the model. @@ -40,6 +40,59 @@ class Classifier(custom_model.CustomModel): super(Classifier, self).__init__(model_spec, shuffle) self._label_names = label_names self._num_classes = len(label_names) + self._model: tf.keras.Model = None + self._optimizer: Union[str, tf.keras.optimizers.Optimizer] = None + self._loss_function: Union[str, tf.keras.losses.Loss] = None + self._metric_function: Union[str, tf.keras.metrics.Metric] = None + self._callbacks: Sequence[tf.keras.callbacks.Callback] = None + self._hparams: hp.BaseHParams = None + self._history: tf.keras.callbacks.History = None + + # TODO: Integrate this into all Model Maker tasks. + def _train_model(self, + train_data: classification_ds.ClassificationDataset, + validation_data: classification_ds.ClassificationDataset, + preprocessor: Optional[Callable[..., bool]] = None): + """Trains the classifier model. + + Compiles and fits the tf.keras `_model` and records the `_history`. + + Args: + train_data: Training data. + validation_data: Validation data. + preprocessor: An optional data preprocessor that can be used when + generating a tf.data.Dataset. + """ + tf.compat.v1.logging.info('Training the models...') + if len(train_data) < self._hparams.batch_size: + raise ValueError( + f'The size of the train_data {len(train_data)} can\'t be smaller than' + f' batch_size {self._hparams.batch_size}. To solve this problem, set' + ' the batch_size smaller or increase the size of the train_data.') + + train_dataset = train_data.gen_tf_dataset( + batch_size=self._hparams.batch_size, + is_training=True, + shuffle=self._shuffle, + preprocess=preprocessor) + self._hparams.steps_per_epoch = model_util.get_steps_per_epoch( + steps_per_epoch=self._hparams.steps_per_epoch, + batch_size=self._hparams.batch_size, + train_data=train_data) + train_dataset = train_dataset.take(count=self._hparams.steps_per_epoch) + validation_dataset = validation_data.gen_tf_dataset( + batch_size=self._hparams.batch_size, + is_training=False, + preprocess=preprocessor) + self._model.compile( + optimizer=self._optimizer, + loss=self._loss_function, + metrics=[self._metric_function]) + self._history = self._model.fit( + x=train_dataset, + epochs=self._hparams.epochs, + validation_data=validation_dataset, + callbacks=self._callbacks) def evaluate(self, data: dataset.Dataset, batch_size: int = 32) -> Any: """Evaluates the classifier with the provided evaluation dataset. diff --git a/mediapipe/model_maker/python/core/utils/BUILD b/mediapipe/model_maker/python/core/utils/BUILD index a2ec52044..12fef631f 100644 --- a/mediapipe/model_maker/python/core/utils/BUILD +++ b/mediapipe/model_maker/python/core/utils/BUILD @@ -35,6 +35,7 @@ py_library( name = "model_util", srcs = ["model_util.py"], deps = [ + ":file_util", ":quantization", "//mediapipe/model_maker/python/core/data:dataset", ], @@ -50,6 +51,18 @@ py_test( ], ) +py_library( + name = "file_util", + srcs = ["file_util.py"], +) + +py_test( + name = "file_util_test", + srcs = ["file_util_test.py"], + data = ["//mediapipe/model_maker/python/core/utils/testdata"], + deps = [":file_util"], +) + py_library( name = "loss_functions", srcs = ["loss_functions.py"], diff --git a/mediapipe/model_maker/python/core/utils/file_util.py b/mediapipe/model_maker/python/core/utils/file_util.py new file mode 100644 index 000000000..bccf928e2 --- /dev/null +++ b/mediapipe/model_maker/python/core/utils/file_util.py @@ -0,0 +1,36 @@ +# Copyright 2022 The MediaPipe Authors. All Rights Reserved. +# +# 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. +"""Utilities for files.""" + +import os + +# resources dependency + + +def get_absolute_path(file_path: str) -> str: + """Gets the absolute path of a file. + + Args: + file_path: The path to a file relative to the `mediapipe` dir + + Returns: + The full path of the file + """ + # Extract the file path before mediapipe/ as the `base_dir`. By joining it + # with the `path` which defines the relative path under mediapipe/, it + # yields to the absolute path of the model files directory. + cwd = os.path.dirname(__file__) + base_dir = cwd[:cwd.rfind('mediapipe')] + absolute_path = os.path.join(base_dir, file_path) + return absolute_path diff --git a/mediapipe/model_maker/python/core/utils/file_util_test.py b/mediapipe/model_maker/python/core/utils/file_util_test.py new file mode 100644 index 000000000..4a2d6dcfb --- /dev/null +++ b/mediapipe/model_maker/python/core/utils/file_util_test.py @@ -0,0 +1,29 @@ +# Copyright 2022 The MediaPipe Authors. All Rights Reserved. +# +# 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. +import os + +from absl.testing import absltest +from mediapipe.model_maker.python.core.utils import file_util + + +class FileUtilTest(absltest.TestCase): + + def test_get_absolute_path(self): + test_file = 'mediapipe/model_maker/python/core/utils/testdata/test.txt' + absolute_path = file_util.get_absolute_path(test_file) + self.assertTrue(os.path.exists(absolute_path)) + + +if __name__ == '__main__': + absltest.main() diff --git a/mediapipe/model_maker/python/core/utils/model_util.py b/mediapipe/model_maker/python/core/utils/model_util.py index 02d4f5b1e..f10d9390c 100644 --- a/mediapipe/model_maker/python/core/utils/model_util.py +++ b/mediapipe/model_maker/python/core/utils/model_util.py @@ -11,7 +11,7 @@ # 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. -"""Utilities for keras models.""" +"""Utilities for models.""" from __future__ import absolute_import from __future__ import division @@ -19,21 +19,33 @@ from __future__ import print_function import os import tempfile -from typing import Any, Callable, Dict, List, Optional, Text, Tuple, Union +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union # Dependency imports import numpy as np import tensorflow as tf -# resources dependency from mediapipe.model_maker.python.core.data import dataset +from mediapipe.model_maker.python.core.utils import file_util from mediapipe.model_maker.python.core.utils import quantization DEFAULT_SCALE, DEFAULT_ZERO_POINT = 0, 0 ESTIMITED_STEPS_PER_EPOCH = 1000 +def get_default_callbacks( + export_dir: str) -> Sequence[tf.keras.callbacks.Callback]: + """Gets default callbacks.""" + summary_dir = os.path.join(export_dir, 'summaries') + summary_callback = tf.keras.callbacks.TensorBoard(summary_dir) + + checkpoint_path = os.path.join(export_dir, 'checkpoint') + checkpoint_callback = tf.keras.callbacks.ModelCheckpoint( + checkpoint_path, save_weights_only=True) + return [summary_callback, checkpoint_callback] + + def load_keras_model(model_path: str, compile_on_load: bool = False) -> tf.keras.Model: """Loads a tensorflow Keras model from file and returns the Keras model. @@ -49,16 +61,26 @@ def load_keras_model(model_path: str, Returns: A tensorflow Keras model. """ - # Extract the file path before mediapipe/ as the `base_dir`. By joining it - # with the `model_path` which defines the relative path under mediapipe/, it - # yields to the aboslution path of the model files directory. - cwd = os.path.dirname(__file__) - base_dir = cwd[:cwd.rfind('mediapipe')] - absolute_path = os.path.join(base_dir, model_path) + absolute_path = file_util.get_absolute_path(model_path) return tf.keras.models.load_model( absolute_path, custom_objects={'tf': tf}, compile=compile_on_load) +def load_tflite_model_buffer(model_path: str) -> bytearray: + """Loads a TFLite model buffer from file. + + Args: + model_path: Relative path to a TFLite file + + Returns: + A TFLite model buffer + """ + absolute_path = file_util.get_absolute_path(model_path) + with tf.io.gfile.GFile(absolute_path, 'rb') as f: + tflite_model_buffer = f.read() + return tflite_model_buffer + + def get_steps_per_epoch(steps_per_epoch: Optional[int] = None, batch_size: Optional[int] = None, train_data: Optional[dataset.Dataset] = None) -> int: @@ -174,7 +196,7 @@ class WarmUp(tf.keras.optimizers.schedules.LearningRateSchedule): lambda: self.decay_schedule_fn(step), name=name) - def get_config(self) -> Dict[Text, Any]: + def get_config(self) -> Dict[str, Any]: return { 'initial_learning_rate': self.initial_learning_rate, 'decay_schedule_fn': self.decay_schedule_fn, diff --git a/mediapipe/model_maker/python/core/utils/model_util_test.py b/mediapipe/model_maker/python/core/utils/model_util_test.py index 1f9e0f1db..bef9c8a97 100644 --- a/mediapipe/model_maker/python/core/utils/model_util_test.py +++ b/mediapipe/model_maker/python/core/utils/model_util_test.py @@ -24,7 +24,7 @@ from mediapipe.model_maker.python.core.utils import test_util class ModelUtilTest(tf.test.TestCase, parameterized.TestCase): - def test_load_model(self): + def test_load_keras_model(self): input_dim = 4 model = test_util.build_model(input_shape=[input_dim], num_classes=2) saved_model_path = os.path.join(self.get_temp_dir(), 'saved_model') @@ -36,6 +36,19 @@ class ModelUtilTest(tf.test.TestCase, parameterized.TestCase): loaded_model_output = loaded_model.predict_on_batch(input_tensors) self.assertTrue((model_output == loaded_model_output).all()) + def test_load_tflite_model_buffer(self): + input_dim = 4 + model = test_util.build_model(input_shape=[input_dim], num_classes=2) + tflite_model = model_util.convert_to_tflite(model) + tflite_file = os.path.join(self.get_temp_dir(), 'model.tflite') + model_util.save_tflite(tflite_model=tflite_model, tflite_file=tflite_file) + + tflite_model_buffer = model_util.load_tflite_model_buffer(tflite_file) + test_util.test_tflite( + keras_model=model, + tflite_model=tflite_model_buffer, + size=[1, input_dim]) + @parameterized.named_parameters( dict( testcase_name='input_only_steps_per_epoch', diff --git a/mediapipe/model_maker/python/core/utils/testdata/BUILD b/mediapipe/model_maker/python/core/utils/testdata/BUILD new file mode 100644 index 000000000..8eed72f78 --- /dev/null +++ b/mediapipe/model_maker/python/core/utils/testdata/BUILD @@ -0,0 +1,23 @@ +# Copyright 2022 The MediaPipe Authors. All Rights Reserved. +# +# 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. + +package( + default_visibility = ["//mediapipe/model_maker/python/core/utils:__subpackages__"], + licenses = ["notice"], # Apache 2.0 +) + +filegroup( + name = "testdata", + srcs = ["test.txt"], +) diff --git a/mediapipe/model_maker/python/core/utils/testdata/test.txt b/mediapipe/model_maker/python/core/utils/testdata/test.txt new file mode 100644 index 000000000..e69de29bb diff --git a/mediapipe/model_maker/python/vision/image_classifier/BUILD b/mediapipe/model_maker/python/vision/image_classifier/BUILD index 551c3777c..c581d9fbc 100644 --- a/mediapipe/model_maker/python/vision/image_classifier/BUILD +++ b/mediapipe/model_maker/python/vision/image_classifier/BUILD @@ -28,6 +28,8 @@ py_library( ":dataset", ":hyperparameters", ":image_classifier", + ":image_classifier_options", + ":model_options", ":model_spec", ], ) @@ -58,6 +60,24 @@ py_test( py_library( name = "hyperparameters", srcs = ["hyperparameters.py"], + deps = [ + "//mediapipe/model_maker/python/core:hyperparameters", + ], +) + +py_library( + name = "model_options", + srcs = ["model_options.py"], +) + +py_library( + name = "image_classifier_options", + srcs = ["image_classifier_options.py"], + deps = [ + ":hyperparameters", + ":model_options", + ":model_spec", + ], ) py_library( @@ -74,6 +94,8 @@ py_library( srcs = ["image_classifier.py"], deps = [ ":hyperparameters", + ":image_classifier_options", + ":model_options", ":model_spec", ":train_image_classifier_lib", "//mediapipe/model_maker/python/core/data:classification_dataset", @@ -99,6 +121,7 @@ py_library( py_test( name = "image_classifier_test", + size = "large", srcs = ["image_classifier_test.py"], shard_count = 2, tags = ["requires-net:external"], diff --git a/mediapipe/model_maker/python/vision/image_classifier/__init__.py b/mediapipe/model_maker/python/vision/image_classifier/__init__.py index 3ba6b0764..3d0543cd2 100644 --- a/mediapipe/model_maker/python/vision/image_classifier/__init__.py +++ b/mediapipe/model_maker/python/vision/image_classifier/__init__.py @@ -16,10 +16,14 @@ from mediapipe.model_maker.python.vision.image_classifier import dataset from mediapipe.model_maker.python.vision.image_classifier import hyperparameters from mediapipe.model_maker.python.vision.image_classifier import image_classifier +from mediapipe.model_maker.python.vision.image_classifier import image_classifier_options +from mediapipe.model_maker.python.vision.image_classifier import model_options from mediapipe.model_maker.python.vision.image_classifier import model_spec ImageClassifier = image_classifier.ImageClassifier HParams = hyperparameters.HParams Dataset = dataset.Dataset +ModelOptions = model_options.ImageClassifierModelOptions ModelSpec = model_spec.ModelSpec SupportedModels = model_spec.SupportedModels +ImageClassifierOptions = image_classifier_options.ImageClassifierOptions diff --git a/mediapipe/model_maker/python/vision/image_classifier/hyperparameters.py b/mediapipe/model_maker/python/vision/image_classifier/hyperparameters.py index 6df18579a..1d3bfdad2 100644 --- a/mediapipe/model_maker/python/vision/image_classifier/hyperparameters.py +++ b/mediapipe/model_maker/python/vision/image_classifier/hyperparameters.py @@ -14,28 +14,20 @@ """Hyperparameters for training image classification models.""" import dataclasses -import tempfile -from typing import Optional + +from mediapipe.model_maker.python.core import hyperparameters as hp -# TODO: Expose other hyperparameters, e.g. data augmentation -# hyperparameters if requested. @dataclasses.dataclass -class HParams: +class HParams(hp.BaseHParams): """The hyperparameters for training image classifiers. - The hyperparameters include: - # Parameters about training data. + Attributes: + learning_rate: Learning rate to use for gradient descent training. + batch_size: Batch size for training. + epochs: Number of training iterations over the dataset. do_fine_tuning: If true, the base module is trained together with the classification layer on top. - shuffle: A boolean controlling if shuffle the dataset. Default to false. - - # Parameters about training configuration - train_epochs: Training will do this many iterations over the dataset. - batch_size: Each training step samples a batch of this many images. - learning_rate: The learning rate to use for gradient descent training. - dropout_rate: The fraction of the input units to drop, used in dropout - layer. l1_regularizer: A regularizer that applies a L1 regularization penalty. l2_regularizer: A regularizer that applies a L2 regularization penalty. label_smoothing: Amount of label smoothing to apply. See tf.keras.losses for @@ -43,32 +35,21 @@ class HParams: do_data_augmentation: A boolean controlling whether the training dataset is augmented by randomly distorting input images, including random cropping, flipping, etc. See utils.image_preprocessing documentation for details. - steps_per_epoch: An optional integer indicate the number of training steps - per epoch. If not set, the training pipeline calculates the default steps - per epoch as the training dataset size devided by batch size. decay_samples: Number of training samples used to calculate the decay steps and create the training optimizer. warmup_steps: Number of warmup steps for a linear increasing warmup schedule - on learning rate. Used to set up warmup schedule by model_util.WarmUp. - - # Parameters about the saved checkpoint - model_dir: The location of model checkpoint files and exported model files. + on learning rate. Used to set up warmup schedule by model_util.WarmUp.s """ - # Parameters about training data - do_fine_tuning: bool = False - shuffle: bool = False + # Parameters from BaseHParams class. + learning_rate: float = 0.001 + batch_size: int = 2 + epochs: int = 10 # Parameters about training configuration - train_epochs: int = 5 - batch_size: int = 32 - learning_rate: float = 0.005 - dropout_rate: float = 0.2 + do_fine_tuning: bool = False l1_regularizer: float = 0.0 l2_regularizer: float = 0.0001 label_smoothing: float = 0.1 do_data_augmentation: bool = True - steps_per_epoch: Optional[int] = None + # TODO: Use lr_decay in hp.baseHParams to infer decay_samples. decay_samples: int = 10000 * 256 warmup_epochs: int = 2 - - # Parameters about the saved checkpoint - model_dir: str = tempfile.mkdtemp() diff --git a/mediapipe/model_maker/python/vision/image_classifier/image_classifier.py b/mediapipe/model_maker/python/vision/image_classifier/image_classifier.py index 569138df7..1ff6132b4 100644 --- a/mediapipe/model_maker/python/vision/image_classifier/image_classifier.py +++ b/mediapipe/model_maker/python/vision/image_classifier/image_classifier.py @@ -25,6 +25,8 @@ from mediapipe.model_maker.python.core.utils import model_util from mediapipe.model_maker.python.core.utils import quantization from mediapipe.model_maker.python.vision.core import image_preprocessing from mediapipe.model_maker.python.vision.image_classifier import hyperparameters as hp +from mediapipe.model_maker.python.vision.image_classifier import image_classifier_options +from mediapipe.model_maker.python.vision.image_classifier import model_options as model_opt from mediapipe.model_maker.python.vision.image_classifier import model_spec as ms from mediapipe.model_maker.python.vision.image_classifier import train_image_classifier_lib from mediapipe.tasks.python.metadata.metadata_writers import image_classifier as image_classifier_writer @@ -35,17 +37,20 @@ class ImageClassifier(classifier.Classifier): """ImageClassifier for building image classification model.""" def __init__(self, model_spec: ms.ModelSpec, label_names: List[str], - hparams: hp.HParams): + hparams: hp.HParams, + model_options: model_opt.ImageClassifierModelOptions): """Initializes ImageClassifier class. Args: model_spec: Specification for the model. label_names: A list of label names for the classes. hparams: The hyperparameters for training image classifier. + model_options: Model options for creating image classifier. """ super().__init__( model_spec=model_spec, label_names=label_names, shuffle=hparams.shuffle) self._hparams = hparams + self._model_options = model_options self._preprocess = image_preprocessing.Preprocessor( input_shape=self._model_spec.input_image_shape, num_classes=self._num_classes, @@ -57,30 +62,37 @@ class ImageClassifier(classifier.Classifier): @classmethod def create( cls, - model_spec: ms.SupportedModels, train_data: classification_ds.ClassificationDataset, validation_data: classification_ds.ClassificationDataset, - hparams: Optional[hp.HParams] = None, + options: image_classifier_options.ImageClassifierOptions, ) -> 'ImageClassifier': """Creates and trains an image classifier. - Loads data and trains the model based on data for image classification. + Loads data and trains the model based on data for image classification. If a + checkpoint file exists in the {options.hparams.export_dir}/checkpoint/ + directory, the training process will load the weight from the checkpoint + file for continual training. Args: - model_spec: Specification for the model. train_data: Training data. validation_data: Validation data. - hparams: Hyperparameters for training image classifier. + options: configuration to create image classifier. Returns: An instance based on ImageClassifier. """ - if hparams is None: - hparams = hp.HParams() + if options.hparams is None: + options.hparams = hp.HParams() - spec = ms.SupportedModels.get(model_spec) + if options.model_options is None: + options.model_options = model_opt.ImageClassifierModelOptions() + + spec = ms.SupportedModels.get(options.supported_model) image_classifier = cls( - model_spec=spec, label_names=train_data.label_names, hparams=hparams) + model_spec=spec, + label_names=train_data.label_names, + hparams=options.hparams, + model_options=options.model_options) image_classifier._create_model() @@ -90,6 +102,7 @@ class ImageClassifier(classifier.Classifier): return image_classifier + # TODO: Migrate to the shared training library of Model Maker. def _train(self, train_data: classification_ds.ClassificationDataset, validation_data: classification_ds.ClassificationDataset): """Trains the model with input train_data. @@ -142,7 +155,7 @@ class ImageClassifier(classifier.Classifier): self._model = tf.keras.Sequential([ tf.keras.Input(shape=(image_size[0], image_size[1], 3)), module_layer, - tf.keras.layers.Dropout(rate=self._hparams.dropout_rate), + tf.keras.layers.Dropout(rate=self._model_options.dropout_rate), tf.keras.layers.Dense( units=self._num_classes, activation='softmax', @@ -167,10 +180,10 @@ class ImageClassifier(classifier.Classifier): path is {self._hparams.model_dir}/{model_name}. quantization_config: The configuration for model quantization. """ - if not tf.io.gfile.exists(self._hparams.model_dir): - tf.io.gfile.makedirs(self._hparams.model_dir) - tflite_file = os.path.join(self._hparams.model_dir, model_name) - metadata_file = os.path.join(self._hparams.model_dir, 'metadata.json') + if not tf.io.gfile.exists(self._hparams.export_dir): + tf.io.gfile.makedirs(self._hparams.export_dir) + tflite_file = os.path.join(self._hparams.export_dir, model_name) + metadata_file = os.path.join(self._hparams.export_dir, 'metadata.json') tflite_model = model_util.convert_to_tflite( model=self._model, @@ -180,7 +193,7 @@ class ImageClassifier(classifier.Classifier): tflite_model, self._model_spec.mean_rgb, self._model_spec.stddev_rgb, - labels=metadata_writer.Labels().add(self._label_names)) + labels=metadata_writer.Labels().add(list(self._label_names))) tflite_model_with_metadata, metadata_json = writer.populate() model_util.save_tflite(tflite_model_with_metadata, tflite_file) with open(metadata_file, 'w') as f: diff --git a/mediapipe/model_maker/python/vision/image_classifier/image_classifier_options.py b/mediapipe/model_maker/python/vision/image_classifier/image_classifier_options.py new file mode 100644 index 000000000..d3566a9fa --- /dev/null +++ b/mediapipe/model_maker/python/vision/image_classifier/image_classifier_options.py @@ -0,0 +1,35 @@ +# Copyright 2022 The MediaPipe Authors. All Rights Reserved. +# +# 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. +"""Options for building image classifier.""" + +import dataclasses +from typing import Optional + +from mediapipe.model_maker.python.vision.image_classifier import hyperparameters +from mediapipe.model_maker.python.vision.image_classifier import model_options as model_opt +from mediapipe.model_maker.python.vision.image_classifier import model_spec + + +@dataclasses.dataclass +class ImageClassifierOptions: + """Configurable options for building image classifier. + + Attributes: + supported_model: A model from the SupportedModels enum. + model_options: A set of options for configuring the selected model. + hparams: A set of hyperparameters used to train the image classifier. + """ + supported_model: model_spec.SupportedModels + model_options: Optional[model_opt.ImageClassifierModelOptions] = None + hparams: Optional[hyperparameters.HParams] = None diff --git a/mediapipe/model_maker/python/vision/image_classifier/image_classifier_test.py b/mediapipe/model_maker/python/vision/image_classifier/image_classifier_test.py index 2f949e648..252659edc 100644 --- a/mediapipe/model_maker/python/vision/image_classifier/image_classifier_test.py +++ b/mediapipe/model_maker/python/vision/image_classifier/image_classifier_test.py @@ -13,9 +13,13 @@ # limitations under the License. import filecmp +import io import os +import tempfile +from unittest import mock as unittest_mock from absl.testing import parameterized +import mock import numpy as np import tensorflow as tf @@ -54,54 +58,74 @@ class ImageClassifierTest(tf.test.TestCase, parameterized.TestCase): super(ImageClassifierTest, self).setUp() all_data = self._gen_cmy_data() # Splits data, 90% data for training, 10% for testing - self.train_data, self.test_data = all_data.split(0.9) + self._train_data, self._test_data = all_data.split(0.9) @parameterized.named_parameters( dict( testcase_name='mobilenet_v2', - model_spec=image_classifier.SupportedModels.MOBILENET_V2, - hparams=image_classifier.HParams( - train_epochs=1, batch_size=1, shuffle=True)), + options=image_classifier.ImageClassifierOptions( + supported_model=image_classifier.SupportedModels.MOBILENET_V2, + hparams=image_classifier.HParams( + epochs=1, + batch_size=1, + shuffle=True, + export_dir=tempfile.mkdtemp()))), dict( testcase_name='efficientnet_lite0', - model_spec=image_classifier.SupportedModels.EFFICIENTNET_LITE0, - hparams=image_classifier.HParams( - train_epochs=1, batch_size=1, shuffle=True)), + options=image_classifier.ImageClassifierOptions( + supported_model=( + image_classifier.SupportedModels.EFFICIENTNET_LITE0), + hparams=image_classifier.HParams( + epochs=1, + batch_size=1, + shuffle=True, + export_dir=tempfile.mkdtemp()))), + dict( + testcase_name='efficientnet_lite0_change_dropout_rate', + options=image_classifier.ImageClassifierOptions( + supported_model=( + image_classifier.SupportedModels.EFFICIENTNET_LITE0), + model_options=image_classifier.ModelOptions(dropout_rate=0.1), + hparams=image_classifier.HParams( + epochs=1, + batch_size=1, + shuffle=True, + export_dir=tempfile.mkdtemp()))), dict( testcase_name='efficientnet_lite2', - model_spec=image_classifier.SupportedModels.EFFICIENTNET_LITE2, - hparams=image_classifier.HParams( - train_epochs=1, batch_size=1, shuffle=True)), + options=image_classifier.ImageClassifierOptions( + supported_model=( + image_classifier.SupportedModels.EFFICIENTNET_LITE2), + hparams=image_classifier.HParams( + epochs=1, + batch_size=1, + shuffle=True, + export_dir=tempfile.mkdtemp()))), dict( testcase_name='efficientnet_lite4', - model_spec=image_classifier.SupportedModels.EFFICIENTNET_LITE4, - hparams=image_classifier.HParams( - train_epochs=1, batch_size=1, shuffle=True)), + options=image_classifier.ImageClassifierOptions( + supported_model=( + image_classifier.SupportedModels.EFFICIENTNET_LITE4), + hparams=image_classifier.HParams( + epochs=1, + batch_size=1, + shuffle=True, + export_dir=tempfile.mkdtemp()))), ) - def test_create_and_train_model(self, - model_spec: image_classifier.SupportedModels, - hparams: image_classifier.HParams): + def test_create_and_train_model( + self, options: image_classifier.ImageClassifierOptions): model = image_classifier.ImageClassifier.create( - model_spec=model_spec, - train_data=self.train_data, - hparams=hparams, - validation_data=self.test_data) - self._test_accuracy(model) - - def test_efficientnetlite0_model_train_and_export(self): - hparams = image_classifier.HParams( - train_epochs=1, batch_size=1, shuffle=True) - model = image_classifier.ImageClassifier.create( - model_spec=image_classifier.SupportedModels.EFFICIENTNET_LITE0, - train_data=self.train_data, - hparams=hparams, - validation_data=self.test_data) + train_data=self._train_data, + validation_data=self._test_data, + options=options) self._test_accuracy(model) # Test export_model model.export_model() - output_metadata_file = os.path.join(hparams.model_dir, 'metadata.json') - output_tflite_file = os.path.join(hparams.model_dir, 'model.tflite') + output_metadata_file = os.path.join(options.hparams.export_dir, + 'metadata.json') + output_tflite_file = os.path.join(options.hparams.export_dir, + 'model.tflite') expected_metadata_file = test_utils.get_test_data_path('metadata.json') self.assertTrue(os.path.exists(output_tflite_file)) @@ -111,10 +135,50 @@ class ImageClassifierTest(tf.test.TestCase, parameterized.TestCase): self.assertGreater(os.path.getsize(output_metadata_file), 0) self.assertTrue(filecmp.cmp(output_metadata_file, expected_metadata_file)) + def test_continual_training_by_loading_checkpoint(self): + mock_stdout = io.StringIO() + with mock.patch('sys.stdout', mock_stdout): + options = image_classifier.ImageClassifierOptions( + supported_model=image_classifier.SupportedModels.EFFICIENTNET_LITE0, + hparams=image_classifier.HParams( + epochs=5, batch_size=1, shuffle=True)) + model = image_classifier.ImageClassifier.create( + train_data=self._train_data, + validation_data=self._test_data, + options=options) + model = image_classifier.ImageClassifier.create( + train_data=self._train_data, + validation_data=self._test_data, + options=options) + self._test_accuracy(model) + + self.assertRegex(mock_stdout.getvalue(), 'Resuming from') + def _test_accuracy(self, model, threshold=0.0): - _, accuracy = model.evaluate(self.test_data) + _, accuracy = model.evaluate(self._test_data) self.assertGreaterEqual(accuracy, threshold) + @unittest_mock.patch.object( + image_classifier.hyperparameters, + 'HParams', + autospec=True, + return_value=image_classifier.HParams(epochs=1)) + @unittest_mock.patch.object( + image_classifier.model_options, + 'ImageClassifierModelOptions', + autospec=True, + return_value=image_classifier.ModelOptions()) + def test_create_hparams_and_model_options_if_none_in_image_classifier_options( + self, mock_hparams, mock_model_options): + options = image_classifier.ImageClassifierOptions( + supported_model=(image_classifier.SupportedModels.EFFICIENTNET_LITE0)) + image_classifier.ImageClassifier.create( + train_data=self._train_data, + validation_data=self._test_data, + options=options) + mock_hparams.assert_called_once() + mock_model_options.assert_called_once() + if __name__ == '__main__': # Load compressed models from tensorflow_hub diff --git a/mediapipe/model_maker/python/vision/image_classifier/model_options.py b/mediapipe/model_maker/python/vision/image_classifier/model_options.py new file mode 100644 index 000000000..a8f89b577 --- /dev/null +++ b/mediapipe/model_maker/python/vision/image_classifier/model_options.py @@ -0,0 +1,27 @@ +# Copyright 2022 The MediaPipe Authors. All Rights Reserved. +# +# 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. +"""Configurable model options for image classifier models.""" + +import dataclasses + + +@dataclasses.dataclass +class ImageClassifierModelOptions: + """Configurable options for image classifier model. + + Attributes: + dropout_rate: The fraction of the input units to drop, used in dropout + layer. + """ + dropout_rate: float = 0.2 diff --git a/mediapipe/model_maker/python/vision/image_classifier/train_image_classifier_lib.py b/mediapipe/model_maker/python/vision/image_classifier/train_image_classifier_lib.py index 265c36a6e..c5b28cff5 100644 --- a/mediapipe/model_maker/python/vision/image_classifier/train_image_classifier_lib.py +++ b/mediapipe/model_maker/python/vision/image_classifier/train_image_classifier_lib.py @@ -14,8 +14,6 @@ """Library to train model.""" import os -from typing import List - import tensorflow as tf from mediapipe.model_maker.python.core.utils import model_util @@ -49,18 +47,6 @@ def _create_optimizer(init_lr: float, decay_steps: int, return optimizer -def _get_default_callbacks(model_dir: str) -> List[tf.keras.callbacks.Callback]: - """Gets default callbacks.""" - summary_dir = os.path.join(model_dir, 'summaries') - summary_callback = tf.keras.callbacks.TensorBoard(summary_dir) - # Save checkpoint every 20 epochs. - - checkpoint_path = os.path.join(model_dir, 'checkpoint') - checkpoint_callback = tf.keras.callbacks.ModelCheckpoint( - checkpoint_path, save_weights_only=True, period=20) - return [summary_callback, checkpoint_callback] - - def train_model(model: tf.keras.Model, hparams: hp.HParams, train_ds: tf.data.Dataset, validation_ds: tf.data.Dataset) -> tf.keras.callbacks.History: @@ -81,7 +67,8 @@ def train_model(model: tf.keras.Model, hparams: hp.HParams, learning_rate = hparams.learning_rate * hparams.batch_size / 256 # Get decay steps. - total_training_steps = hparams.steps_per_epoch * hparams.train_epochs + # NOMUTANTS--(b/256493858):Plan to test it in the unified training library. + total_training_steps = hparams.steps_per_epoch * hparams.epochs default_decay_steps = hparams.decay_samples // hparams.batch_size decay_steps = max(total_training_steps, default_decay_steps) @@ -92,11 +79,24 @@ def train_model(model: tf.keras.Model, hparams: hp.HParams, loss = tf.keras.losses.CategoricalCrossentropy( label_smoothing=hparams.label_smoothing) model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy']) - callbacks = _get_default_callbacks(hparams.model_dir) + + summary_dir = os.path.join(hparams.export_dir, 'summaries') + summary_callback = tf.keras.callbacks.TensorBoard(summary_dir) + # Save checkpoint every 5 epochs. + checkpoint_path = os.path.join(hparams.export_dir, 'checkpoint') + checkpoint_callback = tf.keras.callbacks.ModelCheckpoint( + os.path.join(checkpoint_path, 'model-{epoch:04d}'), + save_weights_only=True, + period=5) + + latest_checkpoint = tf.train.latest_checkpoint(checkpoint_path) + if latest_checkpoint: + print(f'Resuming from {latest_checkpoint}') + model.load_weights(latest_checkpoint) # Train the model. return model.fit( x=train_ds, - epochs=hparams.train_epochs, + epochs=hparams.epochs, validation_data=validation_ds, - callbacks=callbacks) + callbacks=[summary_callback, checkpoint_callback]) diff --git a/mediapipe/python/BUILD b/mediapipe/python/BUILD index e2ff52e2f..09388ae5a 100644 --- a/mediapipe/python/BUILD +++ b/mediapipe/python/BUILD @@ -87,7 +87,6 @@ cc_library( cc_library( name = "builtin_task_graphs", deps = [ - "//mediapipe/tasks/cc/audio/audio_classifier:audio_classifier_graph", "//mediapipe/tasks/cc/vision/gesture_recognizer:gesture_recognizer_graph", "//mediapipe/tasks/cc/vision/image_classifier:image_classifier_graph", "//mediapipe/tasks/cc/vision/image_segmenter:image_segmenter_graph", @@ -95,8 +94,10 @@ cc_library( "//mediapipe/tasks/cc/vision/image_embedder:image_embedder_graph", ] + select({ # TODO: Build text_classifier_graph on Windows. + # TODO: Build audio_classifier_graph on Windows. "//mediapipe:windows": [], "//conditions:default": [ + "//mediapipe/tasks/cc/audio/audio_classifier:audio_classifier_graph", "//mediapipe/tasks/cc/text/text_classifier:text_classifier_graph", ], }), diff --git a/mediapipe/tasks/cc/components/containers/BUILD b/mediapipe/tasks/cc/components/containers/BUILD index 5004383d2..b5c9427a3 100644 --- a/mediapipe/tasks/cc/components/containers/BUILD +++ b/mediapipe/tasks/cc/components/containers/BUILD @@ -30,6 +30,15 @@ cc_library( ], ) +cc_library( + name = "hand_landmarks_detection_result", + hdrs = ["hand_landmarks_detection_result.h"], + deps = [ + "//mediapipe/framework/formats:classification_cc_proto", + "//mediapipe/framework/formats:landmark_cc_proto", + ], +) + cc_library( name = "category", srcs = ["category.cc"], diff --git a/mediapipe/tasks/cc/components/containers/hand_landmarks_detection_result.h b/mediapipe/tasks/cc/components/containers/hand_landmarks_detection_result.h new file mode 100644 index 000000000..b341cd8d3 --- /dev/null +++ b/mediapipe/tasks/cc/components/containers/hand_landmarks_detection_result.h @@ -0,0 +1,43 @@ +/* Copyright 2022 The MediaPipe Authors. All Rights Reserved. + +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_TASKS_CC_COMPONENTS_CONTAINERS_HAND_LANDMARKS_DETECTION_RESULT_H_ +#define MEDIAPIPE_TASKS_CC_COMPONENTS_CONTAINERS_HAND_LANDMARKS_DETECTION_RESULT_H_ + +#include "mediapipe/framework/formats/classification.pb.h" +#include "mediapipe/framework/formats/landmark.pb.h" + +namespace mediapipe { +namespace tasks { +namespace components { +namespace containers { + +// The hand landmarks detection result from HandLandmarker, where each vector +// element represents a single hand detected in the image. +struct HandLandmarksDetectionResult { + // Classification of handedness. + std::vector handedness; + // Detected hand landmarks in normalized image coordinates. + std::vector hand_landmarks; + // Detected hand landmarks in world coordinates. + std::vector hand_world_landmarks; +}; + +} // namespace containers +} // namespace components +} // namespace tasks +} // namespace mediapipe + +#endif // MEDIAPIPE_TASKS_CC_COMPONENTS_CONTAINERS_HAND_LANDMARKS_DETECTION_RESULT_H_ diff --git a/mediapipe/tasks/cc/components/proto/segmenter_options.proto b/mediapipe/tasks/cc/components/proto/segmenter_options.proto index a2f37d3a0..ca9986707 100644 --- a/mediapipe/tasks/cc/components/proto/segmenter_options.proto +++ b/mediapipe/tasks/cc/components/proto/segmenter_options.proto @@ -17,6 +17,9 @@ syntax = "proto2"; package mediapipe.tasks.components.proto; +option java_package = "com.google.mediapipe.tasks.components.proto"; +option java_outer_classname = "SegmenterOptionsProto"; + // Shared options used by image segmentation tasks. message SegmenterOptions { // Optional output mask type. diff --git a/mediapipe/tasks/cc/text/text_embedder/BUILD b/mediapipe/tasks/cc/text/text_embedder/BUILD new file mode 100644 index 000000000..331902362 --- /dev/null +++ b/mediapipe/tasks/cc/text/text_embedder/BUILD @@ -0,0 +1,87 @@ +# Copyright 2022 The MediaPipe Authors. All Rights Reserved. +# +# 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. + +package(default_visibility = ["//mediapipe/tasks:internal"]) + +licenses(["notice"]) + +cc_library( + name = "text_embedder", + srcs = ["text_embedder.cc"], + hdrs = ["text_embedder.h"], + deps = [ + ":text_embedder_graph", + "//mediapipe/calculators/tensor:inference_calculator_cc_proto", + "//mediapipe/framework:calculator_cc_proto", + "//mediapipe/framework/api2:builder", + "//mediapipe/tasks/cc/components/containers:embedding_result", + "//mediapipe/tasks/cc/components/containers/proto:embeddings_cc_proto", + "//mediapipe/tasks/cc/components/processors:embedder_options", + "//mediapipe/tasks/cc/components/processors/proto:embedder_options_cc_proto", + "//mediapipe/tasks/cc/components/utils:cosine_similarity", + "//mediapipe/tasks/cc/core:base_options", + "//mediapipe/tasks/cc/core:base_task_api", + "//mediapipe/tasks/cc/core:task_api_factory", + "//mediapipe/tasks/cc/core/proto:base_options_cc_proto", + "//mediapipe/tasks/cc/text/text_embedder/proto:text_embedder_graph_options_cc_proto", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "text_embedder_graph", + srcs = ["text_embedder_graph.cc"], + deps = [ + "//mediapipe/calculators/tensor:inference_calculator", + "//mediapipe/calculators/tensor:inference_calculator_cc_proto", + "//mediapipe/framework:calculator_cc_proto", + "//mediapipe/framework:calculator_framework", + "//mediapipe/framework/api2:builder", + "//mediapipe/framework/api2:port", + "//mediapipe/tasks/cc/components:text_preprocessing_graph", + "//mediapipe/tasks/cc/components/containers/proto:embeddings_cc_proto", + "//mediapipe/tasks/cc/components/processors:embedding_postprocessing_graph", + "//mediapipe/tasks/cc/components/processors/proto:embedding_postprocessing_graph_options_cc_proto", + "//mediapipe/tasks/cc/components/proto:text_preprocessing_graph_options_cc_proto", + "//mediapipe/tasks/cc/core:model_resources", + "//mediapipe/tasks/cc/core:model_task_graph", + "//mediapipe/tasks/cc/core/proto:model_resources_calculator_cc_proto", + "//mediapipe/tasks/cc/text/text_embedder/proto:text_embedder_graph_options_cc_proto", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + ], + alwayslink = 1, +) + +cc_test( + name = "text_embedder_test", + srcs = ["text_embedder_test.cc"], + data = [ + "//mediapipe/tasks/testdata/text:mobilebert_embedding_model", + "//mediapipe/tasks/testdata/text:regex_embedding_with_metadata", + ], + deps = [ + ":text_embedder", + "//mediapipe/framework/deps:file_path", + "//mediapipe/framework/port:gtest_main", + "//mediapipe/tasks/cc:common", + "//mediapipe/tasks/cc/components/containers:embedding_result", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@org_tensorflow//tensorflow/lite/core/shims:cc_shims_test_util", + ], +) diff --git a/mediapipe/tasks/cc/text/text_embedder/proto/BUILD b/mediapipe/tasks/cc/text/text_embedder/proto/BUILD new file mode 100644 index 000000000..146483af1 --- /dev/null +++ b/mediapipe/tasks/cc/text/text_embedder/proto/BUILD @@ -0,0 +1,30 @@ +# Copyright 2022 The MediaPipe Authors. All Rights Reserved. +# +# 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. + +load("//mediapipe/framework/port:build_config.bzl", "mediapipe_proto_library") + +package(default_visibility = ["//mediapipe/tasks:internal"]) + +licenses(["notice"]) + +mediapipe_proto_library( + name = "text_embedder_graph_options_proto", + srcs = ["text_embedder_graph_options.proto"], + deps = [ + "//mediapipe/framework:calculator_options_proto", + "//mediapipe/framework:calculator_proto", + "//mediapipe/tasks/cc/components/processors/proto:embedder_options_proto", + "//mediapipe/tasks/cc/core/proto:base_options_proto", + ], +) diff --git a/mediapipe/tasks/cc/text/text_embedder/proto/text_embedder_graph_options.proto b/mediapipe/tasks/cc/text/text_embedder/proto/text_embedder_graph_options.proto new file mode 100644 index 000000000..6b8d41a57 --- /dev/null +++ b/mediapipe/tasks/cc/text/text_embedder/proto/text_embedder_graph_options.proto @@ -0,0 +1,36 @@ +/* Copyright 2022 The MediaPipe Authors. All Rights Reserved. + +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.tasks.text.text_embedder.proto; + +import "mediapipe/framework/calculator.proto"; +import "mediapipe/tasks/cc/components/processors/proto/embedder_options.proto"; +import "mediapipe/tasks/cc/core/proto/base_options.proto"; + +message TextEmbedderGraphOptions { + extend mediapipe.CalculatorOptions { + optional TextEmbedderGraphOptions ext = 477589892; + } + + // Base options for configuring MediaPipe Tasks, such as specifying the TfLite + // model file with metadata, accelerator options, etc. + optional core.proto.BaseOptions base_options = 1; + + // Options for configuring the embedder behavior, such as normalization or + // quantization. + optional components.processors.proto.EmbedderOptions embedder_options = 2; +} diff --git a/mediapipe/tasks/cc/text/text_embedder/text_embedder.cc b/mediapipe/tasks/cc/text/text_embedder/text_embedder.cc new file mode 100644 index 000000000..375058d57 --- /dev/null +++ b/mediapipe/tasks/cc/text/text_embedder/text_embedder.cc @@ -0,0 +1,104 @@ +/* Copyright 2022 The MediaPipe Authors. All Rights Reserved. + +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/tasks/cc/text/text_embedder/text_embedder.h" + +#include + +#include "absl/status/statusor.h" +#include "mediapipe/calculators/tensor/inference_calculator.pb.h" +#include "mediapipe/framework/api2/builder.h" +#include "mediapipe/framework/calculator.pb.h" +#include "mediapipe/tasks/cc/components/containers/embedding_result.h" +#include "mediapipe/tasks/cc/components/containers/proto/embeddings.pb.h" +#include "mediapipe/tasks/cc/components/processors/embedder_options.h" +#include "mediapipe/tasks/cc/components/processors/proto/embedder_options.pb.h" +#include "mediapipe/tasks/cc/components/utils/cosine_similarity.h" +#include "mediapipe/tasks/cc/core/base_options.h" +#include "mediapipe/tasks/cc/core/proto/base_options.pb.h" +#include "mediapipe/tasks/cc/core/task_api_factory.h" +#include "mediapipe/tasks/cc/text/text_embedder/proto/text_embedder_graph_options.pb.h" + +namespace mediapipe::tasks::text::text_embedder { +namespace { + +constexpr char kTextTag[] = "TEXT"; +constexpr char kEmbeddingsTag[] = "EMBEDDINGS"; +constexpr char kTextInStreamName[] = "text_in"; +constexpr char kEmbeddingsStreamName[] = "embeddings_out"; +constexpr char kGraphTypeName[] = + "mediapipe.tasks.text.text_embedder.TextEmbedderGraph"; + +using ::mediapipe::tasks::components::containers::ConvertToEmbeddingResult; +using ::mediapipe::tasks::components::containers::proto::EmbeddingResult; + +// Creates a MediaPipe graph config that contains a single node of type +// "mediapipe.tasks.text.text_embedder.TextEmbedderGraph". +CalculatorGraphConfig CreateGraphConfig( + std::unique_ptr options_proto) { + api2::builder::Graph graph; + auto& task_graph = graph.AddNode(kGraphTypeName); + task_graph.GetOptions().Swap( + options_proto.get()); + graph.In(kTextTag).SetName(kTextInStreamName) >> task_graph.In(kTextTag); + task_graph.Out(kEmbeddingsTag).SetName(kEmbeddingsStreamName) >> + graph.Out(kEmbeddingsTag); + return graph.GetConfig(); +} + +// Converts the user-facing TextEmbedderOptions struct to the internal +// TextEmbedderGraphOptions proto. +std::unique_ptr +ConvertTextEmbedderOptionsToProto(TextEmbedderOptions* options) { + auto options_proto = std::make_unique(); + auto base_options_proto = std::make_unique( + tasks::core::ConvertBaseOptionsToProto(&(options->base_options))); + options_proto->mutable_base_options()->Swap(base_options_proto.get()); + auto embedder_options_proto = + std::make_unique( + components::processors::ConvertEmbedderOptionsToProto( + &(options->embedder_options))); + options_proto->mutable_embedder_options()->Swap(embedder_options_proto.get()); + return options_proto; +} + +} // namespace + +absl::StatusOr> TextEmbedder::Create( + std::unique_ptr options) { + std::unique_ptr options_proto = + ConvertTextEmbedderOptionsToProto(options.get()); + return core::TaskApiFactory::Create( + CreateGraphConfig(std::move(options_proto)), + std::move(options->base_options.op_resolver)); +} + +absl::StatusOr TextEmbedder::Embed(absl::string_view text) { + ASSIGN_OR_RETURN( + auto output_packets, + runner_->Process( + {{kTextInStreamName, MakePacket(std::string(text))}})); + return ConvertToEmbeddingResult( + output_packets[kEmbeddingsStreamName].Get()); +} + +absl::StatusOr TextEmbedder::CosineSimilarity( + const components::containers::Embedding& u, + const components::containers::Embedding& v) { + return components::utils::CosineSimilarity(u, v); +} + +} // namespace mediapipe::tasks::text::text_embedder diff --git a/mediapipe/tasks/cc/text/text_embedder/text_embedder.h b/mediapipe/tasks/cc/text/text_embedder/text_embedder.h new file mode 100644 index 000000000..81f90fd27 --- /dev/null +++ b/mediapipe/tasks/cc/text/text_embedder/text_embedder.h @@ -0,0 +1,96 @@ +/* Copyright 2022 The MediaPipe Authors. All Rights Reserved. + +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_TASKS_CC_TEXT_TEXT_EMBEDDER_TEXT_EMBEDDER_H_ +#define MEDIAPIPE_TASKS_CC_TEXT_TEXT_EMBEDDER_TEXT_EMBEDDER_H_ + +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "mediapipe/tasks/cc/components/containers/embedding_result.h" +#include "mediapipe/tasks/cc/components/processors/embedder_options.h" +#include "mediapipe/tasks/cc/core/base_options.h" +#include "mediapipe/tasks/cc/core/base_task_api.h" + +namespace mediapipe::tasks::text::text_embedder { + +// Alias the shared EmbeddingResult struct as result typo. +using TextEmbedderResult = + ::mediapipe::tasks::components::containers::EmbeddingResult; + +// Options for configuring a MediaPipe text embedder task. +struct TextEmbedderOptions { + // Base options for configuring MediaPipe Tasks, such as specifying the model + // file with metadata, accelerator options, op resolver, etc. + tasks::core::BaseOptions base_options; + + // Options for configuring the embedder behavior, such as L2-normalization or + // scalar-quantization. + components::processors::EmbedderOptions embedder_options; +}; + +// Performs embedding extraction on text. +// +// This API expects a TFLite model with TFLite Model Metadata that contains the +// mandatory (described below) input tensors and output tensors. Metadata should +// contain the input process unit for the model's Tokenizer as well as input / +// output tensor metadata. +// +// TODO: Support Universal Sentence Encoder. +// Input tensors: +// (kTfLiteInt32) +// - 3 input tensors of size `[batch_size x bert_max_seq_len]` with names +// "ids", "mask", and "segment_ids" representing the input ids, mask ids, and +// segment ids respectively +// - or 1 input tensor of size `[batch_size x max_seq_len]` representing the +// input ids +// +// At least one output tensor with: +// (kTfLiteFloat32) +// - `N` components corresponding to the `N` dimensions of the returned +// feature vector for this output layer. +// - Either 2 or 4 dimensions, i.e. `[1 x N]` or `[1 x 1 x 1 x N]`. +class TextEmbedder : core::BaseTaskApi { + public: + using BaseTaskApi::BaseTaskApi; + + // Creates a TextEmbedder from the provided `options`. A non-default + // OpResolver can be specified in the BaseOptions in order to support custom + // Ops or specify a subset of built-in Ops. + static absl::StatusOr> Create( + std::unique_ptr options); + + // Performs embedding extraction on the input `text`. + absl::StatusOr Embed(absl::string_view text); + + // Shuts down the TextEmbedder when all the work is done. + absl::Status Close() { return runner_->Close(); } + + // Utility function to compute cosine similarity [1] between two embeddings. + // May return an InvalidArgumentError if e.g. the embeddings are of different + // types (quantized vs. float), have different sizes, or have a an L2-norm of + // 0. + // + // [1]: https://en.wikipedia.org/wiki/Cosine_similarity + static absl::StatusOr CosineSimilarity( + const components::containers::Embedding& u, + const components::containers::Embedding& v); +}; + +} // namespace mediapipe::tasks::text::text_embedder + +#endif // MEDIAPIPE_TASKS_CC_TEXT_TEXT_EMBEDDER_TEXT_EMBEDDER_H_ diff --git a/mediapipe/tasks/cc/text/text_embedder/text_embedder_graph.cc b/mediapipe/tasks/cc/text/text_embedder/text_embedder_graph.cc new file mode 100644 index 000000000..79eedb6b5 --- /dev/null +++ b/mediapipe/tasks/cc/text/text_embedder/text_embedder_graph.cc @@ -0,0 +1,145 @@ +/* Copyright 2022 The MediaPipe Authors. All Rights Reserved. + +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 "absl/status/status.h" +#include "absl/status/statusor.h" +#include "mediapipe/calculators/tensor/inference_calculator.pb.h" +#include "mediapipe/framework/api2/builder.h" +#include "mediapipe/framework/api2/port.h" +#include "mediapipe/framework/calculator.pb.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/tasks/cc/components/containers/proto/embeddings.pb.h" +#include "mediapipe/tasks/cc/components/processors/embedding_postprocessing_graph.h" +#include "mediapipe/tasks/cc/components/processors/proto/embedding_postprocessing_graph_options.pb.h" +#include "mediapipe/tasks/cc/components/proto/text_preprocessing_graph_options.pb.h" +#include "mediapipe/tasks/cc/components/text_preprocessing_graph.h" +#include "mediapipe/tasks/cc/core/model_resources.h" +#include "mediapipe/tasks/cc/core/model_task_graph.h" +#include "mediapipe/tasks/cc/core/proto/model_resources_calculator.pb.h" +#include "mediapipe/tasks/cc/text/text_embedder/proto/text_embedder_graph_options.pb.h" + +namespace mediapipe::tasks::text::text_embedder { +namespace { + +using ::mediapipe::api2::Input; +using ::mediapipe::api2::Output; +using ::mediapipe::api2::builder::Graph; +using ::mediapipe::api2::builder::Source; +using ::mediapipe::tasks::components::containers::proto::EmbeddingResult; +using ::mediapipe::tasks::core::ModelResources; + +constexpr char kEmbeddingsTag[] = "EMBEDDINGS"; +constexpr char kTextTag[] = "TEXT"; +constexpr char kMetadataExtractorTag[] = "METADATA_EXTRACTOR"; +constexpr char kTensorsTag[] = "TENSORS"; + +} // namespace + +// A "mediapipe.tasks.text.TextEmbedderGraph" performs text embedding +// extraction. +// - Accepts input text and outputs embeddings on CPU. +// +// Inputs: +// TEXT - std::string +// Input text to perform embedding extraction on. +// +// Outputs: +// EMBEDDINGS - EmbeddingResult +// The embedding result. +// +// Example: +// node { +// calculator: "mediapipe.tasks.text.TextEmbedderGraph" +// input_stream: "TEXT:text_in" +// output_stream: "EMBEDDINGS:embedding_result_out" +// options { +// [mediapipe.tasks.text.text_embedder.proto.TextEmbedderGraphOptions.ext] { +// base_options { +// model_asset { +// file_name: "/path/to/model.tflite" +// } +// } +// } +// } +// } +class TextEmbedderGraph : public core::ModelTaskGraph { + public: + absl::StatusOr GetConfig( + SubgraphContext* sc) override { + CHECK(sc != nullptr); + ASSIGN_OR_RETURN(const ModelResources* model_resources, + CreateModelResources(sc)); + Graph graph; + ASSIGN_OR_RETURN( + Source embedding_result_out, + BuildTextEmbedderTask(sc->Options(), + *model_resources, + graph[Input(kTextTag)], graph)); + embedding_result_out >> graph[Output(kEmbeddingsTag)]; + return graph.GetConfig(); + } + + private: + // Adds a mediapipe TextEmbedder task graph into the provided + // builder::Graph instance. The TextEmbedder task takes an input + // text (std::string) and returns an embedding result. + // + // task_options: the mediapipe tasks TextEmbedderGraphOptions proto. + // model_resources: the ModelResources object initialized from a + // TextEmbedder model file with model metadata. + // text_in: (std::string) stream to run embedding extraction on. + // graph: the mediapipe builder::Graph instance to be updated. + absl::StatusOr> BuildTextEmbedderTask( + const proto::TextEmbedderGraphOptions& task_options, + const ModelResources& model_resources, Source text_in, + Graph& graph) { + // Adds preprocessing calculators and connects them to the text input + // stream. + auto& preprocessing = + graph.AddNode("mediapipe.tasks.components.TextPreprocessingSubgraph"); + MP_RETURN_IF_ERROR(components::ConfigureTextPreprocessingSubgraph( + model_resources, + preprocessing.GetOptions< + tasks::components::proto::TextPreprocessingGraphOptions>())); + text_in >> preprocessing.In(kTextTag); + + // Adds both InferenceCalculator and ModelResourcesCalculator. + auto& inference = AddInference( + model_resources, task_options.base_options().acceleration(), graph); + // The metadata extractor side-output comes from the + // ModelResourcesCalculator. + inference.SideOut(kMetadataExtractorTag) >> + preprocessing.SideIn(kMetadataExtractorTag); + preprocessing.Out(kTensorsTag) >> inference.In(kTensorsTag); + + // Adds postprocessing calculators and connects its input stream to the + // inference results. + auto& postprocessing = graph.AddNode( + "mediapipe.tasks.components.processors.EmbeddingPostprocessingGraph"); + MP_RETURN_IF_ERROR(components::processors::ConfigureEmbeddingPostprocessing( + model_resources, task_options.embedder_options(), + &postprocessing.GetOptions())); + inference.Out(kTensorsTag) >> postprocessing.In(kTensorsTag); + + // Outputs the embedding result. + return postprocessing[Output(kEmbeddingsTag)]; + } +}; + +REGISTER_MEDIAPIPE_GRAPH( + ::mediapipe::tasks::text::text_embedder::TextEmbedderGraph); + +} // namespace mediapipe::tasks::text::text_embedder diff --git a/mediapipe/tasks/cc/text/text_embedder/text_embedder_test.cc b/mediapipe/tasks/cc/text/text_embedder/text_embedder_test.cc new file mode 100644 index 000000000..fa3d8af91 --- /dev/null +++ b/mediapipe/tasks/cc/text/text_embedder/text_embedder_test.cc @@ -0,0 +1,143 @@ +/* Copyright 2022 The MediaPipe Authors. All Rights Reserved. + +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/tasks/cc/text/text_embedder/text_embedder.h" + +#include + +#include "absl/flags/flag.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "mediapipe/framework/deps/file_path.h" +#include "mediapipe/framework/port/gmock.h" +#include "mediapipe/framework/port/gtest.h" +#include "mediapipe/framework/port/status_matchers.h" +#include "mediapipe/tasks/cc/common.h" +#include "mediapipe/tasks/cc/components/containers/embedding_result.h" +#include "tensorflow/lite/core/shims/cc/shims_test_util.h" + +namespace mediapipe::tasks::text::text_embedder { +namespace { + +constexpr char kTestDataDirectory[] = "/mediapipe/tasks/testdata/text/"; + +// Note that these models use dynamic-sized tensors. +// Embedding model with BERT preprocessing. +constexpr char kMobileBert[] = "mobilebert_embedding_with_metadata.tflite"; +// Embedding model with regex preprocessing. +constexpr char kRegexOneEmbeddingModel[] = + "regex_one_embedding_with_metadata.tflite"; + +// Tolerance for embedding vector coordinate values. +constexpr float kEpsilon = 1e-4; +// Tolerancy for cosine similarity evaluation. +constexpr double kSimilarityTolerancy = 1e-6; + +using ::mediapipe::file::JoinPath; +using ::testing::HasSubstr; +using ::testing::Optional; + +class EmbedderTest : public tflite_shims::testing::Test {}; + +TEST_F(EmbedderTest, FailsWithMissingModel) { + auto text_embedder = + TextEmbedder::Create(std::make_unique()); + ASSERT_EQ(text_embedder.status().code(), absl::StatusCode::kInvalidArgument); + ASSERT_THAT( + text_embedder.status().message(), + HasSubstr("ExternalFile must specify at least one of 'file_content', " + "'file_name', 'file_pointer_meta' or 'file_descriptor_meta'.")); + ASSERT_THAT(text_embedder.status().GetPayload(kMediaPipeTasksPayload), + Optional(absl::Cord(absl::StrCat( + MediaPipeTasksStatus::kRunnerInitializationError)))); +} + +TEST_F(EmbedderTest, SucceedsWithMobileBert) { + auto options = std::make_unique(); + options->base_options.model_asset_path = + JoinPath("./", kTestDataDirectory, kMobileBert); + MP_ASSERT_OK_AND_ASSIGN(std::unique_ptr text_embedder, + TextEmbedder::Create(std::move(options))); + MP_ASSERT_OK_AND_ASSIGN( + TextEmbedderResult result0, + text_embedder->Embed("it's a charming and often affecting journey")); + ASSERT_EQ(result0.embeddings.size(), 1); + ASSERT_EQ(result0.embeddings[0].float_embedding.size(), 512); + ASSERT_NEAR(result0.embeddings[0].float_embedding[0], 19.9016f, kEpsilon); + + MP_ASSERT_OK_AND_ASSIGN( + auto result1, text_embedder->Embed("what a great and fantastic trip")); + ASSERT_EQ(result1.embeddings.size(), 1); + ASSERT_EQ(result1.embeddings[0].float_embedding.size(), 512); + ASSERT_NEAR(result1.embeddings[0].float_embedding[0], 22.626251f, kEpsilon); + + // Check cosine similarity. + MP_ASSERT_OK_AND_ASSIGN( + double similarity, TextEmbedder::CosineSimilarity(result0.embeddings[0], + result1.embeddings[0])); + EXPECT_NEAR(similarity, 0.969514, kSimilarityTolerancy); + + MP_ASSERT_OK(text_embedder->Close()); +} + +TEST(EmbedTest, SucceedsWithRegexOneEmbeddingModel) { + auto options = std::make_unique(); + options->base_options.model_asset_path = + JoinPath("./", kTestDataDirectory, kRegexOneEmbeddingModel); + MP_ASSERT_OK_AND_ASSIGN(std::unique_ptr text_embedder, + TextEmbedder::Create(std::move(options))); + + MP_ASSERT_OK_AND_ASSIGN( + auto result0, + text_embedder->Embed("it's a charming and often affecting journey")); + EXPECT_EQ(result0.embeddings.size(), 1); + EXPECT_EQ(result0.embeddings[0].float_embedding.size(), 16); + + EXPECT_NEAR(result0.embeddings[0].float_embedding[0], 0.0309356f, kEpsilon); + + MP_ASSERT_OK_AND_ASSIGN( + auto result1, text_embedder->Embed("what a great and fantastic trip")); + EXPECT_EQ(result1.embeddings.size(), 1); + EXPECT_EQ(result1.embeddings[0].float_embedding.size(), 16); + + EXPECT_NEAR(result1.embeddings[0].float_embedding[0], 0.0312863f, kEpsilon); + + // Check cosine similarity. + MP_ASSERT_OK_AND_ASSIGN( + double similarity, TextEmbedder::CosineSimilarity(result0.embeddings[0], + result1.embeddings[0])); + EXPECT_NEAR(similarity, 0.999937, kSimilarityTolerancy); + + MP_ASSERT_OK(text_embedder->Close()); +} + +TEST_F(EmbedderTest, SucceedsWithQuantization) { + auto options = std::make_unique(); + options->base_options.model_asset_path = + JoinPath("./", kTestDataDirectory, kMobileBert); + options->embedder_options.quantize = true; + MP_ASSERT_OK_AND_ASSIGN(std::unique_ptr text_embedder, + TextEmbedder::Create(std::move(options))); + MP_ASSERT_OK_AND_ASSIGN( + TextEmbedderResult result, + text_embedder->Embed("it's a charming and often affecting journey")); + ASSERT_EQ(result.embeddings.size(), 1); + ASSERT_EQ(result.embeddings[0].quantized_embedding.size(), 512); + + MP_ASSERT_OK(text_embedder->Close()); +} + +} // namespace +} // namespace mediapipe::tasks::text::text_embedder diff --git a/mediapipe/tasks/cc/vision/hand_landmarker/BUILD b/mediapipe/tasks/cc/vision/hand_landmarker/BUILD index 9090fc7b3..084934286 100644 --- a/mediapipe/tasks/cc/vision/hand_landmarker/BUILD +++ b/mediapipe/tasks/cc/vision/hand_landmarker/BUILD @@ -110,4 +110,38 @@ cc_library( alwayslink = 1, ) +cc_library( + name = "hand_landmarker", + srcs = ["hand_landmarker.cc"], + hdrs = ["hand_landmarker.h"], + deps = [ + ":hand_landmarker_graph", + "//mediapipe/framework/api2:builder", + "//mediapipe/framework/api2:port", + "//mediapipe/framework/formats:classification_cc_proto", + "//mediapipe/framework/formats:image", + "//mediapipe/framework/formats:landmark_cc_proto", + "//mediapipe/framework/formats:rect_cc_proto", + "//mediapipe/tasks/cc:common", + "//mediapipe/tasks/cc/components:image_preprocessing", + "//mediapipe/tasks/cc/components/containers:hand_landmarks_detection_result", + "//mediapipe/tasks/cc/components/processors:classifier_options", + "//mediapipe/tasks/cc/components/processors/proto:classifier_options_cc_proto", + "//mediapipe/tasks/cc/core:base_options", + "//mediapipe/tasks/cc/core:base_task_api", + "//mediapipe/tasks/cc/core:model_resources", + "//mediapipe/tasks/cc/core:task_runner", + "//mediapipe/tasks/cc/core:utils", + "//mediapipe/tasks/cc/core/proto:inference_subgraph_cc_proto", + "//mediapipe/tasks/cc/vision/core:base_vision_task_api", + "//mediapipe/tasks/cc/vision/core:image_processing_options", + "//mediapipe/tasks/cc/vision/core:running_mode", + "//mediapipe/tasks/cc/vision/core:vision_task_api_factory", + "//mediapipe/tasks/cc/vision/hand_detector/proto:hand_detector_graph_options_cc_proto", + "//mediapipe/tasks/cc/vision/hand_landmarker/proto:hand_landmarker_graph_options_cc_proto", + "//mediapipe/tasks/cc/vision/hand_landmarker/proto:hand_landmarks_detector_graph_options_cc_proto", + "@com_google_absl//absl/status:statusor", + ], +) + # TODO: Enable this test diff --git a/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker.cc b/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker.cc new file mode 100644 index 000000000..ec9790c30 --- /dev/null +++ b/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker.cc @@ -0,0 +1,269 @@ +/* Copyright 2022 The MediaPipe Authors. All Rights Reserved. + +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/tasks/cc/vision/hand_landmarker/hand_landmarker.h" + +#include "mediapipe/framework/api2/builder.h" +#include "mediapipe/framework/api2/port.h" +#include "mediapipe/framework/formats/classification.pb.h" +#include "mediapipe/framework/formats/image.h" +#include "mediapipe/framework/formats/landmark.pb.h" +#include "mediapipe/framework/formats/rect.pb.h" +#include "mediapipe/tasks/cc/common.h" +#include "mediapipe/tasks/cc/components/containers/hand_landmarks_detection_result.h" +#include "mediapipe/tasks/cc/components/image_preprocessing.h" +#include "mediapipe/tasks/cc/components/processors/proto/classifier_options.pb.h" +#include "mediapipe/tasks/cc/core/base_task_api.h" +#include "mediapipe/tasks/cc/core/model_resources.h" +#include "mediapipe/tasks/cc/core/proto/inference_subgraph.pb.h" +#include "mediapipe/tasks/cc/core/task_runner.h" +#include "mediapipe/tasks/cc/core/utils.h" +#include "mediapipe/tasks/cc/vision/core/base_vision_task_api.h" +#include "mediapipe/tasks/cc/vision/core/image_processing_options.h" +#include "mediapipe/tasks/cc/vision/core/vision_task_api_factory.h" +#include "mediapipe/tasks/cc/vision/hand_detector/proto/hand_detector_graph_options.pb.h" +#include "mediapipe/tasks/cc/vision/hand_landmarker/proto/hand_landmarker_graph_options.pb.h" +#include "mediapipe/tasks/cc/vision/hand_landmarker/proto/hand_landmarks_detector_graph_options.pb.h" + +namespace mediapipe { +namespace tasks { +namespace vision { +namespace hand_landmarker { + +namespace { + +using HandLandmarkerGraphOptionsProto = ::mediapipe::tasks::vision:: + hand_landmarker::proto::HandLandmarkerGraphOptions; + +using ::mediapipe::tasks::components::containers::HandLandmarksDetectionResult; + +constexpr char kHandLandmarkerGraphTypeName[] = + "mediapipe.tasks.vision.hand_landmarker.HandLandmarkerGraph"; + +constexpr char kImageTag[] = "IMAGE"; +constexpr char kImageInStreamName[] = "image_in"; +constexpr char kImageOutStreamName[] = "image_out"; +constexpr char kNormRectTag[] = "NORM_RECT"; +constexpr char kNormRectStreamName[] = "norm_rect_in"; +constexpr char kHandednessTag[] = "HANDEDNESS"; +constexpr char kHandednessStreamName[] = "handedness"; +constexpr char kHandLandmarksTag[] = "LANDMARKS"; +constexpr char kHandLandmarksStreamName[] = "landmarks"; +constexpr char kHandWorldLandmarksTag[] = "WORLD_LANDMARKS"; +constexpr char kHandWorldLandmarksStreamName[] = "world_landmarks"; +constexpr int kMicroSecondsPerMilliSecond = 1000; + +// Creates a MediaPipe graph config that contains a subgraph node of +// "mediapipe.tasks.vision.hand_ladnamrker.HandLandmarkerGraph". If the task is +// running in the live stream mode, a "FlowLimiterCalculator" will be added to +// limit the number of frames in flight. +CalculatorGraphConfig CreateGraphConfig( + std::unique_ptr options, + bool enable_flow_limiting) { + api2::builder::Graph graph; + auto& subgraph = graph.AddNode(kHandLandmarkerGraphTypeName); + subgraph.GetOptions().Swap(options.get()); + graph.In(kImageTag).SetName(kImageInStreamName); + graph.In(kNormRectTag).SetName(kNormRectStreamName); + subgraph.Out(kHandednessTag).SetName(kHandednessStreamName) >> + graph.Out(kHandednessTag); + subgraph.Out(kHandLandmarksTag).SetName(kHandLandmarksStreamName) >> + graph.Out(kHandLandmarksTag); + subgraph.Out(kHandWorldLandmarksTag).SetName(kHandWorldLandmarksStreamName) >> + graph.Out(kHandWorldLandmarksTag); + subgraph.Out(kImageTag).SetName(kImageOutStreamName) >> graph.Out(kImageTag); + if (enable_flow_limiting) { + return tasks::core::AddFlowLimiterCalculator( + graph, subgraph, {kImageTag, kNormRectTag}, kHandLandmarksTag); + } + graph.In(kImageTag) >> subgraph.In(kImageTag); + graph.In(kNormRectTag) >> subgraph.In(kNormRectTag); + return graph.GetConfig(); +} + +// Converts the user-facing HandLandmarkerOptions struct to the internal +// HandLandmarkerGraphOptions proto. +std::unique_ptr +ConvertHandLandmarkerGraphOptionsProto(HandLandmarkerOptions* options) { + auto options_proto = std::make_unique(); + auto base_options_proto = std::make_unique( + tasks::core::ConvertBaseOptionsToProto(&(options->base_options))); + options_proto->mutable_base_options()->Swap(base_options_proto.get()); + options_proto->mutable_base_options()->set_use_stream_mode( + options->running_mode != core::RunningMode::IMAGE); + + // Configure hand detector options. + auto* hand_detector_graph_options = + options_proto->mutable_hand_detector_graph_options(); + hand_detector_graph_options->set_num_hands(options->num_hands); + hand_detector_graph_options->set_min_detection_confidence( + options->min_hand_detection_confidence); + + // Configure hand landmark detector options. + options_proto->set_min_tracking_confidence(options->min_tracking_confidence); + auto* hand_landmarks_detector_graph_options = + options_proto->mutable_hand_landmarks_detector_graph_options(); + hand_landmarks_detector_graph_options->set_min_detection_confidence( + options->min_hand_presence_confidence); + + return options_proto; +} + +} // namespace + +absl::StatusOr> HandLandmarker::Create( + std::unique_ptr options) { + auto options_proto = ConvertHandLandmarkerGraphOptionsProto(options.get()); + tasks::core::PacketsCallback packets_callback = nullptr; + if (options->result_callback) { + auto result_callback = options->result_callback; + packets_callback = [=](absl::StatusOr + status_or_packets) { + if (!status_or_packets.ok()) { + Image image; + result_callback(status_or_packets.status(), image, + Timestamp::Unset().Value()); + return; + } + if (status_or_packets.value()[kImageOutStreamName].IsEmpty()) { + return; + } + Packet image_packet = status_or_packets.value()[kImageOutStreamName]; + if (status_or_packets.value()[kHandLandmarksStreamName].IsEmpty()) { + Packet empty_packet = + status_or_packets.value()[kHandLandmarksStreamName]; + result_callback( + {HandLandmarksDetectionResult()}, image_packet.Get(), + empty_packet.Timestamp().Value() / kMicroSecondsPerMilliSecond); + return; + } + Packet handedness_packet = + status_or_packets.value()[kHandednessStreamName]; + Packet hand_landmarks_packet = + status_or_packets.value()[kHandLandmarksStreamName]; + Packet hand_world_landmarks_packet = + status_or_packets.value()[kHandWorldLandmarksStreamName]; + result_callback( + {{handedness_packet.Get>(), + hand_landmarks_packet.Get>(), + hand_world_landmarks_packet.Get>()}}, + image_packet.Get(), + hand_landmarks_packet.Timestamp().Value() / + kMicroSecondsPerMilliSecond); + }; + } + return core::VisionTaskApiFactory::Create( + CreateGraphConfig( + std::move(options_proto), + options->running_mode == core::RunningMode::LIVE_STREAM), + std::move(options->base_options.op_resolver), options->running_mode, + std::move(packets_callback)); +} + +absl::StatusOr HandLandmarker::Detect( + mediapipe::Image image, + std::optional image_processing_options) { + if (image.UsesGpu()) { + return CreateStatusWithPayload( + absl::StatusCode::kInvalidArgument, + "GPU input images are currently not supported.", + MediaPipeTasksStatus::kRunnerUnexpectedInputError); + } + ASSIGN_OR_RETURN( + NormalizedRect norm_rect, + ConvertToNormalizedRect(image_processing_options, /*roi_allowed=*/false)); + ASSIGN_OR_RETURN( + auto output_packets, + ProcessImageData( + {{kImageInStreamName, MakePacket(std::move(image))}, + {kNormRectStreamName, + MakePacket(std::move(norm_rect))}})); + if (output_packets[kHandLandmarksStreamName].IsEmpty()) { + return {HandLandmarksDetectionResult()}; + } + return {{/* handedness= */ + {output_packets[kHandednessStreamName] + .Get>()}, + /* hand_landmarks= */ + {output_packets[kHandLandmarksStreamName] + .Get>()}, + /* hand_world_landmarks */ + {output_packets[kHandWorldLandmarksStreamName] + .Get>()}}}; +} + +absl::StatusOr HandLandmarker::DetectForVideo( + mediapipe::Image image, int64 timestamp_ms, + std::optional image_processing_options) { + if (image.UsesGpu()) { + return CreateStatusWithPayload( + absl::StatusCode::kInvalidArgument, + absl::StrCat("GPU input images are currently not supported."), + MediaPipeTasksStatus::kRunnerUnexpectedInputError); + } + ASSIGN_OR_RETURN( + NormalizedRect norm_rect, + ConvertToNormalizedRect(image_processing_options, /*roi_allowed=*/false)); + ASSIGN_OR_RETURN( + auto output_packets, + ProcessVideoData( + {{kImageInStreamName, + MakePacket(std::move(image)) + .At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}, + {kNormRectStreamName, + MakePacket(std::move(norm_rect)) + .At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}})); + if (output_packets[kHandLandmarksStreamName].IsEmpty()) { + return {HandLandmarksDetectionResult()}; + } + return { + {/* handedness= */ + {output_packets[kHandednessStreamName] + .Get>()}, + /* hand_landmarks= */ + {output_packets[kHandLandmarksStreamName] + .Get>()}, + /* hand_world_landmarks */ + {output_packets[kHandWorldLandmarksStreamName] + .Get>()}}, + }; +} + +absl::Status HandLandmarker::DetectAsync( + mediapipe::Image image, int64 timestamp_ms, + std::optional image_processing_options) { + if (image.UsesGpu()) { + return CreateStatusWithPayload( + absl::StatusCode::kInvalidArgument, + absl::StrCat("GPU input images are currently not supported."), + MediaPipeTasksStatus::kRunnerUnexpectedInputError); + } + ASSIGN_OR_RETURN( + NormalizedRect norm_rect, + ConvertToNormalizedRect(image_processing_options, /*roi_allowed=*/false)); + return SendLiveStreamData( + {{kImageInStreamName, + MakePacket(std::move(image)) + .At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}, + {kNormRectStreamName, + MakePacket(std::move(norm_rect)) + .At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}}); +} + +} // namespace hand_landmarker +} // namespace vision +} // namespace tasks +} // namespace mediapipe diff --git a/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker.h b/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker.h new file mode 100644 index 000000000..3538ab3f5 --- /dev/null +++ b/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker.h @@ -0,0 +1,192 @@ +/* Copyright 2022 The MediaPipe Authors. All Rights Reserved. + +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_TASKS_CC_VISION_HAND_LANDMARKER_HAND_LANDMARKER_H_ +#define MEDIAPIPE_TASKS_CC_VISION_HAND_LANDMARKER_HAND_LANDMARKER_H_ + +#include +#include +#include + +#include "absl/status/statusor.h" +#include "mediapipe/framework/formats/classification.pb.h" +#include "mediapipe/framework/formats/image.h" +#include "mediapipe/framework/formats/landmark.pb.h" +#include "mediapipe/tasks/cc/components/containers/hand_landmarks_detection_result.h" +#include "mediapipe/tasks/cc/components/processors/classifier_options.h" +#include "mediapipe/tasks/cc/core/base_options.h" +#include "mediapipe/tasks/cc/vision/core/base_vision_task_api.h" +#include "mediapipe/tasks/cc/vision/core/image_processing_options.h" +#include "mediapipe/tasks/cc/vision/core/running_mode.h" + +namespace mediapipe { +namespace tasks { +namespace vision { +namespace hand_landmarker { + +struct HandLandmarkerOptions { + // Base options for configuring MediaPipe Tasks library, such as specifying + // the TfLite model bundle file with metadata, accelerator options, op + // resolver, etc. + tasks::core::BaseOptions base_options; + + // The running mode of the task. Default to the image mode. + // HandLandmarker has three running modes: + // 1) The image mode for detecting hand landmarks on single image inputs. + // 2) The video mode for detecting hand landmarks on the decoded frames of a + // video. + // 3) The live stream mode for detecting hand landmarks on the live stream of + // input data, such as from camera. In this mode, the "result_callback" + // below must be specified to receive the detection results asynchronously. + core::RunningMode running_mode = core::RunningMode::IMAGE; + + // The maximum number of hands can be detected by the HandLandmarker. + int num_hands = 1; + + // The minimum confidence score for the hand detection to be considered + // successful. + float min_hand_detection_confidence = 0.5; + + // The minimum confidence score of hand presence score in the hand landmark + // detection. + float min_hand_presence_confidence = 0.5; + + // The minimum confidence score for the hand tracking to be considered + // successful. + float min_tracking_confidence = 0.5; + + // The user-defined result callback for processing live stream data. + // The result callback should only be specified when the running mode is set + // to RunningMode::LIVE_STREAM. + std::function, + const Image&, int64)> + result_callback = nullptr; +}; + +// Performs hand landmarks detection on the given image. +// +// TODO add the link to DevSite. +// This API expects a pre-trained hand landmarker model asset bundle. +// +// Inputs: +// Image +// - The image that hand landmarks detection runs on. +// std::optional +// - If provided, can be used to specify the rotation to apply to the image +// before performing hand landmarks detection, by setting its 'rotation' +// field in radians (e.g. 'M_PI / 2' for a 90° anti-clockwise rotation). +// Note that specifying a region-of-interest using the 'x_center', +// 'y_center', 'width' and 'height' fields is NOT supported and will +// result in an invalid argument error being returned. +// Outputs: +// HandLandmarksDetectionResult +// - The hand landmarks detection results. +class HandLandmarker : tasks::vision::core::BaseVisionTaskApi { + public: + using BaseVisionTaskApi::BaseVisionTaskApi; + + // Creates a HandLandmarker from a HandLandmarkerOptions to process image data + // or streaming data. Hand landmarker can be created with one of the following + // three running modes: + // 1) Image mode for detecting hand landmarks on single image inputs. Users + // provide mediapipe::Image to the `Detect` method, and will receive the + // deteced hand landmarks results as the return value. + // 2) Video mode for detecting hand landmarks on the decoded frames of a + // video. Users call `DetectForVideo` method, and will receive the detected + // hand landmarks results as the return value. + // 3) Live stream mode for detecting hand landmarks on the live stream of the + // input data, such as from camera. Users call `DetectAsync` to push the + // image data into the HandLandmarker, the detected results along with the + // input timestamp and the image that hand landmarker runs on will be + // available in the result callback when the hand landmarker finishes the + // work. + static absl::StatusOr> Create( + std::unique_ptr options); + + // Performs hand landmarks detection on the given image. + // Only use this method when the HandLandmarker is created with the image + // running mode. + // + // The optional 'image_processing_options' parameter can be used to specify + // the rotation to apply to the image before performing detection, by setting + // its 'rotation_degrees' field. Note that specifying a region-of-interest + // using the 'region_of_interest' field is NOT supported and will result in an + // invalid argument error being returned. + // + // The image can be of any size with format RGB or RGBA. + // TODO: Describes how the input image will be preprocessed + // after the yuv support is implemented. + absl::StatusOr Detect( + Image image, + std::optional image_processing_options = + std::nullopt); + + // Performs hand landmarks detection on the provided video frame. + // Only use this method when the HandLandmarker is created with the video + // running mode. + // + // The optional 'image_processing_options' parameter can be used to specify + // the rotation to apply to the image before performing detection, by setting + // its 'rotation_degrees' field. Note that specifying a region-of-interest + // using the 'region_of_interest' field is NOT supported and will result in an + // invalid argument error being returned. + // + // The image can be of any size with format RGB or RGBA. It's required to + // provide the video frame's timestamp (in milliseconds). The input timestamps + // must be monotonically increasing. + absl::StatusOr + DetectForVideo(Image image, int64 timestamp_ms, + std::optional + image_processing_options = std::nullopt); + + // Sends live image data to perform hand landmarks detection, and the results + // will be available via the "result_callback" provided in the + // HandLandmarkerOptions. Only use this method when the HandLandmarker + // is created with the live stream running mode. + // + // The image can be of any size with format RGB or RGBA. It's required to + // provide a timestamp (in milliseconds) to indicate when the input image is + // sent to the hand landmarker. The input timestamps must be monotonically + // increasing. + // + // The optional 'image_processing_options' parameter can be used to specify + // the rotation to apply to the image before performing detection, by setting + // its 'rotation_degrees' field. Note that specifying a region-of-interest + // using the 'region_of_interest' field is NOT supported and will result in an + // invalid argument error being returned. + // + // The "result_callback" provides + // - A vector of HandLandmarksDetectionResult, each is the detected results + // for a input frame. + // - The const reference to the corresponding input image that the hand + // landmarker runs on. Note that the const reference to the image will no + // longer be valid when the callback returns. To access the image data + // outside of the callback, callers need to make a copy of the image. + // - The input timestamp in milliseconds. + absl::Status DetectAsync(Image image, int64 timestamp_ms, + std::optional + image_processing_options = std::nullopt); + + // Shuts down the HandLandmarker when all works are done. + absl::Status Close() { return runner_->Close(); } +}; + +} // namespace hand_landmarker +} // namespace vision +} // namespace tasks +} // namespace mediapipe + +#endif // MEDIAPIPE_TASKS_CC_VISION_HAND_LANDMARKER_HAND_LANDMARKER_H_ diff --git a/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker_test.cc b/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker_test.cc new file mode 100644 index 000000000..ee8c3c10d --- /dev/null +++ b/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker_test.cc @@ -0,0 +1,511 @@ +/* Copyright 2022 The MediaPipe Authors. All Rights Reserved. + +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/tasks/cc/vision/hand_landmarker/hand_landmarker.h" + +#include +#include +#include +#include + +#include "absl/flags/flag.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "mediapipe/framework/deps/file_path.h" +#include "mediapipe/framework/formats/classification.pb.h" +#include "mediapipe/framework/formats/image.h" +#include "mediapipe/framework/formats/landmark.pb.h" +#include "mediapipe/framework/port/file_helpers.h" +#include "mediapipe/framework/port/gmock.h" +#include "mediapipe/framework/port/gtest.h" +#include "mediapipe/tasks/cc/common.h" +#include "mediapipe/tasks/cc/components/containers/hand_landmarks_detection_result.h" +#include "mediapipe/tasks/cc/components/containers/proto/landmarks_detection_result.pb.h" +#include "mediapipe/tasks/cc/components/containers/rect.h" +#include "mediapipe/tasks/cc/components/processors/proto/classifier_options.pb.h" +#include "mediapipe/tasks/cc/core/base_options.h" +#include "mediapipe/tasks/cc/vision/core/image_processing_options.h" +#include "mediapipe/tasks/cc/vision/utils/image_utils.h" +#include "tensorflow/lite/core/shims/cc/shims_test_util.h" + +namespace mediapipe { +namespace tasks { +namespace vision { +namespace hand_landmarker { + +namespace { + +using ::file::Defaults; +using ::mediapipe::file::JoinPath; +using ::mediapipe::tasks::components::containers::HandLandmarksDetectionResult; +using ::mediapipe::tasks::components::containers::Rect; +using ::mediapipe::tasks::containers::proto::LandmarksDetectionResult; +using ::mediapipe::tasks::vision::core::ImageProcessingOptions; +using ::testing::EqualsProto; +using ::testing::HasSubstr; +using ::testing::Optional; +using ::testing::Pointwise; +using ::testing::TestParamInfo; +using ::testing::TestWithParam; +using ::testing::Values; +using ::testing::proto::Approximately; +using ::testing::proto::Partially; + +constexpr char kTestDataDirectory[] = "/mediapipe/tasks/testdata/vision/"; +constexpr char kHandLandmarkerBundleAsset[] = "hand_landmarker.task"; +constexpr char kThumbUpLandmarksFilename[] = "thumb_up_landmarks.pbtxt"; +constexpr char kPointingUpLandmarksFilename[] = "pointing_up_landmarks.pbtxt"; +constexpr char kPointingUpRotatedLandmarksFilename[] = + "pointing_up_rotated_landmarks.pbtxt"; +constexpr char kThumbUpImage[] = "thumb_up.jpg"; +constexpr char kPointingUpImage[] = "pointing_up.jpg"; +constexpr char kPointingUpRotatedImage[] = "pointing_up_rotated.jpg"; +constexpr char kNoHandsImage[] = "cats_and_dogs.jpg"; + +constexpr float kLandmarksFractionDiff = 0.03; // percentage +constexpr float kLandmarksAbsMargin = 0.03; +constexpr float kHandednessMargin = 0.05; + +LandmarksDetectionResult GetLandmarksDetectionResult( + absl::string_view landmarks_file_name) { + LandmarksDetectionResult result; + MP_EXPECT_OK(GetTextProto( + file::JoinPath("./", kTestDataDirectory, landmarks_file_name), &result, + Defaults())); + // Remove z position of landmarks, because they are not used in correctness + // testing. For video or live stream mode, the z positions varies a lot during + // tracking from frame to frame. + for (int i = 0; i < result.landmarks().landmark().size(); i++) { + auto& landmark = *result.mutable_landmarks()->mutable_landmark(i); + landmark.clear_z(); + } + return result; +} + +HandLandmarksDetectionResult GetExpectedHandLandmarksDetectionResult( + const std::vector& landmarks_file_names) { + HandLandmarksDetectionResult expected_results; + for (const auto& file_name : landmarks_file_names) { + const auto landmarks_detection_result = + GetLandmarksDetectionResult(file_name); + expected_results.hand_landmarks.push_back( + landmarks_detection_result.landmarks()); + expected_results.handedness.push_back( + landmarks_detection_result.classifications()); + } + return expected_results; +} + +void ExpectHandLandmarksDetectionResultsCorrect( + const HandLandmarksDetectionResult& actual_results, + const HandLandmarksDetectionResult& expected_results) { + const auto& actual_landmarks = actual_results.hand_landmarks; + const auto& actual_handedness = actual_results.handedness; + + const auto& expected_landmarks = expected_results.hand_landmarks; + const auto& expected_handedness = expected_results.handedness; + + ASSERT_EQ(actual_landmarks.size(), expected_landmarks.size()); + ASSERT_EQ(actual_handedness.size(), expected_handedness.size()); + + EXPECT_THAT( + actual_handedness, + Pointwise(Approximately(Partially(EqualsProto()), kHandednessMargin), + expected_handedness)); + EXPECT_THAT(actual_landmarks, + Pointwise(Approximately(Partially(EqualsProto()), + /*margin=*/kLandmarksAbsMargin, + /*fraction=*/kLandmarksFractionDiff), + expected_landmarks)); +} + +} // namespace + +struct TestParams { + // The name of this test, for convenience when displaying test results. + std::string test_name; + // The filename of test image. + std::string test_image_name; + // The filename of test model. + std::string test_model_file; + // The rotation to apply to the test image before processing, in degrees + // clockwise. + int rotation; + // Expected results from the hand landmarker model output. + HandLandmarksDetectionResult expected_results; +}; + +class ImageModeTest : public testing::TestWithParam {}; + +TEST_F(ImageModeTest, FailsWithCallingWrongMethod) { + MP_ASSERT_OK_AND_ASSIGN( + Image image, + DecodeImageFromFile(JoinPath("./", kTestDataDirectory, kThumbUpImage))); + auto options = std::make_unique(); + options->base_options.model_asset_path = + JoinPath("./", kTestDataDirectory, kHandLandmarkerBundleAsset); + options->running_mode = core::RunningMode::IMAGE; + + MP_ASSERT_OK_AND_ASSIGN(std::unique_ptr hand_landmarker, + HandLandmarker::Create(std::move(options))); + auto results = hand_landmarker->DetectForVideo(image, 0); + EXPECT_EQ(results.status().code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(results.status().message(), + HasSubstr("not initialized with the video mode")); + EXPECT_THAT(results.status().GetPayload(kMediaPipeTasksPayload), + Optional(absl::Cord(absl::StrCat( + MediaPipeTasksStatus::kRunnerApiCalledInWrongModeError)))); + + results = hand_landmarker->DetectAsync(image, 0); + EXPECT_EQ(results.status().code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(results.status().message(), + HasSubstr("not initialized with the live stream mode")); + EXPECT_THAT(results.status().GetPayload(kMediaPipeTasksPayload), + Optional(absl::Cord(absl::StrCat( + MediaPipeTasksStatus::kRunnerApiCalledInWrongModeError)))); + MP_ASSERT_OK(hand_landmarker->Close()); +} + +TEST_F(ImageModeTest, FailsWithRegionOfInterest) { + MP_ASSERT_OK_AND_ASSIGN( + Image image, + DecodeImageFromFile(JoinPath("./", kTestDataDirectory, kThumbUpImage))); + auto options = std::make_unique(); + options->base_options.model_asset_path = + JoinPath("./", kTestDataDirectory, kHandLandmarkerBundleAsset); + options->running_mode = core::RunningMode::IMAGE; + MP_ASSERT_OK_AND_ASSIGN(std::unique_ptr hand_landmarker, + HandLandmarker::Create(std::move(options))); + Rect roi{/*left=*/0.1, /*top=*/0, /*right=*/0.9, /*bottom=*/1}; + ImageProcessingOptions image_processing_options{roi, /*rotation_degrees=*/0}; + + auto results = hand_landmarker->Detect(image, image_processing_options); + EXPECT_EQ(results.status().code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(results.status().message(), + HasSubstr("This task doesn't support region-of-interest")); + EXPECT_THAT( + results.status().GetPayload(kMediaPipeTasksPayload), + Optional(absl::Cord(absl::StrCat( + MediaPipeTasksStatus::kImageProcessingInvalidArgumentError)))); +} + +TEST_P(ImageModeTest, Succeeds) { + MP_ASSERT_OK_AND_ASSIGN( + Image image, DecodeImageFromFile(JoinPath("./", kTestDataDirectory, + GetParam().test_image_name))); + auto options = std::make_unique(); + options->base_options.model_asset_path = + JoinPath("./", kTestDataDirectory, GetParam().test_model_file); + options->running_mode = core::RunningMode::IMAGE; + + MP_ASSERT_OK_AND_ASSIGN(std::unique_ptr hand_landmarker, + HandLandmarker::Create(std::move(options))); + HandLandmarksDetectionResult hand_landmarker_results; + if (GetParam().rotation != 0) { + ImageProcessingOptions image_processing_options; + image_processing_options.rotation_degrees = GetParam().rotation; + MP_ASSERT_OK_AND_ASSIGN( + hand_landmarker_results, + hand_landmarker->Detect(image, image_processing_options)); + } else { + MP_ASSERT_OK_AND_ASSIGN(hand_landmarker_results, + hand_landmarker->Detect(image)); + } + ExpectHandLandmarksDetectionResultsCorrect(hand_landmarker_results, + GetParam().expected_results); + MP_ASSERT_OK(hand_landmarker->Close()); +} + +INSTANTIATE_TEST_SUITE_P( + HandGestureTest, ImageModeTest, + Values(TestParams{ + /* test_name= */ "LandmarksThumbUp", + /* test_image_name= */ kThumbUpImage, + /* test_model_file= */ kHandLandmarkerBundleAsset, + /* rotation= */ 0, + /* expected_results = */ + GetExpectedHandLandmarksDetectionResult( + {kThumbUpLandmarksFilename}), + }, + TestParams{ + /* test_name= */ "LandmarksPointingUp", + /* test_image_name= */ kPointingUpImage, + /* test_model_file= */ kHandLandmarkerBundleAsset, + /* rotation= */ 0, + /* expected_results = */ + GetExpectedHandLandmarksDetectionResult( + {kPointingUpLandmarksFilename}), + }, + TestParams{ + /* test_name= */ "LandmarksPointingUpRotated", + /* test_image_name= */ kPointingUpRotatedImage, + /* test_model_file= */ kHandLandmarkerBundleAsset, + /* rotation= */ -90, + /* expected_results = */ + GetExpectedHandLandmarksDetectionResult( + {kPointingUpRotatedLandmarksFilename}), + }, + TestParams{ + /* test_name= */ "NoHands", + /* test_image_name= */ kNoHandsImage, + /* test_model_file= */ kHandLandmarkerBundleAsset, + /* rotation= */ 0, + /* expected_results = */ + {{}, {}, {}}, + }), + [](const TestParamInfo& info) { + return info.param.test_name; + }); + +class VideoModeTest : public testing::TestWithParam {}; + +TEST_F(VideoModeTest, FailsWithCallingWrongMethod) { + MP_ASSERT_OK_AND_ASSIGN( + Image image, + DecodeImageFromFile(JoinPath("./", kTestDataDirectory, kThumbUpImage))); + auto options = std::make_unique(); + options->base_options.model_asset_path = + JoinPath("./", kTestDataDirectory, kHandLandmarkerBundleAsset); + options->running_mode = core::RunningMode::VIDEO; + + MP_ASSERT_OK_AND_ASSIGN(std::unique_ptr hand_landmarker, + HandLandmarker::Create(std::move(options))); + auto results = hand_landmarker->Detect(image); + EXPECT_EQ(results.status().code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(results.status().message(), + HasSubstr("not initialized with the image mode")); + EXPECT_THAT(results.status().GetPayload(kMediaPipeTasksPayload), + Optional(absl::Cord(absl::StrCat( + MediaPipeTasksStatus::kRunnerApiCalledInWrongModeError)))); + + results = hand_landmarker->DetectAsync(image, 0); + EXPECT_EQ(results.status().code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(results.status().message(), + HasSubstr("not initialized with the live stream mode")); + EXPECT_THAT(results.status().GetPayload(kMediaPipeTasksPayload), + Optional(absl::Cord(absl::StrCat( + MediaPipeTasksStatus::kRunnerApiCalledInWrongModeError)))); + MP_ASSERT_OK(hand_landmarker->Close()); +} + +TEST_P(VideoModeTest, Succeeds) { + const int iterations = 100; + MP_ASSERT_OK_AND_ASSIGN( + Image image, DecodeImageFromFile(JoinPath("./", kTestDataDirectory, + GetParam().test_image_name))); + auto options = std::make_unique(); + options->base_options.model_asset_path = + JoinPath("./", kTestDataDirectory, GetParam().test_model_file); + options->running_mode = core::RunningMode::VIDEO; + + MP_ASSERT_OK_AND_ASSIGN(std::unique_ptr hand_landmarker, + HandLandmarker::Create(std::move(options))); + const auto expected_results = GetParam().expected_results; + for (int i = 0; i < iterations; ++i) { + HandLandmarksDetectionResult hand_landmarker_results; + if (GetParam().rotation != 0) { + ImageProcessingOptions image_processing_options; + image_processing_options.rotation_degrees = GetParam().rotation; + MP_ASSERT_OK_AND_ASSIGN( + hand_landmarker_results, + hand_landmarker->DetectForVideo(image, i, image_processing_options)); + } else { + MP_ASSERT_OK_AND_ASSIGN(hand_landmarker_results, + hand_landmarker->DetectForVideo(image, i)); + } + ExpectHandLandmarksDetectionResultsCorrect(hand_landmarker_results, + expected_results); + } + MP_ASSERT_OK(hand_landmarker->Close()); +} + +INSTANTIATE_TEST_SUITE_P( + HandGestureTest, VideoModeTest, + Values(TestParams{ + /* test_name= */ "LandmarksThumbUp", + /* test_image_name= */ kThumbUpImage, + /* test_model_file= */ kHandLandmarkerBundleAsset, + /* rotation= */ 0, + /* expected_results = */ + GetExpectedHandLandmarksDetectionResult( + {kThumbUpLandmarksFilename}), + }, + TestParams{ + /* test_name= */ "LandmarksPointingUp", + /* test_image_name= */ kPointingUpImage, + /* test_model_file= */ kHandLandmarkerBundleAsset, + /* rotation= */ 0, + /* expected_results = */ + GetExpectedHandLandmarksDetectionResult( + {kPointingUpLandmarksFilename}), + }, + TestParams{ + /* test_name= */ "LandmarksPointingUpRotated", + /* test_image_name= */ kPointingUpRotatedImage, + /* test_model_file= */ kHandLandmarkerBundleAsset, + /* rotation= */ -90, + /* expected_results = */ + GetExpectedHandLandmarksDetectionResult( + {kPointingUpRotatedLandmarksFilename}), + }, + TestParams{ + /* test_name= */ "NoHands", + /* test_image_name= */ kNoHandsImage, + /* test_model_file= */ kHandLandmarkerBundleAsset, + /* rotation= */ 0, + /* expected_results = */ + {{}, {}, {}}, + }), + [](const TestParamInfo& info) { + return info.param.test_name; + }); + +class LiveStreamModeTest : public testing::TestWithParam {}; + +TEST_F(LiveStreamModeTest, FailsWithCallingWrongMethod) { + MP_ASSERT_OK_AND_ASSIGN( + Image image, + DecodeImageFromFile(JoinPath("./", kTestDataDirectory, kThumbUpImage))); + auto options = std::make_unique(); + options->base_options.model_asset_path = + JoinPath("./", kTestDataDirectory, kHandLandmarkerBundleAsset); + options->running_mode = core::RunningMode::LIVE_STREAM; + options->result_callback = + [](absl::StatusOr results, + const Image& image, int64 timestamp_ms) {}; + + MP_ASSERT_OK_AND_ASSIGN(std::unique_ptr hand_landmarker, + HandLandmarker::Create(std::move(options))); + auto results = hand_landmarker->Detect(image); + EXPECT_EQ(results.status().code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(results.status().message(), + HasSubstr("not initialized with the image mode")); + EXPECT_THAT(results.status().GetPayload(kMediaPipeTasksPayload), + Optional(absl::Cord(absl::StrCat( + MediaPipeTasksStatus::kRunnerApiCalledInWrongModeError)))); + + results = hand_landmarker->DetectForVideo(image, 0); + EXPECT_EQ(results.status().code(), absl::StatusCode::kInvalidArgument); + EXPECT_THAT(results.status().message(), + HasSubstr("not initialized with the video mode")); + EXPECT_THAT(results.status().GetPayload(kMediaPipeTasksPayload), + Optional(absl::Cord(absl::StrCat( + MediaPipeTasksStatus::kRunnerApiCalledInWrongModeError)))); + MP_ASSERT_OK(hand_landmarker->Close()); +} + +TEST_P(LiveStreamModeTest, Succeeds) { + const int iterations = 100; + MP_ASSERT_OK_AND_ASSIGN( + Image image, DecodeImageFromFile(JoinPath("./", kTestDataDirectory, + GetParam().test_image_name))); + auto options = std::make_unique(); + options->base_options.model_asset_path = + JoinPath("./", kTestDataDirectory, GetParam().test_model_file); + options->running_mode = core::RunningMode::LIVE_STREAM; + std::vector hand_landmarker_results; + std::vector> image_sizes; + std::vector timestamps; + options->result_callback = + [&hand_landmarker_results, &image_sizes, ×tamps]( + absl::StatusOr results, + const Image& image, int64 timestamp_ms) { + MP_ASSERT_OK(results.status()); + hand_landmarker_results.push_back(std::move(results.value())); + image_sizes.push_back({image.width(), image.height()}); + timestamps.push_back(timestamp_ms); + }; + + MP_ASSERT_OK_AND_ASSIGN(std::unique_ptr hand_landmarker, + HandLandmarker::Create(std::move(options))); + for (int i = 0; i < iterations; ++i) { + HandLandmarksDetectionResult hand_landmarker_results; + if (GetParam().rotation != 0) { + ImageProcessingOptions image_processing_options; + image_processing_options.rotation_degrees = GetParam().rotation; + MP_ASSERT_OK( + hand_landmarker->DetectAsync(image, i, image_processing_options)); + } else { + MP_ASSERT_OK(hand_landmarker->DetectAsync(image, i)); + } + } + MP_ASSERT_OK(hand_landmarker->Close()); + // Due to the flow limiter, the total of outputs will be smaller than the + // number of iterations. + ASSERT_LE(hand_landmarker_results.size(), iterations); + ASSERT_GT(hand_landmarker_results.size(), 0); + + const auto expected_results = GetParam().expected_results; + for (int i = 0; i < hand_landmarker_results.size(); ++i) { + ExpectHandLandmarksDetectionResultsCorrect(hand_landmarker_results[i], + expected_results); + } + for (const auto& image_size : image_sizes) { + EXPECT_EQ(image_size.first, image.width()); + EXPECT_EQ(image_size.second, image.height()); + } + int64 timestamp_ms = -1; + for (const auto& timestamp : timestamps) { + EXPECT_GT(timestamp, timestamp_ms); + timestamp_ms = timestamp; + } +} + +INSTANTIATE_TEST_SUITE_P( + HandGestureTest, LiveStreamModeTest, + Values(TestParams{ + /* test_name= */ "LandmarksThumbUp", + /* test_image_name= */ kThumbUpImage, + /* test_model_file= */ kHandLandmarkerBundleAsset, + /* rotation= */ 0, + /* expected_results = */ + GetExpectedHandLandmarksDetectionResult( + {kThumbUpLandmarksFilename}), + }, + TestParams{ + /* test_name= */ "LandmarksPointingUp", + /* test_image_name= */ kPointingUpImage, + /* test_model_file= */ kHandLandmarkerBundleAsset, + /* rotation= */ 0, + /* expected_results = */ + GetExpectedHandLandmarksDetectionResult( + {kPointingUpLandmarksFilename}), + }, + TestParams{ + /* test_name= */ "LandmarksPointingUpRotated", + /* test_image_name= */ kPointingUpRotatedImage, + /* test_model_file= */ kHandLandmarkerBundleAsset, + /* rotation= */ -90, + /* expected_results = */ + GetExpectedHandLandmarksDetectionResult( + {kPointingUpRotatedLandmarksFilename}), + }, + TestParams{ + /* test_name= */ "NoHands", + /* test_image_name= */ kNoHandsImage, + /* test_model_file= */ kHandLandmarkerBundleAsset, + /* rotation= */ 0, + /* expected_results = */ + {{}, {}, {}}, + }), + [](const TestParamInfo& info) { + return info.param.test_name; + }); + +} // namespace hand_landmarker +} // namespace vision +} // namespace tasks +} // namespace mediapipe diff --git a/mediapipe/tasks/cc/vision/image_segmenter/BUILD b/mediapipe/tasks/cc/vision/image_segmenter/BUILD index 81cd43e34..4c43a07f5 100644 --- a/mediapipe/tasks/cc/vision/image_segmenter/BUILD +++ b/mediapipe/tasks/cc/vision/image_segmenter/BUILD @@ -32,7 +32,7 @@ cc_library( "//mediapipe/tasks/cc/vision/core:image_processing_options", "//mediapipe/tasks/cc/vision/core:running_mode", "//mediapipe/tasks/cc/vision/core:vision_task_api_factory", - "//mediapipe/tasks/cc/vision/image_segmenter/proto:image_segmenter_options_cc_proto", + "//mediapipe/tasks/cc/vision/image_segmenter/proto:image_segmenter_graph_options_cc_proto", "@com_google_absl//absl/memory", "@com_google_absl//absl/status:statusor", "@org_tensorflow//tensorflow/lite/kernels:builtin_ops", @@ -63,7 +63,7 @@ cc_library( "//mediapipe/tasks/cc/core/proto:acceleration_cc_proto", "//mediapipe/tasks/cc/core/proto:inference_subgraph_cc_proto", "//mediapipe/tasks/cc/metadata:metadata_extractor", - "//mediapipe/tasks/cc/vision/image_segmenter/proto:image_segmenter_options_cc_proto", + "//mediapipe/tasks/cc/vision/image_segmenter/proto:image_segmenter_graph_options_cc_proto", "//mediapipe/tasks/metadata:metadata_schema_cc", "//mediapipe/util:label_map_cc_proto", "//mediapipe/util:label_map_util", diff --git a/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter.cc b/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter.cc index 209ee0df3..6dce1b4ea 100644 --- a/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter.cc +++ b/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter.cc @@ -23,10 +23,12 @@ limitations under the License. #include "mediapipe/tasks/cc/vision/core/image_processing_options.h" #include "mediapipe/tasks/cc/vision/core/running_mode.h" #include "mediapipe/tasks/cc/vision/core/vision_task_api_factory.h" +#include "mediapipe/tasks/cc/vision/image_segmenter/proto/image_segmenter_graph_options.pb.h" namespace mediapipe { namespace tasks { namespace vision { +namespace image_segmenter { namespace { constexpr char kSegmentationStreamName[] = "segmented_mask_out"; @@ -37,23 +39,24 @@ constexpr char kImageTag[] = "IMAGE"; constexpr char kNormRectStreamName[] = "norm_rect_in"; constexpr char kNormRectTag[] = "NORM_RECT"; constexpr char kSubgraphTypeName[] = - "mediapipe.tasks.vision.ImageSegmenterGraph"; + "mediapipe.tasks.vision.image_segmenter.ImageSegmenterGraph"; constexpr int kMicroSecondsPerMilliSecond = 1000; using ::mediapipe::CalculatorGraphConfig; using ::mediapipe::Image; using ::mediapipe::tasks::components::proto::SegmenterOptions; -using ImageSegmenterOptionsProto = - image_segmenter::proto::ImageSegmenterOptions; +using ImageSegmenterGraphOptionsProto = ::mediapipe::tasks::vision:: + image_segmenter::proto::ImageSegmenterGraphOptions; // Creates a MediaPipe graph config that only contains a single subgraph node of -// "mediapipe.tasks.vision.ImageSegmenterGraph". +// "mediapipe.tasks.vision.image_segmenter.ImageSegmenterGraph". CalculatorGraphConfig CreateGraphConfig( - std::unique_ptr options, + std::unique_ptr options, bool enable_flow_limiting) { api2::builder::Graph graph; auto& task_subgraph = graph.AddNode(kSubgraphTypeName); - task_subgraph.GetOptions().Swap(options.get()); + task_subgraph.GetOptions().Swap( + options.get()); graph.In(kImageTag).SetName(kImageInStreamName); graph.In(kNormRectTag).SetName(kNormRectStreamName); task_subgraph.Out(kGroupedSegmentationTag).SetName(kSegmentationStreamName) >> @@ -72,9 +75,9 @@ CalculatorGraphConfig CreateGraphConfig( // Converts the user-facing ImageSegmenterOptions struct to the internal // ImageSegmenterOptions proto. -std::unique_ptr ConvertImageSegmenterOptionsToProto( - ImageSegmenterOptions* options) { - auto options_proto = std::make_unique(); +std::unique_ptr +ConvertImageSegmenterOptionsToProto(ImageSegmenterOptions* options) { + auto options_proto = std::make_unique(); auto base_options_proto = std::make_unique( tasks::core::ConvertBaseOptionsToProto(&(options->base_options))); options_proto->mutable_base_options()->Swap(base_options_proto.get()); @@ -137,7 +140,7 @@ absl::StatusOr> ImageSegmenter::Create( }; } return core::VisionTaskApiFactory::Create( + ImageSegmenterGraphOptionsProto>( CreateGraphConfig( std::move(options_proto), options->running_mode == core::RunningMode::LIVE_STREAM), @@ -211,6 +214,7 @@ absl::Status ImageSegmenter::SegmentAsync( .At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}}); } +} // namespace image_segmenter } // namespace vision } // namespace tasks } // namespace mediapipe diff --git a/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter.h b/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter.h index 54269ec0e..43bf5b7e6 100644 --- a/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter.h +++ b/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter.h @@ -26,12 +26,12 @@ limitations under the License. #include "mediapipe/tasks/cc/core/base_options.h" #include "mediapipe/tasks/cc/vision/core/base_vision_task_api.h" #include "mediapipe/tasks/cc/vision/core/image_processing_options.h" -#include "mediapipe/tasks/cc/vision/image_segmenter/proto/image_segmenter_options.pb.h" #include "tensorflow/lite/kernels/register.h" namespace mediapipe { namespace tasks { namespace vision { +namespace image_segmenter { // The options for configuring a mediapipe image segmenter task. struct ImageSegmenterOptions { @@ -191,6 +191,7 @@ class ImageSegmenter : tasks::vision::core::BaseVisionTaskApi { absl::Status Close() { return runner_->Close(); } }; +} // namespace image_segmenter } // namespace vision } // namespace tasks } // namespace mediapipe diff --git a/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter_graph.cc b/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter_graph.cc index 31fed6d8d..44742e043 100644 --- a/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter_graph.cc +++ b/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter_graph.cc @@ -35,7 +35,7 @@ limitations under the License. #include "mediapipe/tasks/cc/core/proto/acceleration.pb.h" #include "mediapipe/tasks/cc/core/proto/inference_subgraph.pb.h" #include "mediapipe/tasks/cc/metadata/metadata_extractor.h" -#include "mediapipe/tasks/cc/vision/image_segmenter/proto/image_segmenter_options.pb.h" +#include "mediapipe/tasks/cc/vision/image_segmenter/proto/image_segmenter_graph_options.pb.h" #include "mediapipe/tasks/metadata/metadata_schema_generated.h" #include "mediapipe/util/label_map.pb.h" #include "mediapipe/util/label_map_util.h" @@ -44,6 +44,7 @@ limitations under the License. namespace mediapipe { namespace tasks { namespace vision { +namespace image_segmenter { namespace { @@ -55,7 +56,8 @@ using ::mediapipe::api2::builder::MultiSource; using ::mediapipe::api2::builder::Source; using ::mediapipe::tasks::components::proto::SegmenterOptions; using ::mediapipe::tasks::metadata::ModelMetadataExtractor; -using ::mediapipe::tasks::vision::image_segmenter::proto::ImageSegmenterOptions; +using ::mediapipe::tasks::vision::image_segmenter::proto:: + ImageSegmenterGraphOptions; using ::tflite::Tensor; using ::tflite::TensorMetadata; using LabelItems = mediapipe::proto_ns::Map; @@ -77,7 +79,7 @@ struct ImageSegmenterOutputs { } // namespace -absl::Status SanityCheckOptions(const ImageSegmenterOptions& options) { +absl::Status SanityCheckOptions(const ImageSegmenterGraphOptions& options) { if (options.segmenter_options().output_type() == SegmenterOptions::UNSPECIFIED) { return CreateStatusWithPayload(absl::StatusCode::kInvalidArgument, @@ -112,7 +114,7 @@ absl::StatusOr GetLabelItemsIfAny( } absl::Status ConfigureTensorsToSegmentationCalculator( - const ImageSegmenterOptions& segmenter_option, + const ImageSegmenterGraphOptions& segmenter_option, const core::ModelResources& model_resources, TensorsToSegmentationCalculatorOptions* options) { *options->mutable_segmenter_options() = segmenter_option.segmenter_options(); @@ -181,7 +183,7 @@ absl::StatusOr GetOutputTensor( // input_stream: "IMAGE:image" // output_stream: "SEGMENTATION:segmented_masks" // options { -// [mediapipe.tasks.vision.image_segmenter.proto.ImageSegmenterOptions.ext] +// [mediapipe.tasks.vision.image_segmenter.proto.ImageSegmenterGraphOptions.ext] // { // base_options { // model_asset { @@ -200,12 +202,12 @@ class ImageSegmenterGraph : public core::ModelTaskGraph { absl::StatusOr GetConfig( mediapipe::SubgraphContext* sc) override { ASSIGN_OR_RETURN(const auto* model_resources, - CreateModelResources(sc)); + CreateModelResources(sc)); Graph graph; ASSIGN_OR_RETURN( auto output_streams, BuildSegmentationTask( - sc->Options(), *model_resources, + sc->Options(), *model_resources, graph[Input(kImageTag)], graph[Input::Optional(kNormRectTag)], graph)); @@ -228,13 +230,13 @@ class ImageSegmenterGraph : public core::ModelTaskGraph { // builder::Graph instance. The segmentation pipeline takes images // (mediapipe::Image) as the input and returns segmented image mask as output. // - // task_options: the mediapipe tasks ImageSegmenterOptions proto. + // task_options: the mediapipe tasks ImageSegmenterGraphOptions proto. // model_resources: the ModelSources object initialized from a segmentation // model file with model metadata. // image_in: (mediapipe::Image) stream to run segmentation on. // graph: the mediapipe builder::Graph instance to be updated. absl::StatusOr BuildSegmentationTask( - const ImageSegmenterOptions& task_options, + const ImageSegmenterGraphOptions& task_options, const core::ModelResources& model_resources, Source image_in, Source norm_rect_in, Graph& graph) { MP_RETURN_IF_ERROR(SanityCheckOptions(task_options)); @@ -293,8 +295,10 @@ class ImageSegmenterGraph : public core::ModelTaskGraph { } }; -REGISTER_MEDIAPIPE_GRAPH(::mediapipe::tasks::vision::ImageSegmenterGraph); +REGISTER_MEDIAPIPE_GRAPH( + ::mediapipe::tasks::vision::image_segmenter::ImageSegmenterGraph); +} // namespace image_segmenter } // namespace vision } // namespace tasks } // namespace mediapipe diff --git a/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter_test.cc b/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter_test.cc index 07235563b..752a116dd 100644 --- a/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter_test.cc +++ b/mediapipe/tasks/cc/vision/image_segmenter/image_segmenter_test.cc @@ -33,7 +33,7 @@ limitations under the License. #include "mediapipe/tasks/cc/core/proto/base_options.pb.h" #include "mediapipe/tasks/cc/core/proto/external_file.pb.h" #include "mediapipe/tasks/cc/vision/core/image_processing_options.h" -#include "mediapipe/tasks/cc/vision/image_segmenter/proto/image_segmenter_options.pb.h" +#include "mediapipe/tasks/cc/vision/image_segmenter/proto/image_segmenter_graph_options.pb.h" #include "mediapipe/tasks/cc/vision/utils/image_utils.h" #include "tensorflow/lite/core/shims/cc/shims_test_util.h" #include "tensorflow/lite/kernels/builtin_op_kernels.h" @@ -42,6 +42,7 @@ limitations under the License. namespace mediapipe { namespace tasks { namespace vision { +namespace image_segmenter { namespace { using ::mediapipe::Image; @@ -547,6 +548,7 @@ TEST_F(LiveStreamModeTest, Succeeds) { // TODO: Add test for hair segmentation model. } // namespace +} // namespace image_segmenter } // namespace vision } // namespace tasks } // namespace mediapipe diff --git a/mediapipe/tasks/cc/vision/image_segmenter/proto/BUILD b/mediapipe/tasks/cc/vision/image_segmenter/proto/BUILD index d768c2bb1..3b14060f1 100644 --- a/mediapipe/tasks/cc/vision/image_segmenter/proto/BUILD +++ b/mediapipe/tasks/cc/vision/image_segmenter/proto/BUILD @@ -19,8 +19,8 @@ package(default_visibility = ["//mediapipe/tasks:internal"]) licenses(["notice"]) mediapipe_proto_library( - name = "image_segmenter_options_proto", - srcs = ["image_segmenter_options.proto"], + name = "image_segmenter_graph_options_proto", + srcs = ["image_segmenter_graph_options.proto"], deps = [ "//mediapipe/framework:calculator_options_proto", "//mediapipe/framework:calculator_proto", diff --git a/mediapipe/tasks/cc/vision/image_segmenter/proto/image_segmenter_options.proto b/mediapipe/tasks/cc/vision/image_segmenter/proto/image_segmenter_graph_options.proto similarity index 85% rename from mediapipe/tasks/cc/vision/image_segmenter/proto/image_segmenter_options.proto rename to mediapipe/tasks/cc/vision/image_segmenter/proto/image_segmenter_graph_options.proto index 6e24a6665..166e2e8e0 100644 --- a/mediapipe/tasks/cc/vision/image_segmenter/proto/image_segmenter_options.proto +++ b/mediapipe/tasks/cc/vision/image_segmenter/proto/image_segmenter_graph_options.proto @@ -21,9 +21,12 @@ import "mediapipe/framework/calculator.proto"; import "mediapipe/tasks/cc/components/proto/segmenter_options.proto"; import "mediapipe/tasks/cc/core/proto/base_options.proto"; -message ImageSegmenterOptions { +option java_package = "com.google.mediapipe.tasks.vision.imagesegmenter.proto"; +option java_outer_classname = "ImageSegmenterGraphOptionsProto"; + +message ImageSegmenterGraphOptions { extend mediapipe.CalculatorOptions { - optional ImageSegmenterOptions ext = 458105758; + optional ImageSegmenterGraphOptions ext = 458105758; } // Base options for configuring MediaPipe Tasks, such as specifying the TfLite // model file with metadata, accelerator options, etc. diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/BUILD b/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/BUILD index 9dfa53031..63697229f 100644 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/BUILD +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/BUILD @@ -20,6 +20,7 @@ android_library( name = "category", srcs = ["Category.java"], deps = [ + "//mediapipe/framework/formats:classification_java_proto_lite", "//third_party:autovalue", "@maven//:com_google_guava_guava", ], @@ -36,20 +37,29 @@ android_library( ) android_library( - name = "classification_entry", - srcs = ["ClassificationEntry.java"], + name = "classifications", + srcs = ["Classifications.java"], + javacopts = [ + "-Xep:AndroidJdkLibsChecker:OFF", + ], deps = [ ":category", + "//mediapipe/framework/formats:classification_java_proto_lite", + "//mediapipe/tasks/cc/components/containers/proto:classifications_java_proto_lite", "//third_party:autovalue", "@maven//:com_google_guava_guava", ], ) android_library( - name = "classifications", - srcs = ["Classifications.java"], + name = "classificationresult", + srcs = ["ClassificationResult.java"], + javacopts = [ + "-Xep:AndroidJdkLibsChecker:OFF", + ], deps = [ - ":classification_entry", + ":classifications", + "//mediapipe/tasks/cc/components/containers/proto:classifications_java_proto_lite", "//third_party:autovalue", "@maven//:com_google_guava_guava", ], diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/Category.java b/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/Category.java index 3b7c41fbe..e955605e4 100644 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/Category.java +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/Category.java @@ -15,6 +15,7 @@ package com.google.mediapipe.tasks.components.containers; import com.google.auto.value.AutoValue; +import com.google.mediapipe.formats.proto.ClassificationProto; import java.util.Objects; /** @@ -38,6 +39,16 @@ public abstract class Category { return new AutoValue_Category(score, index, categoryName, displayName); } + /** + * Creates a {@link Category} object from a {@link ClassificationProto.Classification} protobuf + * message. + * + * @param proto the {@link ClassificationProto.Classification} protobuf message to convert. + */ + public static Category createFromProto(ClassificationProto.Classification proto) { + return create(proto.getScore(), proto.getIndex(), proto.getLabel(), proto.getDisplayName()); + } + /** The probability score of this label category. */ public abstract float score(); diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/ClassificationEntry.java b/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/ClassificationEntry.java deleted file mode 100644 index 8fc1daa03..000000000 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/ClassificationEntry.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2022 The MediaPipe Authors. All Rights Reserved. -// -// 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. - -package com.google.mediapipe.tasks.components.containers; - -import com.google.auto.value.AutoValue; -import java.util.Collections; -import java.util.List; - -/** - * Represents a list of predicted categories with an optional timestamp. Typically used as result - * for classification tasks. - */ -@AutoValue -public abstract class ClassificationEntry { - /** - * Creates a {@link ClassificationEntry} instance from a list of {@link Category} and optional - * timestamp. - * - * @param categories the list of {@link Category} objects that contain category name, display - * name, score and label index. - * @param timestampMs the {@link long} representing the timestamp for which these categories were - * obtained. - */ - public static ClassificationEntry create(List categories, long timestampMs) { - return new AutoValue_ClassificationEntry(Collections.unmodifiableList(categories), timestampMs); - } - - /** The list of predicted {@link Category} objects, sorted by descending score. */ - public abstract List categories(); - - /** - * The timestamp (in milliseconds) associated to the classification entry. This is useful for time - * series use cases, e.g. audio classification. - */ - public abstract long timestampMs(); -} diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/ClassificationResult.java b/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/ClassificationResult.java new file mode 100644 index 000000000..d30099d8b --- /dev/null +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/ClassificationResult.java @@ -0,0 +1,76 @@ +// Copyright 2022 The MediaPipe Authors. All Rights Reserved. +// +// 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. + +package com.google.mediapipe.tasks.components.containers; + +import com.google.auto.value.AutoValue; +import com.google.mediapipe.tasks.components.containers.proto.ClassificationsProto; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * Represents the classification results of a model. Typically used as a result for classification + * tasks. + */ +@AutoValue +public abstract class ClassificationResult { + + /** + * Creates a {@link ClassificationResult} instance. + * + * @param classifications the list of {@link Classifications} objects containing the predicted + * categories for each head of the model. + * @param timestampMs the optional timestamp (in milliseconds) of the start of the chunk of data + * corresponding to these results. + */ + public static ClassificationResult create( + List classifications, Optional timestampMs) { + return new AutoValue_ClassificationResult( + Collections.unmodifiableList(classifications), timestampMs); + } + + /** + * Creates a {@link ClassificationResult} object from a {@link + * ClassificationsProto.ClassificationResult} protobuf message. + * + * @param proto the {@link ClassificationsProto.ClassificationResult} protobuf message to convert. + */ + public static ClassificationResult createFromProto( + ClassificationsProto.ClassificationResult proto) { + List classifications = new ArrayList<>(); + for (ClassificationsProto.Classifications classificationsProto : + proto.getClassificationsList()) { + classifications.add(Classifications.createFromProto(classificationsProto)); + } + Optional timestampMs = + proto.hasTimestampMs() ? Optional.of(proto.getTimestampMs()) : Optional.empty(); + return create(classifications, timestampMs); + } + + /** The classification results for each head of the model. */ + public abstract List classifications(); + + /** + * The optional timestamp (in milliseconds) of the start of the chunk of data corresponding to + * these results. + * + *

This is only used for classification on time series (e.g. audio classification). In these + * use cases, the amount of data to process might exceed the maximum size that the model can + * process: to solve this, the input data is split into multiple chunks starting at different + * timestamps. + */ + public abstract Optional timestampMs(); +} diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/Classifications.java b/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/Classifications.java index 726578729..12f14e628 100644 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/Classifications.java +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers/Classifications.java @@ -15,8 +15,12 @@ package com.google.mediapipe.tasks.components.containers; import com.google.auto.value.AutoValue; +import com.google.mediapipe.formats.proto.ClassificationProto; +import com.google.mediapipe.tasks.components.containers.proto.ClassificationsProto; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; /** * Represents the list of classification for a given classifier head. Typically used as a result for @@ -28,25 +32,41 @@ public abstract class Classifications { /** * Creates a {@link Classifications} instance. * - * @param entries the list of {@link ClassificationEntry} objects containing the predicted - * categories. + * @param categories the list of {@link Category} objects containing the predicted categories. * @param headIndex the index of the classifier head. - * @param headName the name of the classifier head. + * @param headName the optional name of the classifier head. */ public static Classifications create( - List entries, int headIndex, String headName) { + List categories, int headIndex, Optional headName) { return new AutoValue_Classifications( - Collections.unmodifiableList(entries), headIndex, headName); + Collections.unmodifiableList(categories), headIndex, headName); } - /** A list of {@link ClassificationEntry} objects. */ - public abstract List entries(); + /** + * Creates a {@link Classifications} object from a {@link ClassificationsProto.Classifications} + * protobuf message. + * + * @param proto the {@link ClassificationsProto.Classifications} protobuf message to convert. + */ + public static Classifications createFromProto(ClassificationsProto.Classifications proto) { + List categories = new ArrayList<>(); + for (ClassificationProto.Classification classificationProto : + proto.getClassificationList().getClassificationList()) { + categories.add(Category.createFromProto(classificationProto)); + } + Optional headName = + proto.hasHeadName() ? Optional.of(proto.getHeadName()) : Optional.empty(); + return create(categories, proto.getHeadIndex(), headName); + } + + /** A list of {@link Category} objects. */ + public abstract List categories(); /** * The index of the classifier head these entries refer to. This is useful for multi-head models. */ public abstract int headIndex(); - /** The name of the classifier head, which is the corresponding tensor metadata name. */ - public abstract String headName(); + /** The optional name of the classifier head, which is the corresponding tensor metadata name. */ + public abstract Optional headName(); } diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/core/BaseOptions.java b/mediapipe/tasks/java/com/google/mediapipe/tasks/core/BaseOptions.java index 7f2903503..d1db08893 100644 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/core/BaseOptions.java +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/core/BaseOptions.java @@ -26,22 +26,23 @@ public abstract class BaseOptions { @AutoValue.Builder public abstract static class Builder { /** - * Sets the model path to a tflite model with metadata in the assets. + * Sets the model path to a model asset file (a tflite model or a model asset bundle file) in + * the Android app assets folder. * *

Note: when model path is set, both model file descriptor and model buffer should be empty. */ public abstract Builder setModelAssetPath(String value); /** - * Sets the native fd int of a tflite model with metadata. + * Sets the native fd int of a model asset file (a tflite model or a model asset bundle file). * *

Note: when model file descriptor is set, both model path and model buffer should be empty. */ public abstract Builder setModelAssetFileDescriptor(Integer value); /** - * Sets either the direct {@link ByteBuffer} or the {@link MappedByteBuffer} of a tflite model - * with metadata. + * Sets either the direct {@link ByteBuffer} or the {@link MappedByteBuffer} of a model asset + * file (a tflite model or a model asset bundle file). * *

Note: when model buffer is set, both model file and model file descriptor should be empty. */ diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/mediapipe_tasks_aar.bzl b/mediapipe/tasks/java/com/google/mediapipe/tasks/mediapipe_tasks_aar.bzl index ff40768f7..62c424f66 100644 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/mediapipe_tasks_aar.bzl +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/mediapipe_tasks_aar.bzl @@ -22,6 +22,7 @@ _CORE_TASKS_JAVA_PROTO_LITE_TARGETS = [ "//mediapipe/tasks/cc/components/containers/proto:classifications_java_proto_lite", "//mediapipe/tasks/cc/components/containers/proto:embeddings_java_proto_lite", "//mediapipe/tasks/cc/components/containers/proto:landmarks_detection_result_java_proto_lite", + "//mediapipe/tasks/cc/components/proto:segmenter_options_java_proto_lite", "//mediapipe/tasks/cc/components/processors/proto:classifier_options_java_proto_lite", "//mediapipe/tasks/cc/components/processors/proto:embedder_options_java_proto_lite", "//mediapipe/tasks/cc/core/proto:acceleration_java_proto_lite", @@ -36,6 +37,7 @@ _VISION_TASKS_JAVA_PROTO_LITE_TARGETS = [ "//mediapipe/tasks/cc/vision/gesture_recognizer/proto:gesture_embedder_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/gesture_recognizer/proto:gesture_recognizer_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/gesture_recognizer/proto:hand_gesture_recognizer_graph_options_java_proto_lite", + "//mediapipe/tasks/cc/vision/image_segmenter/proto:image_segmenter_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/hand_detector/proto:hand_detector_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/hand_landmarker/proto:hand_landmarker_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/hand_landmarker/proto:hand_landmarks_detector_graph_options_java_proto_lite", @@ -232,7 +234,7 @@ def _mediapipe_tasks_aar(name, srcs, manifest, java_proto_lite_targets, native_l "//mediapipe/framework/formats:rect_java_proto_lite", "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:detection", "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:category", - "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:classification_entry", + "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:classificationresult", "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:classifications", "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:landmark", "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/processors:classifieroptions", diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/text/BUILD b/mediapipe/tasks/java/com/google/mediapipe/tasks/text/BUILD index fa2a547c2..b49169529 100644 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/text/BUILD +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/text/BUILD @@ -37,8 +37,8 @@ cc_library( android_library( name = "textclassifier", srcs = [ - "textclassifier/TextClassificationResult.java", "textclassifier/TextClassifier.java", + "textclassifier/TextClassifierResult.java", ], javacopts = [ "-Xep:AndroidJdkLibsChecker:OFF", @@ -51,9 +51,7 @@ android_library( "//mediapipe/tasks/cc/components/containers/proto:classifications_java_proto_lite", "//mediapipe/tasks/cc/core/proto:base_options_java_proto_lite", "//mediapipe/tasks/cc/text/text_classifier/proto:text_classifier_graph_options_java_proto_lite", - "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:category", - "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:classification_entry", - "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:classifications", + "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:classificationresult", "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/processors:classifieroptions", "//mediapipe/tasks/java/com/google/mediapipe/tasks/core", "//mediapipe/tasks/java/com/google/mediapipe/tasks/text:libmediapipe_tasks_text_jni_lib", diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/text/textclassifier/TextClassificationResult.java b/mediapipe/tasks/java/com/google/mediapipe/tasks/text/textclassifier/TextClassificationResult.java deleted file mode 100644 index c1e2446cd..000000000 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/text/textclassifier/TextClassificationResult.java +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2022 The MediaPipe Authors. All Rights Reserved. -// -// 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. - -package com.google.mediapipe.tasks.text.textclassifier; - -import com.google.auto.value.AutoValue; -import com.google.mediapipe.tasks.components.containers.Category; -import com.google.mediapipe.tasks.components.containers.ClassificationEntry; -import com.google.mediapipe.tasks.components.containers.Classifications; -import com.google.mediapipe.tasks.components.containers.proto.CategoryProto; -import com.google.mediapipe.tasks.components.containers.proto.ClassificationsProto; -import com.google.mediapipe.tasks.core.TaskResult; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** Represents the classification results generated by {@link TextClassifier}. */ -@AutoValue -public abstract class TextClassificationResult implements TaskResult { - - /** - * Creates an {@link TextClassificationResult} instance from a {@link - * ClassificationsProto.ClassificationResult} protobuf message. - * - * @param classificationResult a {@link ClassificationsProto.ClassificationResult} protobuf - * message. - * @param timestampMs a timestamp for this result. - */ - // TODO: consolidate output formats across platforms. - static TextClassificationResult create( - ClassificationsProto.ClassificationResult classificationResult, long timestampMs) { - List classifications = new ArrayList<>(); - for (ClassificationsProto.Classifications classificationsProto : - classificationResult.getClassificationsList()) { - classifications.add(classificationsFromProto(classificationsProto)); - } - return new AutoValue_TextClassificationResult( - timestampMs, Collections.unmodifiableList(classifications)); - } - - @Override - public abstract long timestampMs(); - - /** Contains one set of results per classifier head. */ - @SuppressWarnings("AutoValueImmutableFields") - public abstract List classifications(); - - /** - * Converts a {@link CategoryProto.Category} protobuf message to a {@link Category} object. - * - * @param category the {@link CategoryProto.Category} protobuf message to convert. - */ - static Category categoryFromProto(CategoryProto.Category category) { - return Category.create( - category.getScore(), - category.getIndex(), - category.getCategoryName(), - category.getDisplayName()); - } - - /** - * Converts a {@link ClassificationsProto.ClassificationEntry} protobuf message to a {@link - * ClassificationEntry} object. - * - * @param entry the {@link ClassificationsProto.ClassificationEntry} protobuf message to convert. - */ - static ClassificationEntry classificationEntryFromProto( - ClassificationsProto.ClassificationEntry entry) { - List categories = new ArrayList<>(); - for (CategoryProto.Category category : entry.getCategoriesList()) { - categories.add(categoryFromProto(category)); - } - return ClassificationEntry.create(categories, entry.getTimestampMs()); - } - - /** - * Converts a {@link ClassificationsProto.Classifications} protobuf message to a {@link - * Classifications} object. - * - * @param classifications the {@link ClassificationsProto.Classifications} protobuf message to - * convert. - */ - static Classifications classificationsFromProto( - ClassificationsProto.Classifications classifications) { - List entries = new ArrayList<>(); - for (ClassificationsProto.ClassificationEntry entry : classifications.getEntriesList()) { - entries.add(classificationEntryFromProto(entry)); - } - return Classifications.create( - entries, classifications.getHeadIndex(), classifications.getHeadName()); - } -} diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/text/textclassifier/TextClassifier.java b/mediapipe/tasks/java/com/google/mediapipe/tasks/text/textclassifier/TextClassifier.java index 07a4fa48f..341d6bf91 100644 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/text/textclassifier/TextClassifier.java +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/text/textclassifier/TextClassifier.java @@ -22,6 +22,7 @@ import com.google.mediapipe.framework.MediaPipeException; import com.google.mediapipe.framework.Packet; import com.google.mediapipe.framework.PacketGetter; import com.google.mediapipe.framework.ProtoUtil; +import com.google.mediapipe.tasks.components.containers.ClassificationResult; import com.google.mediapipe.tasks.components.containers.proto.ClassificationsProto; import com.google.mediapipe.tasks.components.processors.ClassifierOptions; import com.google.mediapipe.tasks.core.BaseOptions; @@ -86,10 +87,9 @@ public final class TextClassifier implements AutoCloseable { @SuppressWarnings("ConstantCaseForConstants") private static final List OUTPUT_STREAMS = - Collections.unmodifiableList( - Arrays.asList("CLASSIFICATION_RESULT:classification_result_out")); + Collections.unmodifiableList(Arrays.asList("CLASSIFICATIONS:classifications_out")); - private static final int CLASSIFICATION_RESULT_OUT_STREAM_INDEX = 0; + private static final int CLASSIFICATIONS_OUT_STREAM_INDEX = 0; private static final String TASK_GRAPH_NAME = "mediapipe.tasks.text.text_classifier.TextClassifierGraph"; private final TaskRunner runner; @@ -142,17 +142,18 @@ public final class TextClassifier implements AutoCloseable { * @throws MediaPipeException if there is an error during {@link TextClassifier} creation. */ public static TextClassifier createFromOptions(Context context, TextClassifierOptions options) { - OutputHandler handler = new OutputHandler<>(); + OutputHandler handler = new OutputHandler<>(); handler.setOutputPacketConverter( - new OutputHandler.OutputPacketConverter() { + new OutputHandler.OutputPacketConverter() { @Override - public TextClassificationResult convertToTaskResult(List packets) { + public TextClassifierResult convertToTaskResult(List packets) { try { - return TextClassificationResult.create( - PacketGetter.getProto( - packets.get(CLASSIFICATION_RESULT_OUT_STREAM_INDEX), - ClassificationsProto.ClassificationResult.getDefaultInstance()), - packets.get(CLASSIFICATION_RESULT_OUT_STREAM_INDEX).getTimestamp()); + return TextClassifierResult.create( + ClassificationResult.createFromProto( + PacketGetter.getProto( + packets.get(CLASSIFICATIONS_OUT_STREAM_INDEX), + ClassificationsProto.ClassificationResult.getDefaultInstance())), + packets.get(CLASSIFICATIONS_OUT_STREAM_INDEX).getTimestamp()); } catch (IOException e) { throw new MediaPipeException( MediaPipeException.StatusCode.INTERNAL.ordinal(), e.getMessage()); @@ -192,10 +193,10 @@ public final class TextClassifier implements AutoCloseable { * * @param inputText a {@link String} for processing. */ - public TextClassificationResult classify(String inputText) { + public TextClassifierResult classify(String inputText) { Map inputPackets = new HashMap<>(); inputPackets.put(TEXT_IN_STREAM_NAME, runner.getPacketCreator().createString(inputText)); - return (TextClassificationResult) runner.process(inputPackets); + return (TextClassifierResult) runner.process(inputPackets); } /** Closes and cleans up the {@link TextClassifier}. */ diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/text/textclassifier/TextClassifierResult.java b/mediapipe/tasks/java/com/google/mediapipe/tasks/text/textclassifier/TextClassifierResult.java new file mode 100644 index 000000000..64de0ee8d --- /dev/null +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/text/textclassifier/TextClassifierResult.java @@ -0,0 +1,55 @@ +// Copyright 2022 The MediaPipe Authors. All Rights Reserved. +// +// 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. + +package com.google.mediapipe.tasks.text.textclassifier; + +import com.google.auto.value.AutoValue; +import com.google.mediapipe.tasks.components.containers.ClassificationResult; +import com.google.mediapipe.tasks.components.containers.proto.ClassificationsProto; +import com.google.mediapipe.tasks.core.TaskResult; + +/** Represents the classification results generated by {@link TextClassifier}. */ +@AutoValue +public abstract class TextClassifierResult implements TaskResult { + + /** + * Creates an {@link TextClassifierResult} instance. + * + * @param classificationResult the {@link ClassificationResult} object containing one set of + * results per classifier head. + * @param timestampMs a timestamp for this result. + */ + static TextClassifierResult create(ClassificationResult classificationResult, long timestampMs) { + return new AutoValue_TextClassifierResult(classificationResult, timestampMs); + } + + /** + * Creates an {@link TextClassifierResult} instance from a {@link + * ClassificationsProto.ClassificationResult} protobuf message. + * + * @param proto the {@link ClassificationsProto.ClassificationResult} protobuf message to convert. + * @param timestampMs a timestamp for this result. + */ + // TODO: consolidate output formats across platforms. + static TextClassifierResult createFromProto( + ClassificationsProto.ClassificationResult proto, long timestampMs) { + return create(ClassificationResult.createFromProto(proto), timestampMs); + } + + /** Contains one set of results per classifier head. */ + public abstract ClassificationResult classificationResult(); + + @Override + public abstract long timestampMs(); +} diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/BUILD b/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/BUILD index d15040ae7..146097bbd 100644 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/BUILD +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/BUILD @@ -84,8 +84,8 @@ android_library( android_library( name = "imageclassifier", srcs = [ - "imageclassifier/ImageClassificationResult.java", "imageclassifier/ImageClassifier.java", + "imageclassifier/ImageClassifierResult.java", ], javacopts = [ "-Xep:AndroidJdkLibsChecker:OFF", @@ -100,9 +100,7 @@ android_library( "//mediapipe/tasks/cc/components/containers/proto:classifications_java_proto_lite", "//mediapipe/tasks/cc/core/proto:base_options_java_proto_lite", "//mediapipe/tasks/cc/vision/image_classifier/proto:image_classifier_graph_options_java_proto_lite", - "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:category", - "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:classification_entry", - "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:classifications", + "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/containers:classificationresult", "//mediapipe/tasks/java/com/google/mediapipe/tasks/components/processors:classifieroptions", "//mediapipe/tasks/java/com/google/mediapipe/tasks/core", "//third_party:autovalue", diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassificationResult.java b/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassificationResult.java deleted file mode 100644 index d82a47b86..000000000 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassificationResult.java +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2022 The MediaPipe Authors. All Rights Reserved. -// -// 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. - -package com.google.mediapipe.tasks.vision.imageclassifier; - -import com.google.auto.value.AutoValue; -import com.google.mediapipe.tasks.components.containers.Category; -import com.google.mediapipe.tasks.components.containers.ClassificationEntry; -import com.google.mediapipe.tasks.components.containers.Classifications; -import com.google.mediapipe.tasks.components.containers.proto.CategoryProto; -import com.google.mediapipe.tasks.components.containers.proto.ClassificationsProto; -import com.google.mediapipe.tasks.core.TaskResult; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** Represents the classification results generated by {@link ImageClassifier}. */ -@AutoValue -public abstract class ImageClassificationResult implements TaskResult { - - /** - * Creates an {@link ImageClassificationResult} instance from a {@link - * ClassificationsProto.ClassificationResult} protobuf message. - * - * @param classificationResult a {@link ClassificationsProto.ClassificationResult} protobuf - * message. - * @param timestampMs a timestamp for this result. - */ - // TODO: consolidate output formats across platforms. - static ImageClassificationResult create( - ClassificationsProto.ClassificationResult classificationResult, long timestampMs) { - List classifications = new ArrayList<>(); - for (ClassificationsProto.Classifications classificationsProto : - classificationResult.getClassificationsList()) { - classifications.add(classificationsFromProto(classificationsProto)); - } - return new AutoValue_ImageClassificationResult( - timestampMs, Collections.unmodifiableList(classifications)); - } - - @Override - public abstract long timestampMs(); - - /** Contains one set of results per classifier head. */ - public abstract List classifications(); - - /** - * Converts a {@link CategoryProto.Category} protobuf message to a {@link Category} object. - * - * @param category the {@link CategoryProto.Category} protobuf message to convert. - */ - static Category categoryFromProto(CategoryProto.Category category) { - return Category.create( - category.getScore(), - category.getIndex(), - category.getCategoryName(), - category.getDisplayName()); - } - - /** - * Converts a {@link ClassificationsProto.ClassificationEntry} protobuf message to a {@link - * ClassificationEntry} object. - * - * @param entry the {@link ClassificationsProto.ClassificationEntry} protobuf message to convert. - */ - static ClassificationEntry classificationEntryFromProto( - ClassificationsProto.ClassificationEntry entry) { - List categories = new ArrayList<>(); - for (CategoryProto.Category category : entry.getCategoriesList()) { - categories.add(categoryFromProto(category)); - } - return ClassificationEntry.create(categories, entry.getTimestampMs()); - } - - /** - * Converts a {@link ClassificationsProto.Classifications} protobuf message to a {@link - * Classifications} object. - * - * @param classifications the {@link ClassificationsProto.Classifications} protobuf message to - * convert. - */ - static Classifications classificationsFromProto( - ClassificationsProto.Classifications classifications) { - List entries = new ArrayList<>(); - for (ClassificationsProto.ClassificationEntry entry : classifications.getEntriesList()) { - entries.add(classificationEntryFromProto(entry)); - } - return Classifications.create( - entries, classifications.getHeadIndex(), classifications.getHeadName()); - } -} diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassifier.java b/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassifier.java index 3863b6fe0..f01546ffc 100644 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassifier.java +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassifier.java @@ -25,6 +25,7 @@ import com.google.mediapipe.framework.PacketGetter; import com.google.mediapipe.framework.ProtoUtil; import com.google.mediapipe.framework.image.BitmapImageBuilder; import com.google.mediapipe.framework.image.MPImage; +import com.google.mediapipe.tasks.components.containers.ClassificationResult; import com.google.mediapipe.tasks.components.containers.proto.ClassificationsProto; import com.google.mediapipe.tasks.components.processors.ClassifierOptions; import com.google.mediapipe.tasks.core.BaseOptions; @@ -96,8 +97,8 @@ public final class ImageClassifier extends BaseVisionTaskApi { Arrays.asList("IMAGE:" + IMAGE_IN_STREAM_NAME, "NORM_RECT:" + NORM_RECT_IN_STREAM_NAME)); private static final List OUTPUT_STREAMS = Collections.unmodifiableList( - Arrays.asList("CLASSIFICATION_RESULT:classification_result_out", "IMAGE:image_out")); - private static final int CLASSIFICATION_RESULT_OUT_STREAM_INDEX = 0; + Arrays.asList("CLASSIFICATIONS:classifications_out", "IMAGE:image_out")); + private static final int CLASSIFICATIONS_OUT_STREAM_INDEX = 0; private static final int IMAGE_OUT_STREAM_INDEX = 1; private static final String TASK_GRAPH_NAME = "mediapipe.tasks.vision.image_classifier.ImageClassifierGraph"; @@ -164,17 +165,18 @@ public final class ImageClassifier extends BaseVisionTaskApi { * @throws MediaPipeException if there is an error during {@link ImageClassifier} creation. */ public static ImageClassifier createFromOptions(Context context, ImageClassifierOptions options) { - OutputHandler handler = new OutputHandler<>(); + OutputHandler handler = new OutputHandler<>(); handler.setOutputPacketConverter( - new OutputHandler.OutputPacketConverter() { + new OutputHandler.OutputPacketConverter() { @Override - public ImageClassificationResult convertToTaskResult(List packets) { + public ImageClassifierResult convertToTaskResult(List packets) { try { - return ImageClassificationResult.create( - PacketGetter.getProto( - packets.get(CLASSIFICATION_RESULT_OUT_STREAM_INDEX), - ClassificationsProto.ClassificationResult.getDefaultInstance()), - packets.get(CLASSIFICATION_RESULT_OUT_STREAM_INDEX).getTimestamp()); + return ImageClassifierResult.create( + ClassificationResult.createFromProto( + PacketGetter.getProto( + packets.get(CLASSIFICATIONS_OUT_STREAM_INDEX), + ClassificationsProto.ClassificationResult.getDefaultInstance())), + packets.get(CLASSIFICATIONS_OUT_STREAM_INDEX).getTimestamp()); } catch (IOException e) { throw new MediaPipeException( MediaPipeException.StatusCode.INTERNAL.ordinal(), e.getMessage()); @@ -229,7 +231,7 @@ public final class ImageClassifier extends BaseVisionTaskApi { * @param image a MediaPipe {@link MPImage} object for processing. * @throws MediaPipeException if there is an internal error. */ - public ImageClassificationResult classify(MPImage image) { + public ImageClassifierResult classify(MPImage image) { return classify(image, ImageProcessingOptions.builder().build()); } @@ -248,9 +250,9 @@ public final class ImageClassifier extends BaseVisionTaskApi { * input image before running inference. * @throws MediaPipeException if there is an internal error. */ - public ImageClassificationResult classify( + public ImageClassifierResult classify( MPImage image, ImageProcessingOptions imageProcessingOptions) { - return (ImageClassificationResult) processImageData(image, imageProcessingOptions); + return (ImageClassifierResult) processImageData(image, imageProcessingOptions); } /** @@ -271,7 +273,7 @@ public final class ImageClassifier extends BaseVisionTaskApi { * @param timestampMs the input timestamp (in milliseconds). * @throws MediaPipeException if there is an internal error. */ - public ImageClassificationResult classifyForVideo(MPImage image, long timestampMs) { + public ImageClassifierResult classifyForVideo(MPImage image, long timestampMs) { return classifyForVideo(image, ImageProcessingOptions.builder().build(), timestampMs); } @@ -294,9 +296,9 @@ public final class ImageClassifier extends BaseVisionTaskApi { * @param timestampMs the input timestamp (in milliseconds). * @throws MediaPipeException if there is an internal error. */ - public ImageClassificationResult classifyForVideo( + public ImageClassifierResult classifyForVideo( MPImage image, ImageProcessingOptions imageProcessingOptions, long timestampMs) { - return (ImageClassificationResult) processVideoData(image, imageProcessingOptions, timestampMs); + return (ImageClassifierResult) processVideoData(image, imageProcessingOptions, timestampMs); } /** @@ -383,7 +385,7 @@ public final class ImageClassifier extends BaseVisionTaskApi { * the image classifier is in the live stream mode. */ public abstract Builder setResultListener( - ResultListener resultListener); + ResultListener resultListener); /** Sets an optional {@link ErrorListener}. */ public abstract Builder setErrorListener(ErrorListener errorListener); @@ -420,7 +422,7 @@ public final class ImageClassifier extends BaseVisionTaskApi { abstract Optional classifierOptions(); - abstract Optional> resultListener(); + abstract Optional> resultListener(); abstract Optional errorListener(); diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassifierResult.java b/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassifierResult.java new file mode 100644 index 000000000..924542158 --- /dev/null +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassifierResult.java @@ -0,0 +1,55 @@ +// Copyright 2022 The MediaPipe Authors. All Rights Reserved. +// +// 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. + +package com.google.mediapipe.tasks.vision.imageclassifier; + +import com.google.auto.value.AutoValue; +import com.google.mediapipe.tasks.components.containers.ClassificationResult; +import com.google.mediapipe.tasks.components.containers.proto.ClassificationsProto; +import com.google.mediapipe.tasks.core.TaskResult; + +/** Represents the classification results generated by {@link ImageClassifier}. */ +@AutoValue +public abstract class ImageClassifierResult implements TaskResult { + + /** + * Creates an {@link ImageClassifierResult} instance. + * + * @param classificationResult the {@link ClassificationResult} object containing one set of + * results per classifier head. + * @param timestampMs a timestamp for this result. + */ + static ImageClassifierResult create(ClassificationResult classificationResult, long timestampMs) { + return new AutoValue_ImageClassifierResult(classificationResult, timestampMs); + } + + /** + * Creates an {@link ImageClassifierResult} instance from a {@link + * ClassificationsProto.ClassificationResult} protobuf message. + * + * @param proto the {@link ClassificationsProto.ClassificationResult} protobuf message to convert. + * @param timestampMs a timestamp for this result. + */ + // TODO: consolidate output formats across platforms. + static ImageClassifierResult createFromProto( + ClassificationsProto.ClassificationResult proto, long timestampMs) { + return create(ClassificationResult.createFromProto(proto), timestampMs); + } + + /** Contains one set of results per classifier head. */ + public abstract ClassificationResult classificationResult(); + + @Override + public abstract long timestampMs(); +} diff --git a/mediapipe/tasks/javatests/com/google/mediapipe/tasks/text/textclassifier/TextClassifierTest.java b/mediapipe/tasks/javatests/com/google/mediapipe/tasks/text/textclassifier/TextClassifierTest.java index bfca79ced..d3f0e90f3 100644 --- a/mediapipe/tasks/javatests/com/google/mediapipe/tasks/text/textclassifier/TextClassifierTest.java +++ b/mediapipe/tasks/javatests/com/google/mediapipe/tasks/text/textclassifier/TextClassifierTest.java @@ -76,7 +76,7 @@ public class TextClassifierTest { public void classify_succeedsWithBert() throws Exception { TextClassifier textClassifier = TextClassifier.createFromFile(ApplicationProvider.getApplicationContext(), BERT_MODEL_FILE); - TextClassificationResult negativeResults = textClassifier.classify(NEGATIVE_TEXT); + TextClassifierResult negativeResults = textClassifier.classify(NEGATIVE_TEXT); assertHasOneHead(negativeResults); assertCategoriesAre( negativeResults, @@ -84,7 +84,7 @@ public class TextClassifierTest { Category.create(0.95630914f, 0, "negative", ""), Category.create(0.04369091f, 1, "positive", ""))); - TextClassificationResult positiveResults = textClassifier.classify(POSITIVE_TEXT); + TextClassifierResult positiveResults = textClassifier.classify(POSITIVE_TEXT); assertHasOneHead(positiveResults); assertCategoriesAre( positiveResults, @@ -99,7 +99,7 @@ public class TextClassifierTest { TextClassifier.createFromFile( ApplicationProvider.getApplicationContext(), TestUtils.loadFile(ApplicationProvider.getApplicationContext(), BERT_MODEL_FILE)); - TextClassificationResult negativeResults = textClassifier.classify(NEGATIVE_TEXT); + TextClassifierResult negativeResults = textClassifier.classify(NEGATIVE_TEXT); assertHasOneHead(negativeResults); assertCategoriesAre( negativeResults, @@ -107,7 +107,7 @@ public class TextClassifierTest { Category.create(0.95630914f, 0, "negative", ""), Category.create(0.04369091f, 1, "positive", ""))); - TextClassificationResult positiveResults = textClassifier.classify(POSITIVE_TEXT); + TextClassifierResult positiveResults = textClassifier.classify(POSITIVE_TEXT); assertHasOneHead(positiveResults); assertHasOneHead(positiveResults); assertCategoriesAre( @@ -122,7 +122,7 @@ public class TextClassifierTest { TextClassifier textClassifier = TextClassifier.createFromFile( ApplicationProvider.getApplicationContext(), REGEX_MODEL_FILE); - TextClassificationResult negativeResults = textClassifier.classify(NEGATIVE_TEXT); + TextClassifierResult negativeResults = textClassifier.classify(NEGATIVE_TEXT); assertHasOneHead(negativeResults); assertCategoriesAre( negativeResults, @@ -130,7 +130,7 @@ public class TextClassifierTest { Category.create(0.6647746f, 0, "Negative", ""), Category.create(0.33522537f, 1, "Positive", ""))); - TextClassificationResult positiveResults = textClassifier.classify(POSITIVE_TEXT); + TextClassifierResult positiveResults = textClassifier.classify(POSITIVE_TEXT); assertHasOneHead(positiveResults); assertCategoriesAre( positiveResults, @@ -139,16 +139,15 @@ public class TextClassifierTest { Category.create(0.48799595f, 1, "Positive", ""))); } - private static void assertHasOneHead(TextClassificationResult results) { - assertThat(results.classifications()).hasSize(1); - assertThat(results.classifications().get(0).headIndex()).isEqualTo(0); - assertThat(results.classifications().get(0).headName()).isEqualTo("probability"); - assertThat(results.classifications().get(0).entries()).hasSize(1); + private static void assertHasOneHead(TextClassifierResult results) { + assertThat(results.classificationResult().classifications()).hasSize(1); + assertThat(results.classificationResult().classifications().get(0).headIndex()).isEqualTo(0); + assertThat(results.classificationResult().classifications().get(0).headName().get()) + .isEqualTo("probability"); } - private static void assertCategoriesAre( - TextClassificationResult results, List categories) { - assertThat(results.classifications().get(0).entries().get(0).categories()) + private static void assertCategoriesAre(TextClassifierResult results, List categories) { + assertThat(results.classificationResult().classifications().get(0).categories()) .isEqualTo(categories); } } diff --git a/mediapipe/tasks/javatests/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassifierTest.java b/mediapipe/tasks/javatests/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassifierTest.java index 99ebd9777..69820ce2d 100644 --- a/mediapipe/tasks/javatests/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassifierTest.java +++ b/mediapipe/tasks/javatests/com/google/mediapipe/tasks/vision/imageclassifier/ImageClassifierTest.java @@ -91,11 +91,12 @@ public class ImageClassifierTest { ImageClassifier imageClassifier = ImageClassifier.createFromFile( ApplicationProvider.getApplicationContext(), FLOAT_MODEL_FILE); - ImageClassificationResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); + ImageClassifierResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); - assertHasOneHeadAndOneTimestamp(results, 0); - assertThat(results.classifications().get(0).entries().get(0).categories()).hasSize(1001); - assertThat(results.classifications().get(0).entries().get(0).categories().get(0)) + assertHasOneHead(results); + assertThat(results.classificationResult().classifications().get(0).categories()) + .hasSize(1001); + assertThat(results.classificationResult().classifications().get(0).categories().get(0)) .isEqualTo(Category.create(0.7952058f, 934, "cheeseburger", "")); } @@ -108,9 +109,9 @@ public class ImageClassifierTest { .build(); ImageClassifier imageClassifier = ImageClassifier.createFromOptions(ApplicationProvider.getApplicationContext(), options); - ImageClassificationResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); + ImageClassifierResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); - assertHasOneHeadAndOneTimestamp(results, 0); + assertHasOneHead(results); assertCategoriesAre( results, Arrays.asList( @@ -128,9 +129,9 @@ public class ImageClassifierTest { .build(); ImageClassifier imageClassifier = ImageClassifier.createFromOptions(ApplicationProvider.getApplicationContext(), options); - ImageClassificationResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); + ImageClassifierResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); - assertHasOneHeadAndOneTimestamp(results, 0); + assertHasOneHead(results); assertCategoriesAre( results, Arrays.asList(Category.create(0.97265625f, 934, "cheeseburger", ""))); } @@ -144,9 +145,9 @@ public class ImageClassifierTest { .build(); ImageClassifier imageClassifier = ImageClassifier.createFromOptions(ApplicationProvider.getApplicationContext(), options); - ImageClassificationResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); + ImageClassifierResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); - assertHasOneHeadAndOneTimestamp(results, 0); + assertHasOneHead(results); assertCategoriesAre( results, Arrays.asList( @@ -166,9 +167,9 @@ public class ImageClassifierTest { .build(); ImageClassifier imageClassifier = ImageClassifier.createFromOptions(ApplicationProvider.getApplicationContext(), options); - ImageClassificationResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); + ImageClassifierResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); - assertHasOneHeadAndOneTimestamp(results, 0); + assertHasOneHead(results); assertCategoriesAre( results, Arrays.asList( @@ -190,9 +191,9 @@ public class ImageClassifierTest { .build(); ImageClassifier imageClassifier = ImageClassifier.createFromOptions(ApplicationProvider.getApplicationContext(), options); - ImageClassificationResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); + ImageClassifierResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); - assertHasOneHeadAndOneTimestamp(results, 0); + assertHasOneHead(results); assertCategoriesAre( results, Arrays.asList( @@ -214,10 +215,10 @@ public class ImageClassifierTest { RectF roi = new RectF(0.450f, 0.308f, 0.614f, 0.734f); ImageProcessingOptions imageProcessingOptions = ImageProcessingOptions.builder().setRegionOfInterest(roi).build(); - ImageClassificationResult results = + ImageClassifierResult results = imageClassifier.classify(getImageFromAsset(MULTI_OBJECTS_IMAGE), imageProcessingOptions); - assertHasOneHeadAndOneTimestamp(results, 0); + assertHasOneHead(results); assertCategoriesAre( results, Arrays.asList(Category.create(0.9969325f, 806, "soccer ball", ""))); } @@ -233,10 +234,10 @@ public class ImageClassifierTest { ImageClassifier.createFromOptions(ApplicationProvider.getApplicationContext(), options); ImageProcessingOptions imageProcessingOptions = ImageProcessingOptions.builder().setRotationDegrees(-90).build(); - ImageClassificationResult results = + ImageClassifierResult results = imageClassifier.classify(getImageFromAsset(BURGER_ROTATED_IMAGE), imageProcessingOptions); - assertHasOneHeadAndOneTimestamp(results, 0); + assertHasOneHead(results); assertCategoriesAre( results, Arrays.asList( @@ -258,11 +259,11 @@ public class ImageClassifierTest { RectF roi = new RectF(0.0f, 0.1763f, 0.5642f, 0.3049f); ImageProcessingOptions imageProcessingOptions = ImageProcessingOptions.builder().setRegionOfInterest(roi).setRotationDegrees(-90).build(); - ImageClassificationResult results = + ImageClassifierResult results = imageClassifier.classify( getImageFromAsset(MULTI_OBJECTS_ROTATED_IMAGE), imageProcessingOptions); - assertHasOneHeadAndOneTimestamp(results, 0); + assertHasOneHead(results); assertCategoriesAre( results, Arrays.asList(Category.create(0.686824f, 560, "folding chair", ""))); } @@ -391,9 +392,9 @@ public class ImageClassifierTest { .build(); ImageClassifier imageClassifier = ImageClassifier.createFromOptions(ApplicationProvider.getApplicationContext(), options); - ImageClassificationResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); + ImageClassifierResult results = imageClassifier.classify(getImageFromAsset(BURGER_IMAGE)); - assertHasOneHeadAndOneTimestamp(results, 0); + assertHasOneHead(results); assertCategoriesAre( results, Arrays.asList(Category.create(0.7952058f, 934, "cheeseburger", ""))); } @@ -410,9 +411,8 @@ public class ImageClassifierTest { ImageClassifier imageClassifier = ImageClassifier.createFromOptions(ApplicationProvider.getApplicationContext(), options); for (int i = 0; i < 3; i++) { - ImageClassificationResult results = - imageClassifier.classifyForVideo(image, /*timestampMs=*/ i); - assertHasOneHeadAndOneTimestamp(results, i); + ImageClassifierResult results = imageClassifier.classifyForVideo(image, /*timestampMs=*/ i); + assertHasOneHead(results); assertCategoriesAre( results, Arrays.asList(Category.create(0.7952058f, 934, "cheeseburger", ""))); } @@ -478,24 +478,17 @@ public class ImageClassifierTest { return new BitmapImageBuilder(BitmapFactory.decodeStream(istr)).build(); } - private static void assertHasOneHeadAndOneTimestamp( - ImageClassificationResult results, long timestampMs) { - assertThat(results.classifications()).hasSize(1); - assertThat(results.classifications().get(0).headIndex()).isEqualTo(0); - assertThat(results.classifications().get(0).headName()).isEqualTo("probability"); - assertThat(results.classifications().get(0).entries()).hasSize(1); - assertThat(results.classifications().get(0).entries().get(0).timestampMs()) - .isEqualTo(timestampMs); + private static void assertHasOneHead(ImageClassifierResult results) { + assertThat(results.classificationResult().classifications()).hasSize(1); + assertThat(results.classificationResult().classifications().get(0).headIndex()).isEqualTo(0); + assertThat(results.classificationResult().classifications().get(0).headName().get()) + .isEqualTo("probability"); } private static void assertCategoriesAre( - ImageClassificationResult results, List categories) { - assertThat(results.classifications().get(0).entries().get(0).categories()) - .hasSize(categories.size()); - for (int i = 0; i < categories.size(); i++) { - assertThat(results.classifications().get(0).entries().get(0).categories().get(i)) - .isEqualTo(categories.get(i)); - } + ImageClassifierResult results, List categories) { + assertThat(results.classificationResult().classifications().get(0).categories()) + .isEqualTo(categories); } private static void assertImageSizeIsExpected(MPImage inputImage) { diff --git a/mediapipe/tasks/python/metadata/metadata_writers/BUILD b/mediapipe/tasks/python/metadata/metadata_writers/BUILD index d2b55d47d..4f8a675f4 100644 --- a/mediapipe/tasks/python/metadata/metadata_writers/BUILD +++ b/mediapipe/tasks/python/metadata/metadata_writers/BUILD @@ -43,3 +43,9 @@ py_library( srcs = ["image_classifier.py"], deps = [":metadata_writer"], ) + +py_library( + name = "text_classifier", + srcs = ["text_classifier.py"], + deps = [":metadata_writer"], +) diff --git a/mediapipe/tasks/python/metadata/metadata_writers/image_classifier.py b/mediapipe/tasks/python/metadata/metadata_writers/image_classifier.py index c516a342d..7f2685792 100644 --- a/mediapipe/tasks/python/metadata/metadata_writers/image_classifier.py +++ b/mediapipe/tasks/python/metadata/metadata_writers/image_classifier.py @@ -62,10 +62,10 @@ class MetadataWriter(metadata_writer.MetadataWriterBase): https://github.com/google/mediapipe/blob/f8af41b1eb49ff4bdad756ff19d1d36f486be614/mediapipe/tasks/metadata/metadata_schema.fbs#L456 Returns: - An MetadataWrite object. + A MetadataWriter object. """ writer = metadata_writer.MetadataWriter(model_buffer) - writer.add_genernal_info(_MODEL_NAME, _MODEL_DESCRIPTION) + writer.add_general_info(_MODEL_NAME, _MODEL_DESCRIPTION) writer.add_image_input(input_norm_mean, input_norm_std) writer.add_classification_output(labels, score_calibration) return cls(writer) diff --git a/mediapipe/tasks/python/metadata/metadata_writers/metadata_info.py b/mediapipe/tasks/python/metadata/metadata_writers/metadata_info.py index 76b572e86..9300370b7 100644 --- a/mediapipe/tasks/python/metadata/metadata_writers/metadata_info.py +++ b/mediapipe/tasks/python/metadata/metadata_writers/metadata_info.py @@ -228,6 +228,45 @@ class ScoreThresholdingMd: return score_thresholding +class RegexTokenizerMd: + """A container for the Regex tokenizer [1] metadata information. + + [1]: + https://github.com/google/mediapipe/blob/f8af41b1eb49ff4bdad756ff19d1d36f486be614/mediapipe/tasks/metadata/metadata_schema.fbs#L500 + """ + + def __init__(self, delim_regex_pattern: str, vocab_file_path: str): + """Initializes a RegexTokenizerMd object. + + Args: + delim_regex_pattern: the regular expression to segment strings and create + tokens. + vocab_file_path: path to the vocabulary file. + """ + self._delim_regex_pattern = delim_regex_pattern + self._vocab_file_path = vocab_file_path + + def create_metadata(self) -> _metadata_fb.ProcessUnitT: + """Creates the Regex tokenizer metadata based on the information. + + Returns: + A Flatbuffers Python object of the Regex tokenizer metadata. + """ + vocab = _metadata_fb.AssociatedFileT() + vocab.name = self._vocab_file_path + vocab.description = _VOCAB_FILE_DESCRIPTION + vocab.type = _metadata_fb.AssociatedFileType.VOCABULARY + + # Create the RegexTokenizer. + tokenizer = _metadata_fb.ProcessUnitT() + tokenizer.optionsType = ( + _metadata_fb.ProcessUnitOptions.RegexTokenizerOptions) + tokenizer.options = _metadata_fb.RegexTokenizerOptionsT() + tokenizer.options.delimRegexPattern = self._delim_regex_pattern + tokenizer.options.vocabFile = [vocab] + return tokenizer + + class TensorMd: """A container for common tensor metadata information. @@ -397,6 +436,56 @@ class InputImageTensorMd(TensorMd): return tensor_metadata +class InputTextTensorMd(TensorMd): + """A container for the input text tensor metadata information. + + Attributes: + tokenizer_md: information of the tokenizer in the input text tensor, if any. + """ + + def __init__(self, + name: Optional[str] = None, + description: Optional[str] = None, + tokenizer_md: Optional[RegexTokenizerMd] = None): + """Initializes the instance of InputTextTensorMd. + + Args: + name: name of the tensor. + description: description of what the tensor is. + tokenizer_md: information of the tokenizer in the input text tensor, if + any. Only `RegexTokenizer` [1] is currenly supported. If the tokenizer + is `BertTokenizer` [2] or `SentencePieceTokenizer` [3], refer to + `BertInputTensorsMd` class. + [1]: + https://github.com/google/mediapipe/blob/f8af41b1eb49ff4bdad756ff19d1d36f486be614/mediapipe/tasks/metadata/metadata_schema.fbs#L500 + [2]: + https://github.com/google/mediapipe/blob/f8af41b1eb49ff4bdad756ff19d1d36f486be614/mediapipe/tasks/metadata/metadata_schema.fbs#L477 + [3]: + https://github.com/google/mediapipe/blob/f8af41b1eb49ff4bdad756ff19d1d36f486be614/mediapipe/tasks/metadata/metadata_schema.fbs#L485 + """ + super().__init__(name, description) + self.tokenizer_md = tokenizer_md + + def create_metadata(self) -> _metadata_fb.TensorMetadataT: + """Creates the input text metadata based on the information. + + Returns: + A Flatbuffers Python object of the input text metadata. + + Raises: + ValueError: if the type of tokenizer_md is unsupported. + """ + if not isinstance(self.tokenizer_md, (type(None), RegexTokenizerMd)): + raise ValueError( + f"The type of tokenizer_options, {type(self.tokenizer_md)}, is " + f"unsupported") + + tensor_metadata = super().create_metadata() + if self.tokenizer_md: + tensor_metadata.processUnits = [self.tokenizer_md.create_metadata()] + return tensor_metadata + + class ClassificationTensorMd(TensorMd): """A container for the classification tensor metadata information. diff --git a/mediapipe/tasks/python/metadata/metadata_writers/metadata_writer.py b/mediapipe/tasks/python/metadata/metadata_writers/metadata_writer.py index 3a9f91239..49563a73f 100644 --- a/mediapipe/tasks/python/metadata/metadata_writers/metadata_writer.py +++ b/mediapipe/tasks/python/metadata/metadata_writers/metadata_writer.py @@ -29,6 +29,9 @@ from mediapipe.tasks.python.metadata.metadata_writers import writer_utils _INPUT_IMAGE_NAME = 'image' _INPUT_IMAGE_DESCRIPTION = 'Input image to be processed.' +_INPUT_REGEX_TEXT_NAME = 'input_text' +_INPUT_REGEX_TEXT_DESCRIPTION = ('Embedding vectors representing the input ' + 'text to be processed.') _OUTPUT_CLASSIFICATION_NAME = 'score' _OUTPUT_CLASSIFICATION_DESCRIPTION = 'Score of the labels respectively.' @@ -82,6 +85,22 @@ class ScoreThresholding: global_score_threshold: float +@dataclasses.dataclass +class RegexTokenizer: + """Parameters of the Regex tokenizer [1] metadata information. + + [1]: + https://github.com/google/mediapipe/blob/f8af41b1eb49ff4bdad756ff19d1d36f486be614/mediapipe/tasks/metadata/metadata_schema.fbs#L500 + + Attributes: + delim_regex_pattern: the regular expression to segment strings and create + tokens. + vocab_file_path: path to the vocabulary file. + """ + delim_regex_pattern: str + vocab_file_path: str + + class Labels(object): """Simple container holding classification labels of a particular tensor. @@ -355,11 +374,11 @@ class MetadataWriter(object): if os.path.exists(self._temp_folder.name): self._temp_folder.cleanup() - def add_genernal_info( + def add_general_info( self, model_name: str, model_description: Optional[str] = None) -> 'MetadataWriter': - """Adds a genernal info metadata for the general metadata informantion.""" + """Adds a general info metadata for the general metadata informantion.""" # Will overwrite the previous `self._general_md` if exists. self._general_md = metadata_info.GeneralMd( name=model_name, description=model_description) @@ -415,6 +434,34 @@ class MetadataWriter(object): self._input_mds.append(input_md) return self + def add_regex_text_input( + self, + regex_tokenizer: RegexTokenizer, + name: str = _INPUT_REGEX_TEXT_NAME, + description: str = _INPUT_REGEX_TEXT_DESCRIPTION) -> 'MetadataWriter': + """Adds an input text metadata for the text input with regex tokenizer. + + Args: + regex_tokenizer: information of the regex tokenizer [1] used to process + the input string. + name: Name of the input tensor. + description: Description of the input tensor. + + Returns: + The MetaWriter instance, can be used for chained operation. + + [1]: + https://github.com/google/mediapipe/blob/f8af41b1eb49ff4bdad756ff19d1d36f486be614/mediapipe/tasks/metadata/metadata_schema.fbs#L500 + """ + tokenizer_md = metadata_info.RegexTokenizerMd( + delim_regex_pattern=regex_tokenizer.delim_regex_pattern, + vocab_file_path=regex_tokenizer.vocab_file_path) + input_md = metadata_info.InputTextTensorMd( + name=name, description=description, tokenizer_md=tokenizer_md) + self._input_mds.append(input_md) + self._associated_files.append(regex_tokenizer.vocab_file_path) + return self + def add_classification_output( self, labels: Optional[Labels] = None, diff --git a/mediapipe/tasks/python/metadata/metadata_writers/text_classifier.py b/mediapipe/tasks/python/metadata/metadata_writers/text_classifier.py new file mode 100644 index 000000000..ef3df58b1 --- /dev/null +++ b/mediapipe/tasks/python/metadata/metadata_writers/text_classifier.py @@ -0,0 +1,64 @@ +# Copyright 2022 The MediaPipe Authors. All Rights Reserved. +# +# 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. +# ============================================================================== +"""Writes metadata and label file to the Text classifier models.""" + +from mediapipe.tasks.python.metadata.metadata_writers import metadata_writer + +_MODEL_NAME = "TextClassifier" +_MODEL_DESCRIPTION = ("Classify the input text into a set of known categories.") + + +class MetadataWriter(metadata_writer.MetadataWriterBase): + """MetadataWriter to write the metadata into the text classifier.""" + + @classmethod + def create_for_regex_model( + cls, model_buffer: bytearray, + regex_tokenizer: metadata_writer.RegexTokenizer, + labels: metadata_writer.Labels) -> "MetadataWriter": + """Creates MetadataWriter for TFLite model with regex tokentizer. + + The parameters required in this method are mandatory when using MediaPipe + Tasks. + + Note that only the output TFLite is used for deployment. The output JSON + content is used to interpret the metadata content. + + Args: + model_buffer: A valid flatbuffer loaded from the TFLite model file. + regex_tokenizer: information of the regex tokenizer [1] used to process + the input string. If the tokenizer is `BertTokenizer` [2] or + `SentencePieceTokenizer` [3], please refer to + `create_for_bert_model`. + labels: an instance of Labels helper class used in the output + classification tensor [4]. + + [1]: + https://github.com/google/mediapipe/blob/f8af41b1eb49ff4bdad756ff19d1d36f486be614/mediapipe/tasks/metadata/metadata_schema.fbs#L500 + [2]: + https://github.com/google/mediapipe/blob/f8af41b1eb49ff4bdad756ff19d1d36f486be614/mediapipe/tasks/metadata/metadata_schema.fbs#L477 + [3]: + https://github.com/google/mediapipe/blob/f8af41b1eb49ff4bdad756ff19d1d36f486be614/mediapipe/tasks/metadata/metadata_schema.fbs#L485 + [4]: + https://github.com/google/mediapipe/blob/f8af41b1eb49ff4bdad756ff19d1d36f486be614/mediapipe/tasks/metadata/metadata_schema.fbs#L99 + + Returns: + A MetadataWriter object. + """ + writer = metadata_writer.MetadataWriter(model_buffer) + writer.add_general_info(_MODEL_NAME, _MODEL_DESCRIPTION) + writer.add_regex_text_input(regex_tokenizer) + writer.add_classification_output(labels) + return cls(writer) diff --git a/mediapipe/tasks/python/test/audio/audio_classifier_test.py b/mediapipe/tasks/python/test/audio/audio_classifier_test.py index 983e922e7..0d067e587 100644 --- a/mediapipe/tasks/python/test/audio/audio_classifier_test.py +++ b/mediapipe/tasks/python/test/audio/audio_classifier_test.py @@ -174,12 +174,10 @@ class AudioClassifierTest(parameterized.TestCase): self.assertIsInstance(classifier, _AudioClassifier) def test_create_from_options_fails_with_invalid_model_path(self): - # Invalid empty model path. with self.assertRaisesRegex( - ValueError, - r"ExternalFile must specify at least one of 'file_content', " - r"'file_name', 'file_pointer_meta' or 'file_descriptor_meta'."): - base_options = _BaseOptions(model_asset_path='') + RuntimeError, 'Unable to open file at /path/to/invalid/model.tflite'): + base_options = _BaseOptions( + model_asset_path='/path/to/invalid/model.tflite') options = _AudioClassifierOptions(base_options=base_options) _AudioClassifier.create_from_options(options) diff --git a/mediapipe/tasks/python/test/metadata/metadata_writers/BUILD b/mediapipe/tasks/python/test/metadata/metadata_writers/BUILD index a7bfd297d..948b3f8d9 100644 --- a/mediapipe/tasks/python/test/metadata/metadata_writers/BUILD +++ b/mediapipe/tasks/python/test/metadata/metadata_writers/BUILD @@ -53,3 +53,17 @@ py_test( "//mediapipe/tasks/python/test:test_utils", ], ) + +py_test( + name = "text_classifier_test", + srcs = ["text_classifier_test.py"], + data = [ + "//mediapipe/tasks/testdata/metadata:data_files", + "//mediapipe/tasks/testdata/metadata:model_files", + ], + deps = [ + "//mediapipe/tasks/python/metadata/metadata_writers:metadata_writer", + "//mediapipe/tasks/python/metadata/metadata_writers:text_classifier", + "//mediapipe/tasks/python/test:test_utils", + ], +) diff --git a/mediapipe/tasks/python/test/metadata/metadata_writers/metadata_info_test.py b/mediapipe/tasks/python/test/metadata/metadata_writers/metadata_info_test.py index 0e1d1c369..a30f22a08 100644 --- a/mediapipe/tasks/python/test/metadata/metadata_writers/metadata_info_test.py +++ b/mediapipe/tasks/python/test/metadata/metadata_writers/metadata_info_test.py @@ -191,6 +191,43 @@ class InputImageTensorMdTest(parameterized.TestCase): f"{len(norm_mean)} and {len(norm_std)}", str(error.exception)) +class InputTextTensorMdTest(absltest.TestCase): + + _NAME = "input text" + _DESCRIPTION = "The input string." + _VOCAB_FILE = "vocab.txt" + _DELIM_REGEX_PATTERN = r"[^\w\']+" + _EXPECTED_TENSOR_JSON = test_utils.get_test_data_path( + os.path.join(_TEST_DATA_DIR, "input_text_tensor_meta.json")) + _EXPECTED_TENSOR_DEFAULT_JSON = test_utils.get_test_data_path( + os.path.join(_TEST_DATA_DIR, "input_text_tensor_default_meta.json")) + + def test_create_metadata_should_succeed(self): + regex_tokenizer_md = metadata_info.RegexTokenizerMd( + self._DELIM_REGEX_PATTERN, self._VOCAB_FILE) + + text_tensor_md = metadata_info.InputTextTensorMd(self._NAME, + self._DESCRIPTION, + regex_tokenizer_md) + + metadata_json = _metadata.convert_to_json( + _create_dummy_model_metadata_with_tensor( + text_tensor_md.create_metadata())) + with open(self._EXPECTED_TENSOR_JSON, "r") as f: + expected_json = f.read() + self.assertEqual(metadata_json, expected_json) + + def test_create_metadata_by_default_should_succeed(self): + text_tensor_md = metadata_info.InputTextTensorMd() + + metadata_json = _metadata.convert_to_json( + _create_dummy_model_metadata_with_tensor( + text_tensor_md.create_metadata())) + with open(self._EXPECTED_TENSOR_DEFAULT_JSON, "r") as f: + expected_json = f.read() + self.assertEqual(metadata_json, expected_json) + + class ClassificationTensorMdTest(parameterized.TestCase): _NAME = "probability" diff --git a/mediapipe/tasks/python/test/metadata/metadata_writers/metadata_writer_test.py b/mediapipe/tasks/python/test/metadata/metadata_writers/metadata_writer_test.py index 8cde318e7..3c2eb407c 100644 --- a/mediapipe/tasks/python/test/metadata/metadata_writers/metadata_writer_test.py +++ b/mediapipe/tasks/python/test/metadata/metadata_writers/metadata_writer_test.py @@ -113,7 +113,7 @@ class MetadataWriterForTaskTest(absltest.TestCase): def test_initialize_and_populate(self): writer = metadata_writer.MetadataWriter.create( self.image_classifier_model_buffer) - writer.add_genernal_info( + writer.add_general_info( model_name='my_image_model', model_description='my_description') tflite_model, metadata_json = writer.populate() self.assertLen(tflite_model, 1882986) @@ -142,7 +142,7 @@ class MetadataWriterForTaskTest(absltest.TestCase): def test_add_feature_input_output(self): writer = metadata_writer.MetadataWriter.create( self.image_classifier_model_buffer) - writer.add_genernal_info( + writer.add_general_info( model_name='my_model', model_description='my_description') writer.add_feature_input( name='input_tesnor', description='a feature input tensor') @@ -191,7 +191,7 @@ class MetadataWriterForTaskTest(absltest.TestCase): def test_image_classifier(self): writer = metadata_writer.MetadataWriter.create( self.image_classifier_model_buffer) - writer.add_genernal_info( + writer.add_general_info( model_name='image_classifier', model_description='Imagenet classification model') writer.add_image_input( @@ -282,7 +282,7 @@ class MetadataWriterForTaskTest(absltest.TestCase): def test_image_classifier_with_locale_and_score_calibration(self): writer = metadata_writer.MetadataWriter(self.image_classifier_model_buffer) - writer.add_genernal_info( + writer.add_general_info( model_name='image_classifier', model_description='Classify the input image.') writer.add_image_input( diff --git a/mediapipe/tasks/python/test/metadata/metadata_writers/text_classifier_test.py b/mediapipe/tasks/python/test/metadata/metadata_writers/text_classifier_test.py new file mode 100644 index 000000000..e0391085e --- /dev/null +++ b/mediapipe/tasks/python/test/metadata/metadata_writers/text_classifier_test.py @@ -0,0 +1,51 @@ +# Copyright 2022 The MediaPipe Authors. All Rights Reserved. +# +# 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. +# ============================================================================== +"""Tests for metadata_writer.text_classifier.""" + +from absl.testing import absltest + +from mediapipe.tasks.python.metadata.metadata_writers import metadata_writer +from mediapipe.tasks.python.metadata.metadata_writers import text_classifier +from mediapipe.tasks.python.test import test_utils + +_TEST_DIR = "mediapipe/tasks/testdata/metadata/" +_MODEL = test_utils.get_test_data_path(_TEST_DIR + "movie_review.tflite") +_LABEL_FILE = test_utils.get_test_data_path(_TEST_DIR + + "movie_review_labels.txt") +_VOCAB_FILE = test_utils.get_test_data_path(_TEST_DIR + "regex_vocab.txt") +_DELIM_REGEX_PATTERN = r"[^\w\']+" +_JSON_FILE = test_utils.get_test_data_path("movie_review.json") + + +class TextClassifierTest(absltest.TestCase): + + def test_write_metadata(self,): + with open(_MODEL, "rb") as f: + model_buffer = f.read() + writer = text_classifier.MetadataWriter.create_for_regex_model( + model_buffer, + regex_tokenizer=metadata_writer.RegexTokenizer( + delim_regex_pattern=_DELIM_REGEX_PATTERN, + vocab_file_path=_VOCAB_FILE), + labels=metadata_writer.Labels().add_from_file(_LABEL_FILE)) + _, metadata_json = writer.populate() + + with open(_JSON_FILE, "r") as f: + expected_json = f.read() + self.assertEqual(metadata_json, expected_json) + + +if __name__ == "__main__": + absltest.main() diff --git a/mediapipe/tasks/python/test/text/text_classifier_test.py b/mediapipe/tasks/python/test/text/text_classifier_test.py index c93def48e..434181fbe 100644 --- a/mediapipe/tasks/python/test/text/text_classifier_test.py +++ b/mediapipe/tasks/python/test/text/text_classifier_test.py @@ -154,12 +154,10 @@ class ImageClassifierTest(parameterized.TestCase): self.assertIsInstance(classifier, _TextClassifier) def test_create_from_options_fails_with_invalid_model_path(self): - # Invalid empty model path. with self.assertRaisesRegex( - ValueError, - r"ExternalFile must specify at least one of 'file_content', " - r"'file_name', 'file_pointer_meta' or 'file_descriptor_meta'."): - base_options = _BaseOptions(model_asset_path='') + RuntimeError, 'Unable to open file at /path/to/invalid/model.tflite'): + base_options = _BaseOptions( + model_asset_path='/path/to/invalid/model.tflite') options = _TextClassifierOptions(base_options=base_options) _TextClassifier.create_from_options(options) diff --git a/mediapipe/tasks/python/test/vision/image_classifier_test.py b/mediapipe/tasks/python/test/vision/image_classifier_test.py index 11941ce23..97fb30b32 100644 --- a/mediapipe/tasks/python/test/vision/image_classifier_test.py +++ b/mediapipe/tasks/python/test/vision/image_classifier_test.py @@ -147,12 +147,10 @@ class ImageClassifierTest(parameterized.TestCase): self.assertIsInstance(classifier, _ImageClassifier) def test_create_from_options_fails_with_invalid_model_path(self): - # Invalid empty model path. with self.assertRaisesRegex( - ValueError, - r"ExternalFile must specify at least one of 'file_content', " - r"'file_name', 'file_pointer_meta' or 'file_descriptor_meta'."): - base_options = _BaseOptions(model_asset_path='') + RuntimeError, 'Unable to open file at /path/to/invalid/model.tflite'): + base_options = _BaseOptions( + model_asset_path='/path/to/invalid/model.tflite') options = _ImageClassifierOptions(base_options=base_options) _ImageClassifier.create_from_options(options) diff --git a/mediapipe/tasks/python/test/vision/image_segmenter_test.py b/mediapipe/tasks/python/test/vision/image_segmenter_test.py index 5072d3482..7f0b47eb7 100644 --- a/mediapipe/tasks/python/test/vision/image_segmenter_test.py +++ b/mediapipe/tasks/python/test/vision/image_segmenter_test.py @@ -97,12 +97,10 @@ class ImageSegmenterTest(parameterized.TestCase): self.assertIsInstance(segmenter, _ImageSegmenter) def test_create_from_options_fails_with_invalid_model_path(self): - # Invalid empty model path. with self.assertRaisesRegex( - ValueError, - r"ExternalFile must specify at least one of 'file_content', " - r"'file_name', 'file_pointer_meta' or 'file_descriptor_meta'."): - base_options = _BaseOptions(model_asset_path='') + RuntimeError, 'Unable to open file at /path/to/invalid/model.tflite'): + base_options = _BaseOptions( + model_asset_path='/path/to/invalid/model.tflite') options = _ImageSegmenterOptions(base_options=base_options) _ImageSegmenter.create_from_options(options) diff --git a/mediapipe/tasks/python/test/vision/object_detector_test.py b/mediapipe/tasks/python/test/vision/object_detector_test.py index 53c64427f..5afa31459 100644 --- a/mediapipe/tasks/python/test/vision/object_detector_test.py +++ b/mediapipe/tasks/python/test/vision/object_detector_test.py @@ -119,12 +119,10 @@ class ObjectDetectorTest(parameterized.TestCase): self.assertIsInstance(detector, _ObjectDetector) def test_create_from_options_fails_with_invalid_model_path(self): - # Invalid empty model path. with self.assertRaisesRegex( - ValueError, - r"ExternalFile must specify at least one of 'file_content', " - r"'file_name', 'file_pointer_meta' or 'file_descriptor_meta'."): - base_options = _BaseOptions(model_asset_path='') + RuntimeError, 'Unable to open file at /path/to/invalid/model.tflite'): + base_options = _BaseOptions( + model_asset_path='/path/to/invalid/model.tflite') options = _ObjectDetectorOptions(base_options=base_options) _ObjectDetector.create_from_options(options) diff --git a/mediapipe/tasks/python/vision/BUILD b/mediapipe/tasks/python/vision/BUILD index 1d60fa5b5..3c040fb4d 100644 --- a/mediapipe/tasks/python/vision/BUILD +++ b/mediapipe/tasks/python/vision/BUILD @@ -70,7 +70,7 @@ py_library( "//mediapipe/python:packet_creator", "//mediapipe/python:packet_getter", "//mediapipe/tasks/cc/components/proto:segmenter_options_py_pb2", - "//mediapipe/tasks/cc/vision/image_segmenter/proto:image_segmenter_options_py_pb2", + "//mediapipe/tasks/cc/vision/image_segmenter/proto:image_segmenter_graph_options_py_pb2", "//mediapipe/tasks/python/core:base_options", "//mediapipe/tasks/python/core:optional_dependencies", "//mediapipe/tasks/python/core:task_info", diff --git a/mediapipe/tasks/python/vision/image_segmenter.py b/mediapipe/tasks/python/vision/image_segmenter.py index c1b50a5ae..1740d41ef 100644 --- a/mediapipe/tasks/python/vision/image_segmenter.py +++ b/mediapipe/tasks/python/vision/image_segmenter.py @@ -22,7 +22,7 @@ from mediapipe.python import packet_getter from mediapipe.python._framework_bindings import image as image_module from mediapipe.python._framework_bindings import packet from mediapipe.tasks.cc.components.proto import segmenter_options_pb2 -from mediapipe.tasks.cc.vision.image_segmenter.proto import image_segmenter_options_pb2 +from mediapipe.tasks.cc.vision.image_segmenter.proto import image_segmenter_graph_options_pb2 from mediapipe.tasks.python.core import base_options as base_options_module from mediapipe.tasks.python.core import task_info as task_info_module from mediapipe.tasks.python.core.optional_dependencies import doc_controls @@ -31,7 +31,7 @@ from mediapipe.tasks.python.vision.core import vision_task_running_mode _BaseOptions = base_options_module.BaseOptions _SegmenterOptionsProto = segmenter_options_pb2.SegmenterOptions -_ImageSegmenterOptionsProto = image_segmenter_options_pb2.ImageSegmenterOptions +_ImageSegmenterGraphOptionsProto = image_segmenter_graph_options_pb2.ImageSegmenterGraphOptions _RunningMode = vision_task_running_mode.VisionTaskRunningMode _TaskInfo = task_info_module.TaskInfo @@ -40,7 +40,7 @@ _SEGMENTATION_TAG = 'GROUPED_SEGMENTATION' _IMAGE_IN_STREAM_NAME = 'image_in' _IMAGE_OUT_STREAM_NAME = 'image_out' _IMAGE_TAG = 'IMAGE' -_TASK_GRAPH_NAME = 'mediapipe.tasks.vision.ImageSegmenterGraph' +_TASK_GRAPH_NAME = 'mediapipe.tasks.vision.image_segmenter.ImageSegmenterGraph' _MICRO_SECONDS_PER_MILLISECOND = 1000 @@ -81,13 +81,13 @@ class ImageSegmenterOptions: [List[image_module.Image], image_module.Image, int], None]] = None @doc_controls.do_not_generate_docs - def to_pb2(self) -> _ImageSegmenterOptionsProto: + def to_pb2(self) -> _ImageSegmenterGraphOptionsProto: """Generates an ImageSegmenterOptions protobuf object.""" base_options_proto = self.base_options.to_pb2() base_options_proto.use_stream_mode = False if self.running_mode == _RunningMode.IMAGE else True segmenter_options_proto = _SegmenterOptionsProto( output_type=self.output_type.value, activation=self.activation.value) - return _ImageSegmenterOptionsProto( + return _ImageSegmenterGraphOptionsProto( base_options=base_options_proto, segmenter_options=segmenter_options_proto) diff --git a/mediapipe/tasks/testdata/metadata/BUILD b/mediapipe/tasks/testdata/metadata/BUILD index 8ed6e1caa..899e552c2 100644 --- a/mediapipe/tasks/testdata/metadata/BUILD +++ b/mediapipe/tasks/testdata/metadata/BUILD @@ -31,6 +31,7 @@ mediapipe_files(srcs = [ "mobilenet_v2_1.0_224_quant.tflite", "mobilenet_v2_1.0_224_quant_without_metadata.tflite", "mobilenet_v2_1.0_224_without_metadata.tflite", + "movie_review.tflite", ]) exports_files([ @@ -54,6 +55,11 @@ exports_files([ "labels.txt", "mobilenet_v2_1.0_224.json", "mobilenet_v2_1.0_224_quant.json", + "input_text_tensor_meta.json", + "input_text_tensor_default_meta.json", + "movie_review_labels.txt", + "regex_vocab.txt", + "movie_review.json", ]) filegroup( @@ -67,6 +73,7 @@ filegroup( "mobilenet_v2_1.0_224_quant.tflite", "mobilenet_v2_1.0_224_quant_without_metadata.tflite", "mobilenet_v2_1.0_224_without_metadata.tflite", + "movie_review.tflite", ], ) @@ -86,9 +93,14 @@ filegroup( "input_image_tensor_float_meta.json", "input_image_tensor_uint8_meta.json", "input_image_tensor_unsupported_meta.json", + "input_text_tensor_default_meta.json", + "input_text_tensor_meta.json", "labels.txt", "mobilenet_v2_1.0_224.json", "mobilenet_v2_1.0_224_quant.json", + "movie_review.json", + "movie_review_labels.txt", + "regex_vocab.txt", "score_calibration.txt", "score_calibration_file_meta.json", "score_calibration_tensor_meta.json", diff --git a/mediapipe/tasks/testdata/metadata/input_text_tensor_default_meta.json b/mediapipe/tasks/testdata/metadata/input_text_tensor_default_meta.json new file mode 100644 index 000000000..07c23bbbd --- /dev/null +++ b/mediapipe/tasks/testdata/metadata/input_text_tensor_default_meta.json @@ -0,0 +1,17 @@ +{ + "subgraph_metadata": [ + { + "input_tensor_metadata": [ + { + "content": { + "content_properties_type": "FeatureProperties", + "content_properties": { + } + }, + "stats": { + } + } + ] + } + ] +} diff --git a/mediapipe/tasks/testdata/metadata/input_text_tensor_meta.json b/mediapipe/tasks/testdata/metadata/input_text_tensor_meta.json new file mode 100644 index 000000000..04bab2f76 --- /dev/null +++ b/mediapipe/tasks/testdata/metadata/input_text_tensor_meta.json @@ -0,0 +1,34 @@ +{ + "subgraph_metadata": [ + { + "input_tensor_metadata": [ + { + "name": "input text", + "description": "The input string.", + "content": { + "content_properties_type": "FeatureProperties", + "content_properties": { + } + }, + "process_units": [ + { + "options_type": "RegexTokenizerOptions", + "options": { + "delim_regex_pattern": "[^\\w\\']+", + "vocab_file": [ + { + "name": "vocab.txt", + "description": "Vocabulary file to convert natural language words to embedding vectors.", + "type": "VOCABULARY" + } + ] + } + } + ], + "stats": { + } + } + ] + } + ] +} diff --git a/mediapipe/tasks/testdata/metadata/movie_review.json b/mediapipe/tasks/testdata/metadata/movie_review.json new file mode 100644 index 000000000..104ef7099 --- /dev/null +++ b/mediapipe/tasks/testdata/metadata/movie_review.json @@ -0,0 +1,63 @@ +{ + "name": "TextClassifier", + "description": "Classify the input text into a set of known categories.", + "subgraph_metadata": [ + { + "input_tensor_metadata": [ + { + "name": "input_text", + "description": "Embedding vectors representing the input text to be processed.", + "content": { + "content_properties_type": "FeatureProperties", + "content_properties": { + } + }, + "process_units": [ + { + "options_type": "RegexTokenizerOptions", + "options": { + "delim_regex_pattern": "[^\\w\\']+", + "vocab_file": [ + { + "name": "regex_vocab.txt", + "description": "Vocabulary file to convert natural language words to embedding vectors.", + "type": "VOCABULARY" + } + ] + } + } + ], + "stats": { + } + } + ], + "output_tensor_metadata": [ + { + "name": "score", + "description": "Score of the labels respectively.", + "content": { + "content_properties_type": "FeatureProperties", + "content_properties": { + } + }, + "stats": { + "max": [ + 1.0 + ], + "min": [ + 0.0 + ] + }, + "associated_files": [ + { + "name": "labels.txt", + "description": "Labels for categories that the model can recognize.", + "type": "TENSOR_AXIS_LABELS" + } + ] + } + ] + } + ], + "min_parser_version": "1.2.1" +} diff --git a/mediapipe/tasks/testdata/metadata/movie_review_labels.txt b/mediapipe/tasks/testdata/metadata/movie_review_labels.txt new file mode 100644 index 000000000..c0284208a --- /dev/null +++ b/mediapipe/tasks/testdata/metadata/movie_review_labels.txt @@ -0,0 +1,2 @@ +Negative +Positive diff --git a/mediapipe/tasks/testdata/metadata/regex_vocab.txt b/mediapipe/tasks/testdata/metadata/regex_vocab.txt new file mode 100644 index 000000000..0a27d7c60 --- /dev/null +++ b/mediapipe/tasks/testdata/metadata/regex_vocab.txt @@ -0,0 +1,10000 @@ + 0 + 1 + 2 + 3 +the 4 +and 5 +a 6 +of 7 +to 8 +is 9 +br 10 +in 11 +it 12 +i 13 +this 14 +that 15 +was 16 +as 17 +for 18 +with 19 +movie 20 +but 21 +film 22 +on 23 +not 24 +you 25 +are 26 +his 27 +have 28 +he 29 +be 30 +one 31 +all 32 +at 33 +by 34 +an 35 +they 36 +who 37 +so 38 +from 39 +like 40 +her 41 +or 42 +just 43 +about 44 +it's 45 +out 46 +has 47 +if 48 +some 49 +there 50 +what 51 +good 52 +more 53 +when 54 +very 55 +up 56 +no 57 +time 58 +she 59 +even 60 +my 61 +would 62 +which 63 +only 64 +story 65 +really 66 +see 67 +their 68 +had 69 +can 70 +were 71 +me 72 +well 73 +than 74 +we 75 +much 76 +been 77 +bad 78 +get 79 +will 80 +do 81 +also 82 +into 83 +people 84 +other 85 +first 86 +great 87 +because 88 +how 89 +him 90 +most 91 +don't 92 +made 93 +its 94 +then 95 +way 96 +make 97 +them 98 +too 99 +could 100 +any 101 +movies 102 +after 103 +think 104 +characters 105 +watch 106 +two 107 +films 108 +character 109 +seen 110 +many 111 +being 112 +life 113 +plot 114 +never 115 +acting 116 +little 117 +best 118 +love 119 +over 120 +where 121 +did 122 +show 123 +know 124 +off 125 +ever 126 +does 127 +better 128 +your 129 +end 130 +still 131 +man 132 +here 133 +these 134 +say 135 +scene 136 +while 137 +why 138 +scenes 139 +go 140 +such 141 +something 142 +through 143 +should 144 +back 145 +i'm 146 +real 147 +those 148 +watching 149 +now 150 +though 151 +doesn't 152 +years 153 +old 154 +thing 155 +actors 156 +work 157 +10 158 +before 159 +another 160 +didn't 161 +new 162 +funny 163 +nothing 164 +actually 165 +makes 166 +director 167 +look 168 +find 169 +going 170 +few 171 +same 172 +part 173 +again 174 +every 175 +lot 176 +cast 177 +us 178 +quite 179 +down 180 +want 181 +world 182 +things 183 +pretty 184 +young 185 +seems 186 +around 187 +got 188 +horror 189 +however 190 +can't 191 +fact 192 +take 193 +big 194 +enough 195 +long 196 +thought 197 +that's 198 +both 199 +between 200 +series 201 +give 202 +may 203 +original 204 +own 205 +action 206 +i've 207 +right 208 +without 209 +always 210 +times 211 +comedy 212 +point 213 +gets 214 +must 215 +come 216 +role 217 +isn't 218 +saw 219 +almost 220 +interesting 221 +least 222 +family 223 +done 224 +there's 225 +whole 226 +bit 227 +music 228 +script 229 +far 230 +making 231 +guy 232 +anything 233 +minutes 234 +feel 235 +last 236 +since 237 +might 238 +performance 239 +he's 240 +2 241 +probably 242 +kind 243 +am 244 +away 245 +yet 246 +rather 247 +tv 248 +worst 249 +girl 250 +day 251 +sure 252 +fun 253 +hard 254 +woman 255 +played 256 +each 257 +found 258 +anyone 259 +having 260 +although 261 +especially 262 +our 263 +believe 264 +course 265 +comes 266 +looking 267 +screen 268 +trying 269 +set 270 +goes 271 +looks 272 +place 273 +book 274 +different 275 +put 276 +ending 277 +money 278 +maybe 279 +once 280 +sense 281 +reason 282 +true 283 +actor 284 +everything 285 +wasn't 286 +shows 287 +dvd 288 +three 289 +worth 290 +year 291 +job 292 +main 293 +someone 294 +together 295 +watched 296 +play 297 +american 298 +plays 299 +1 300 +said 301 +effects 302 +later 303 +takes 304 +instead 305 +seem 306 +beautiful 307 +john 308 +himself 309 +version 310 +audience 311 +high 312 +house 313 +night 314 +during 315 +everyone 316 +left 317 +special 318 +seeing 319 +half 320 +excellent 321 +wife 322 +star 323 +shot 324 +war 325 +idea 326 +nice 327 +black 328 +less 329 +mind 330 +simply 331 +read 332 +second 333 +else 334 +you're 335 +father 336 +fan 337 +poor 338 +help 339 +completely 340 +death 341 +3 342 +used 343 +home 344 +either 345 +short 346 +line 347 +given 348 +men 349 +top 350 +dead 351 +budget 352 +try 353 +performances 354 +wrong 355 +classic 356 +boring 357 +enjoy 358 +need 359 +rest 360 +use 361 +kids 362 +hollywood 363 +low 364 +production 365 +until 366 +along 367 +full 368 +friends 369 +camera 370 +truly 371 +women 372 +awful 373 +video 374 +next 375 +tell 376 +remember 377 +couple 378 +stupid 379 +start 380 +stars 381 +perhaps 382 +sex 383 +mean 384 +came 385 +recommend 386 +let 387 +moments 388 +wonderful 389 +episode 390 +understand 391 +small 392 +face 393 +terrible 394 +playing 395 +school 396 +getting 397 +written 398 +doing 399 +often 400 +keep 401 +early 402 +name 403 +perfect 404 +style 405 +human 406 +definitely 407 +gives 408 +others 409 +itself 410 +lines 411 +live 412 +become 413 +dialogue 414 +person 415 +lost 416 +finally 417 +piece 418 +head 419 +case 420 +felt 421 +yes 422 +liked 423 +supposed 424 +title 425 +couldn't 426 +absolutely 427 +white 428 +against 429 +boy 430 +picture 431 +sort 432 +worse 433 +certainly 434 +went 435 +entire 436 +waste 437 +cinema 438 +problem 439 +hope 440 +entertaining 441 +she's 442 +mr 443 +overall 444 +evil 445 +called 446 +loved 447 +based 448 +oh 449 +several 450 +fans 451 +mother 452 +drama 453 +beginning 454 +killer 455 +lives 456 +5 457 +direction 458 +care 459 +already 460 +becomes 461 +laugh 462 +example 463 +friend 464 +dark 465 +despite 466 +under 467 +seemed 468 +throughout 469 +4 470 +turn 471 +unfortunately 472 +wanted 473 +i'd 474 +– 475 +children 476 +final 477 +fine 478 +history 479 +amazing 480 +sound 481 +guess 482 +heart 483 +totally 484 +lead 485 +humor 486 +writing 487 +michael 488 +quality 489 +you'll 490 +close 491 +son 492 +guys 493 +wants 494 +works 495 +behind 496 +tries 497 +art 498 +side 499 +game 500 +past 501 +able 502 +b 503 +days 504 +turns 505 +child 506 +they're 507 +hand 508 +flick 509 +enjoyed 510 +act 511 +genre 512 +town 513 +favorite 514 +soon 515 +kill 516 +starts 517 +sometimes 518 +car 519 +gave 520 +run 521 +late 522 +eyes 523 +actress 524 +etc 525 +directed 526 +horrible 527 +won't 528 +viewer 529 +brilliant 530 +parts 531 +self 532 +themselves 533 +hour 534 +expect 535 +thinking 536 +stories 537 +stuff 538 +girls 539 +obviously 540 +blood 541 +decent 542 +city 543 +voice 544 +highly 545 +myself 546 +feeling 547 +fight 548 +except 549 +slow 550 +matter 551 +type 552 +anyway 553 +kid 554 +roles 555 +killed 556 +heard 557 +god 558 +age 559 +says 560 +moment 561 +took 562 +leave 563 +writer 564 +strong 565 +cannot 566 +violence 567 +police 568 +hit 569 +stop 570 +happens 571 +particularly 572 +known 573 +involved 574 +happened 575 +extremely 576 +daughter 577 +obvious 578 +told 579 +chance 580 +living 581 +coming 582 +lack 583 +alone 584 +experience 585 +wouldn't 586 +including 587 +murder 588 +attempt 589 +s 590 +please 591 +james 592 +happen 593 +wonder 594 +crap 595 +ago 596 +brother 597 +film's 598 +gore 599 +none 600 +complete 601 +interest 602 +score 603 +group 604 +cut 605 +simple 606 +save 607 +ok 608 +hell 609 +looked 610 +career 611 +number 612 +song 613 +possible 614 +seriously 615 +annoying 616 +shown 617 +exactly 618 +sad 619 +running 620 +musical 621 +serious 622 +taken 623 +yourself 624 +whose 625 +released 626 +cinematography 627 +david 628 +scary 629 +ends 630 +english 631 +hero 632 +usually 633 +hours 634 +reality 635 +opening 636 +i'll 637 +across 638 +today 639 +jokes 640 +light 641 +hilarious 642 +somewhat 643 +usual 644 +started 645 +cool 646 +ridiculous 647 +body 648 +relationship 649 +view 650 +level 651 +opinion 652 +change 653 +happy 654 +middle 655 +taking 656 +wish 657 +husband 658 +finds 659 +saying 660 +order 661 +talking 662 +ones 663 +documentary 664 +shots 665 +huge 666 +novel 667 +female 668 +mostly 669 +robert 670 +power 671 +episodes 672 +room 673 +important 674 +rating 675 +talent 676 +five 677 +major 678 +turned 679 +strange 680 +word 681 +modern 682 +call 683 +apparently 684 +disappointed 685 +single 686 +events 687 +due 688 +four 689 +songs 690 +basically 691 +attention 692 +7 693 +knows 694 +clearly 695 +supporting 696 +knew 697 +british 698 +television 699 +comic 700 +non 701 +fast 702 +earth 703 +country 704 +future 705 +cheap 706 +class 707 +thriller 708 +8 709 +silly 710 +king 711 +problems 712 +aren't 713 +easily 714 +words 715 +tells 716 +miss 717 +jack 718 +local 719 +sequence 720 +bring 721 +entertainment 722 +paul 723 +beyond 724 +upon 725 +whether 726 +predictable 727 +moving 728 +similar 729 +straight 730 +romantic 731 +sets 732 +review 733 +falls 734 +oscar 735 +mystery 736 +enjoyable 737 +needs 738 +appears 739 +talk 740 +rock 741 +george 742 +giving 743 +eye 744 +richard 745 +within 746 +ten 747 +animation 748 +message 749 +theater 750 +near 751 +above 752 +dull 753 +nearly 754 +sequel 755 +theme 756 +points 757 +' 758 +stand 759 +mention 760 +lady 761 +bunch 762 +add 763 +feels 764 +herself 765 +release 766 +red 767 +team 768 +storyline 769 +surprised 770 +ways 771 +using 772 +named 773 +haven't 774 +lots 775 +easy 776 +fantastic 777 +begins 778 +actual 779 +working 780 +effort 781 +york 782 +die 783 +hate 784 +french 785 +minute 786 +tale 787 +clear 788 +stay 789 +9 790 +elements 791 +feature 792 +among 793 +follow 794 +comments 795 +re 796 +viewers 797 +avoid 798 +sister 799 +showing 800 +typical 801 +editing 802 +what's 803 +famous 804 +tried 805 +sorry 806 +dialog 807 +check 808 +fall 809 +period 810 +season 811 +form 812 +certain 813 +filmed 814 +weak 815 +soundtrack 816 +means 817 +buy 818 +material 819 +somehow 820 +realistic 821 +figure 822 +crime 823 +doubt 824 +gone 825 +peter 826 +tom 827 +kept 828 +viewing 829 +t 830 +general 831 +leads 832 +greatest 833 +space 834 +lame 835 +suspense 836 +dance 837 +imagine 838 +brought 839 +third 840 +atmosphere 841 +hear 842 +particular 843 +sequences 844 +whatever 845 +parents 846 +move 847 +lee 848 +indeed 849 +learn 850 +rent 851 +de 852 +eventually 853 +note 854 +deal 855 +average 856 +reviews 857 +wait 858 +forget 859 +japanese 860 +sexual 861 +poorly 862 +premise 863 +okay 864 +zombie 865 +surprise 866 +believable 867 +stage 868 +possibly 869 +sit 870 +who's 871 +decided 872 +expected 873 +you've 874 +subject 875 +nature 876 +became 877 +difficult 878 +free 879 +killing 880 +screenplay 881 +truth 882 +romance 883 +dr 884 +nor 885 +reading 886 +needed 887 +question 888 +leaves 889 +street 890 +20 891 +meets 892 +hot 893 +unless 894 +begin 895 +baby 896 +superb 897 +credits 898 +imdb 899 +otherwise 900 +write 901 +shame 902 +let's 903 +situation 904 +dramatic 905 +memorable 906 +directors 907 +earlier 908 +meet 909 +disney 910 +open 911 +dog 912 +badly 913 +joe 914 +male 915 +weird 916 +acted 917 +forced 918 +laughs 919 +sci 920 +emotional 921 +older 922 +realize 923 +fi 924 +dream 925 +society 926 +writers 927 +interested 928 +footage 929 +forward 930 +comment 931 +crazy 932 +deep 933 +sounds 934 +plus 935 +beauty 936 +whom 937 +america 938 +fantasy 939 +directing 940 +keeps 941 +ask 942 +development 943 +features 944 +air 945 +quickly 946 +mess 947 +creepy 948 +towards 949 +perfectly 950 +mark 951 +worked 952 +box 953 +cheesy 954 +unique 955 +setting 956 +hands 957 +plenty 958 +result 959 +previous 960 +brings 961 +effect 962 +e 963 +total 964 +personal 965 +incredibly 966 +rate 967 +fire 968 +monster 969 +business 970 +leading 971 +apart 972 +casting 973 +admit 974 +joke 975 +powerful 976 +appear 977 +background 978 +telling 979 +girlfriend 980 +meant 981 +christmas 982 +hardly 983 +present 984 +battle 985 +potential 986 +create 987 +bill 988 +break 989 +pay 990 +masterpiece 991 +gay 992 +political 993 +return 994 +dumb 995 +fails 996 +fighting 997 +various 998 +era 999 +portrayed 1000 +co 1001 +cop 1002 +secret 1003 +inside 1004 +outside 1005 +nudity 1006 +reasons 1007 +ideas 1008 +twist 1009 +western 1010 +front 1011 +missing 1012 +boys 1013 +match 1014 +deserves 1015 +jane 1016 +expecting 1017 +fairly 1018 +villain 1019 +talented 1020 +married 1021 +ben 1022 +success 1023 +william 1024 +unlike 1025 +rich 1026 +attempts 1027 +spoilers 1028 +list 1029 +manages 1030 +social 1031 +odd 1032 +recently 1033 +remake 1034 +flat 1035 +cute 1036 +further 1037 +sadly 1038 +copy 1039 +wrote 1040 +agree 1041 +doctor 1042 +cold 1043 +plain 1044 +following 1045 +mentioned 1046 +sweet 1047 +incredible 1048 +missed 1049 +pure 1050 +crew 1051 +office 1052 +wasted 1053 +ended 1054 +produced 1055 +gun 1056 +filmmakers 1057 +large 1058 +caught 1059 +revenge 1060 +filled 1061 +pace 1062 +popular 1063 +waiting 1064 +'the 1065 +members 1066 +science 1067 +decides 1068 +considering 1069 +hold 1070 +public 1071 +cartoon 1072 +party 1073 +tension 1074 +created 1075 +slightly 1076 +uses 1077 +convincing 1078 +compared 1079 +la 1080 +familiar 1081 +neither 1082 +mary 1083 +spent 1084 +sees 1085 +6 1086 +suddenly 1087 +30 1088 +intelligent 1089 +escape 1090 +scott 1091 +fear 1092 +water 1093 +brothers 1094 +d 1095 +clever 1096 +entirely 1097 +kills 1098 +choice 1099 +bored 1100 +language 1101 +moves 1102 +spirit 1103 +laughing 1104 +dancing 1105 +we're 1106 +value 1107 +cover 1108 +credit 1109 +state 1110 +island 1111 +successful 1112 +trouble 1113 +visual 1114 +violent 1115 +ultimately 1116 +century 1117 +singing 1118 +15 1119 +concept 1120 +basic 1121 +italian 1122 +positive 1123 +german 1124 +animated 1125 +biggest 1126 +exciting 1127 +speak 1128 +runs 1129 +store 1130 +died 1131 +cat 1132 +consider 1133 +effective 1134 +walk 1135 +recent 1136 +depth 1137 +former 1138 +amusing 1139 +control 1140 +common 1141 +spend 1142 +band 1143 +appreciate 1144 +zombies 1145 +portrayal 1146 +force 1147 +c 1148 +pointless 1149 +rated 1150 +books 1151 +focus 1152 +hair 1153 +adventure 1154 +younger 1155 +solid 1156 +trash 1157 +adult 1158 +impressive 1159 +follows 1160 +respect 1161 +bizarre 1162 +tone 1163 +law 1164 +super 1165 +amount 1166 +impossible 1167 +mad 1168 +company 1169 +college 1170 +van 1171 +prison 1172 +weren't 1173 +conclusion 1174 +chemistry 1175 +win 1176 +showed 1177 +recommended 1178 +slasher 1179 +producers 1180 +culture 1181 +studio 1182 +fit 1183 +starring 1184 +heavy 1185 +situations 1186 +project 1187 +makers 1188 +trip 1189 +awesome 1190 +accent 1191 +considered 1192 +disturbing 1193 +changed 1194 +sick 1195 +failed 1196 +decide 1197 +somewhere 1198 +won 1199 +leaving 1200 +barely 1201 +honest 1202 +cause 1203 +questions 1204 +shooting 1205 +u 1206 +longer 1207 +post 1208 +f 1209 +anti 1210 +tough 1211 +aside 1212 +ghost 1213 +fake 1214 +cult 1215 +thanks 1216 +meaning 1217 +images 1218 +fiction 1219 +charming 1220 +audiences 1221 +computer 1222 +tony 1223 +brain 1224 +planet 1225 +south 1226 +literally 1227 +generally 1228 +touch 1229 +steve 1230 +stick 1231 +likes 1232 +ex 1233 +values 1234 +pathetic 1235 +magic 1236 +involving 1237 +surprisingly 1238 +alive 1239 +jim 1240 +immediately 1241 +grade 1242 +yeah 1243 +garbage 1244 +100 1245 +dad 1246 +bought 1247 +military 1248 +natural 1249 +camp 1250 +aspect 1251 +honestly 1252 +adaptation 1253 +utterly 1254 +detective 1255 +ability 1256 +fair 1257 +shoot 1258 +smith 1259 +explain 1260 +pick 1261 +genius 1262 +west 1263 +glad 1264 +frank 1265 +sitting 1266 +appearance 1267 +pictures 1268 +week 1269 +motion 1270 +appeal 1271 +army 1272 +standard 1273 +attack 1274 +knowing 1275 +personally 1276 +catch 1277 +drive 1278 +sexy 1279 +normal 1280 +rare 1281 +nowhere 1282 +added 1283 +sam 1284 +humour 1285 +walking 1286 +remains 1287 +purpose 1288 +edge 1289 +comedies 1290 +thinks 1291 +loud 1292 +beautifully 1293 +thank 1294 +silent 1295 +taste 1296 +unbelievable 1297 +naked 1298 +twists 1299 +master 1300 +touching 1301 +subtle 1302 +terms 1303 +date 1304 +equally 1305 +dreams 1306 +terrific 1307 +channel 1308 +drawn 1309 +mood 1310 +journey 1311 +door 1312 +chase 1313 +fully 1314 +complex 1315 +london 1316 +key 1317 +wow 1318 +managed 1319 +road 1320 +narrative 1321 +laughable 1322 +mistake 1323 +bottom 1324 +producer 1325 +themes 1326 +movie's 1327 +pieces 1328 +likely 1329 +climax 1330 +g 1331 +disappointing 1332 +club 1333 +lovely 1334 +harry 1335 +blue 1336 +nobody 1337 +excuse 1338 +outstanding 1339 +soldiers 1340 +issues 1341 +stewart 1342 +constantly 1343 +award 1344 +pass 1345 +thus 1346 +plan 1347 +surely 1348 +marriage 1349 +painful 1350 +justice 1351 +costumes 1352 +presented 1353 +batman 1354 +80's 1355 +innocent 1356 +soul 1357 +wild 1358 +noir 1359 +cinematic 1360 +spoiler 1361 +vampire 1362 +finish 1363 +slowly 1364 +ride 1365 +gang 1366 +contains 1367 +christopher 1368 +presence 1369 +places 1370 +besides 1371 +government 1372 +details 1373 +train 1374 +central 1375 +thrown 1376 +manner 1377 +chris 1378 +historical 1379 +stunning 1380 +photography 1381 +charm 1382 +hoping 1383 +impression 1384 +scenery 1385 +speaking 1386 +disappointment 1387 +loves 1388 +animals 1389 +you'd 1390 +developed 1391 +drug 1392 +smart 1393 +charles 1394 +indian 1395 +numbers 1396 +mysterious 1397 +expectations 1398 +color 1399 +hey 1400 +exception 1401 +throw 1402 +minor 1403 +ahead 1404 +double 1405 +track 1406 +stands 1407 +suppose 1408 +aspects 1409 +boss 1410 +woods 1411 +sent 1412 +festival 1413 +bother 1414 +cry 1415 +church 1416 +feelings 1417 +critics 1418 +green 1419 +brief 1420 +acts 1421 +opera 1422 +filming 1423 +mainly 1424 +support 1425 +emotion 1426 +element 1427 +held 1428 +fascinating 1429 +building 1430 +million 1431 +boyfriend 1432 +names 1433 +opportunity 1434 +serial 1435 +intended 1436 +forever 1437 +emotions 1438 +available 1439 +victim 1440 +charlie 1441 +dies 1442 +changes 1443 +compelling 1444 +bed 1445 +six 1446 +born 1447 +happening 1448 +bar 1449 +paris 1450 +likable 1451 +lived 1452 +twice 1453 +falling 1454 +hotel 1455 +zero 1456 +puts 1457 +tired 1458 +image 1459 +pain 1460 +lover 1461 +everybody 1462 +giant 1463 +offer 1464 +shock 1465 +spot 1466 +suggest 1467 +j 1468 +henry 1469 +include 1470 +confused 1471 +trailer 1472 +adults 1473 +difference 1474 +student 1475 +fresh 1476 +followed 1477 +bruce 1478 +r 1479 +kelly 1480 +hasn't 1481 +appeared 1482 +approach 1483 +victims 1484 +christian 1485 +fellow 1486 +hurt 1487 +impact 1488 +putting 1489 +gorgeous 1490 +step 1491 +sub 1492 +mix 1493 +event 1494 +notice 1495 +murders 1496 +share 1497 +laughed 1498 +confusing 1499 +content 1500 +mediocre 1501 +11 1502 +lacks 1503 +direct 1504 +supposedly 1505 +summer 1506 +actresses 1507 +flaws 1508 +porn 1509 +system 1510 +page 1511 +holes 1512 +wall 1513 +billy 1514 +moral 1515 +jerry 1516 +worthy 1517 +creative 1518 +relationships 1519 +rape 1520 +tragedy 1521 +race 1522 +thin 1523 +lighting 1524 +helps 1525 +random 1526 +answer 1527 +gem 1528 +funniest 1529 +ii 1530 +americans 1531 +jones 1532 +merely 1533 +proves 1534 +wondering 1535 +alien 1536 +students 1537 +ray 1538 +paid 1539 +al 1540 +land 1541 +seven 1542 +damn 1543 +agent 1544 +delivers 1545 +imagination 1546 +park 1547 +childhood 1548 +flying 1549 +hospital 1550 +forgotten 1551 +90 1552 +standards 1553 +flicks 1554 +impressed 1555 +finding 1556 +absolute 1557 +ugly 1558 +beat 1559 +jean 1560 +don 1561 +thoroughly 1562 +ms 1563 +attractive 1564 +ground 1565 +negative 1566 +wise 1567 +provides 1568 +latter 1569 +50 1570 +stuck 1571 +extreme 1572 +seemingly 1573 +seconds 1574 +becoming 1575 +winning 1576 +addition 1577 +reminded 1578 +tragic 1579 +offers 1580 +inspired 1581 +count 1582 +fell 1583 +thats 1584 +lose 1585 +affair 1586 +turning 1587 +folks 1588 +detail 1589 +faces 1590 +cliché 1591 +design 1592 +martin 1593 +collection 1594 +afraid 1595 +intense 1596 +fashion 1597 +pull 1598 +hidden 1599 +industry 1600 +man's 1601 +allen 1602 +apartment 1603 +o 1604 +quick 1605 +nasty 1606 +arthur 1607 +adds 1608 +area 1609 +rented 1610 +alan 1611 +angry 1612 +personality 1613 +artistic 1614 +length 1615 +shouldn't 1616 +therefore 1617 +information 1618 +chinese 1619 +brian 1620 +shocking 1621 +location 1622 +ready 1623 +professional 1624 +lets 1625 +animal 1626 +anymore 1627 +games 1628 +teen 1629 +states 1630 +soldier 1631 +listen 1632 +mom 1633 +describe 1634 +lord 1635 +news 1636 +picked 1637 +led 1638 +wooden 1639 +favourite 1640 +dirty 1641 +mouth 1642 +asks 1643 +food 1644 +deliver 1645 +onto 1646 +martial 1647 +bond 1648 +clothes 1649 +wars 1650 +struggle 1651 +queen 1652 +redeeming 1653 +stone 1654 +jason 1655 +scientist 1656 +p 1657 +wearing 1658 +ed 1659 +stephen 1660 +compare 1661 +castle 1662 +intelligence 1663 +creature 1664 +cross 1665 +sleep 1666 +teenage 1667 +allowed 1668 +wonderfully 1669 +necessary 1670 +carry 1671 +drugs 1672 +40 1673 +tears 1674 +fox 1675 +criminal 1676 +rip 1677 +helped 1678 +member 1679 +desperate 1680 +moved 1681 +sight 1682 +cgi 1683 +trust 1684 +deeply 1685 +roll 1686 +includes 1687 +willing 1688 +whatsoever 1689 +disaster 1690 +12 1691 +machine 1692 +ship 1693 +treat 1694 +began 1695 +mid 1696 +uncle 1697 +grace 1698 +phone 1699 +70's 1700 +williams 1701 +commentary 1702 +build 1703 +accident 1704 +captain 1705 +realized 1706 +plane 1707 +energy 1708 +station 1709 +warning 1710 +epic 1711 +davis 1712 +rarely 1713 +humans 1714 +loving 1715 +theatre 1716 +comedic 1717 +witch 1718 +pop 1719 +suicide 1720 +dying 1721 +powers 1722 +filmmaker 1723 +independent 1724 +introduced 1725 +nightmare 1726 +extra 1727 +engaging 1728 +actions 1729 +character's 1730 +superior 1731 +unusual 1732 +arts 1733 +apparent 1734 +suit 1735 +religious 1736 +heroes 1737 +danny 1738 +remarkable 1739 +artist 1740 +allow 1741 +pleasure 1742 +continue 1743 +unnecessary 1744 +x 1745 +ring 1746 +returns 1747 +physical 1748 +sky 1749 +teacher 1750 +pre 1751 +mental 1752 +watchable 1753 +provide 1754 +absurd 1755 +tim 1756 +memory 1757 +grand 1758 +technical 1759 +normally 1760 +wedding 1761 +desire 1762 +limited 1763 +anywhere 1764 +scared 1765 +russian 1766 +surprising 1767 +douglas 1768 +finished 1769 +brutal 1770 +skip 1771 +vision 1772 +process 1773 +intriguing 1774 +bloody 1775 +media 1776 +holds 1777 +exist 1778 +accept 1779 +nicely 1780 +suspect 1781 +000 1782 +jump 1783 +twenty 1784 +paced 1785 +wanting 1786 +search 1787 +cops 1788 +torture 1789 +growing 1790 +reminds 1791 +jr 1792 +according 1793 +pacing 1794 +legend 1795 +soft 1796 +passion 1797 +andy 1798 +player 1799 +hated 1800 +bits 1801 +fred 1802 +asked 1803 +faith 1804 +joy 1805 +johnny 1806 +clichés 1807 +jeff 1808 +academy 1809 +dressed 1810 +pilot 1811 +eddie 1812 +constant 1813 +anybody 1814 +ill 1815 +deserved 1816 +horse 1817 +gold 1818 +drunk 1819 +joan 1820 +blame 1821 +originally 1822 +explanation 1823 +dangerous 1824 +instance 1825 +smile 1826 +heaven 1827 +heads 1828 +sat 1829 +community 1830 +england 1831 +superman 1832 +deserve 1833 +issue 1834 +nonsense 1835 +met 1836 +dick 1837 +lies 1838 +capture 1839 +gotten 1840 +toward 1841 +kevin 1842 +somebody 1843 +soap 1844 +field 1845 +lovers 1846 +plots 1847 +taylor 1848 +mixed 1849 +players 1850 +nick 1851 +explained 1852 +record 1853 +fail 1854 +creating 1855 +vhs 1856 +knowledge 1857 +quiet 1858 +unknown 1859 +fights 1860 +starting 1861 +friendship 1862 +accurate 1863 +whilst 1864 +guns 1865 +price 1866 +adam 1867 +kate 1868 +hadn't 1869 +sucks 1870 +ball 1871 +river 1872 +floor 1873 +european 1874 +spanish 1875 +wide 1876 +cable 1877 +radio 1878 +fu 1879 +cars 1880 +jackson 1881 +realism 1882 +memories 1883 +moon 1884 +finest 1885 +heroine 1886 +aware 1887 +loose 1888 +eating 1889 +featuring 1890 +prince 1891 +lacking 1892 +responsible 1893 +saved 1894 +keeping 1895 +empty 1896 +understanding 1897 +japan 1898 +treated 1899 +eat 1900 +results 1901 +cuts 1902 +ice 1903 +bland 1904 +terribly 1905 +pulled 1906 +saving 1907 +below 1908 +officer 1909 +villains 1910 +candy 1911 +broken 1912 +sign 1913 +ladies 1914 +hopes 1915 +rubbish 1916 +delightful 1917 +vs 1918 +judge 1919 +witty 1920 +manage 1921 +fat 1922 +mine 1923 +gene 1924 +noticed 1925 +included 1926 +bright 1927 +months 1928 +forces 1929 +screaming 1930 +higher 1931 +kinda 1932 +wind 1933 +tarzan 1934 +cage 1935 +hits 1936 +loss 1937 +today's 1938 +monsters 1939 +youth 1940 +sing 1941 +numerous 1942 +partner 1943 +conflict 1944 +whenever 1945 +humanity 1946 +concerned 1947 +pretentious 1948 +fate 1949 +singer 1950 +dealing 1951 +mike 1952 +driving 1953 +jesus 1954 +private 1955 +talents 1956 +discovered 1957 +naturally 1958 +skills 1959 +unfunny 1960 +opposite 1961 +finale 1962 +bigger 1963 +v 1964 +ann 1965 +international 1966 +dated 1967 +kick 1968 +ups 1969 +prove 1970 +perspective 1971 +morning 1972 +mission 1973 +discover 1974 +portray 1975 +blonde 1976 +here's 1977 +loses 1978 +locations 1979 +visit 1980 +ordinary 1981 +bank 1982 +m 1983 +humorous 1984 +werewolf 1985 +streets 1986 +psychological 1987 +regular 1988 +reviewers 1989 +received 1990 +kong 1991 +w 1992 +edited 1993 +gags 1994 +ass 1995 +luck 1996 +curious 1997 +gary 1998 +continues 1999 +magnificent 2000 +13 2001 +we've 2002 +behavior 2003 +captured 2004 +jimmy 2005 +satire 2006 +survive 2007 +context 2008 +visually 2009 +breaks 2010 +existence 2011 +shallow 2012 +opens 2013 +l 2014 +mrs 2015 +debut 2016 +advice 2017 +calls 2018 +sea 2019 +foot 2020 +morgan 2021 +shop 2022 +h 2023 +murdered 2024 +connection 2025 +core 2026 +essentially 2027 +current 2028 +revealed 2029 +director's 2030 +corny 2031 +remembered 2032 +deals 2033 +blind 2034 +frankly 2035 +occasionally 2036 +lesson 2037 +genuine 2038 +scream 2039 +traditional 2040 +they've 2041 +lucky 2042 +identity 2043 +dimensional 2044 +african 2045 +bob 2046 +anthony 2047 +efforts 2048 +sean 2049 +golden 2050 +learned 2051 +segment 2052 +stock 2053 +window 2054 +cameo 2055 +owner 2056 +visuals 2057 +versions 2058 +village 2059 +albert 2060 +develop 2061 +santa 2062 +formula 2063 +miles 2064 +keaton 2065 +one's 2066 +sucked 2067 +decade 2068 +buddy 2069 +genuinely 2070 +grown 2071 +references 2072 +suffering 2073 +boat 2074 +lewis 2075 +unexpected 2076 +favor 2077 +study 2078 +washington 2079 +allows 2080 +program 2081 +national 2082 +grew 2083 +80s 2084 +proved 2085 +meanwhile 2086 +overly 2087 +ages 2088 +board 2089 +standing 2090 +logic 2091 +desert 2092 +spectacular 2093 +awkward 2094 +ultimate 2095 +comparison 2096 +reaction 2097 +rob 2098 +sheer 2099 +jennifer 2100 +reach 2101 +thomas 2102 +unable 2103 +failure 2104 +brilliantly 2105 +travel 2106 +grant 2107 +ford 2108 +vampires 2109 +types 2110 +parody 2111 +gangster 2112 +devil 2113 +steal 2114 +brown 2115 +passed 2116 +sudden 2117 +stereotypes 2118 +sake 2119 +flesh 2120 +leader 2121 +frame 2122 +bear 2123 +strength 2124 +speed 2125 +creates 2126 +eric 2127 +awards 2128 +laughter 2129 +dan 2130 +technology 2131 +delivered 2132 +author 2133 +bet 2134 +kung 2135 +crappy 2136 +wood 2137 +site 2138 +broadway 2139 +insane 2140 +trek 2141 +executed 2142 +relief 2143 +lake 2144 +hitler 2145 +gonna 2146 +discovers 2147 +emotionally 2148 +painfully 2149 +dreadful 2150 +marie 2151 +utter 2152 +commercial 2153 +decision 2154 +code 2155 +steven 2156 +fault 2157 +anime 2158 +majority 2159 +anne 2160 +round 2161 +pair 2162 +robin 2163 +caused 2164 +bomb 2165 +families 2166 +psycho 2167 +driven 2168 +attitude 2169 +clean 2170 +built 2171 +gratuitous 2172 +harris 2173 +native 2174 +luke 2175 +entertained 2176 +graphic 2177 +ran 2178 +killers 2179 +meeting 2180 +test 2181 +simon 2182 +flashbacks 2183 +underrated 2184 +nevertheless 2185 +model 2186 +seasons 2187 +asian 2188 +foreign 2189 +hill 2190 +levels 2191 +obsessed 2192 +evening 2193 +feet 2194 +halloween 2195 +vehicle 2196 +barbara 2197 +relate 2198 +treatment 2199 +rise 2200 +practically 2201 +range 2202 +endless 2203 +freedom 2204 +costs 2205 +religion 2206 +gory 2207 +cash 2208 +described 2209 +wit 2210 +pleasant 2211 +aged 2212 +ancient 2213 +tape 2214 +reviewer 2215 +center 2216 +president 2217 +chosen 2218 +lynch 2219 +product 2220 +combination 2221 +send 2222 +fly 2223 +seat 2224 +sell 2225 +70s 2226 +irritating 2227 +exploitation 2228 +excited 2229 +stopped 2230 +hearing 2231 +rescue 2232 +fill 2233 +howard 2234 +portrays 2235 +gordon 2236 +assume 2237 +parker 2238 +classics 2239 +pity 2240 +0 2241 +produce 2242 +hunter 2243 +breaking 2244 +dry 2245 +fame 2246 +anna 2247 +generation 2248 +sheriff 2249 +capable 2250 +believes 2251 +handsome 2252 +theatrical 2253 +asking 2254 +sports 2255 +largely 2256 +choose 2257 +theaters 2258 +sympathetic 2259 +extras 2260 +proper 2261 +ruined 2262 +cares 2263 +contrived 2264 +portraying 2265 +drew 2266 +individual 2267 +embarrassing 2268 +rules 2269 +unrealistic 2270 +learns 2271 +warm 2272 +victor 2273 +daniel 2274 +marry 2275 +appealing 2276 +safe 2277 +dubbed 2278 +depressing 2279 +canadian 2280 +freddy 2281 +shakespeare 2282 +recall 2283 +chick 2284 +uk 2285 +winner 2286 +hearted 2287 +contrast 2288 +sequels 2289 +involves 2290 +par 2291 +woody 2292 +crowd 2293 +matters 2294 +k 2295 +correct 2296 +chief 2297 +costume 2298 +haunting 2299 +paper 2300 +research 2301 +vote 2302 +strongly 2303 +heck 2304 +nominated 2305 +grow 2306 +clue 2307 +claim 2308 +facts 2309 +eight 2310 +protagonist 2311 +matt 2312 +rose 2313 +evidence 2314 +joseph 2315 +appropriate 2316 +disgusting 2317 +excitement 2318 +football 2319 +lousy 2320 +germany 2321 +cost 2322 +france 2323 +saturday 2324 +priest 2325 +talks 2326 +substance 2327 +losing 2328 +patrick 2329 +destroy 2330 +circumstances 2331 +tedious 2332 +training 2333 +thoughts 2334 +hunt 2335 +market 2336 +scare 2337 +voices 2338 +promise 2339 +naive 2340 +bringing 2341 +amateurish 2342 +teenager 2343 +angel 2344 +walter 2345 +captures 2346 +convinced 2347 +hanging 2348 +satisfying 2349 +bodies 2350 +united 2351 +fits 2352 +tend 2353 +jackie 2354 +trilogy 2355 +roy 2356 +horribly 2357 +lower 2358 +asleep 2359 +virtually 2360 +baseball 2361 +robot 2362 +hopefully 2363 +rental 2364 +alex 2365 +com 2366 +factor 2367 +haunted 2368 +teenagers 2369 +hall 2370 +walks 2371 +spoil 2372 +creatures 2373 +amateur 2374 +relatively 2375 +steals 2376 +mask 2377 +welcome 2378 +cinderella 2379 +covered 2380 +ryan 2381 +danger 2382 +europe 2383 +insult 2384 +category 2385 +continuity 2386 +mini 2387 +unlikely 2388 +drag 2389 +sinatra 2390 +skin 2391 +contemporary 2392 +louis 2393 +semi 2394 +viewed 2395 +fare 2396 +north 2397 +influence 2398 +depicted 2399 +handled 2400 +target 2401 +oliver 2402 +offensive 2403 +hat 2404 +initial 2405 +nancy 2406 +scale 2407 +lawyer 2408 +tiny 2409 +cutting 2410 +unfortunate 2411 +holding 2412 +witness 2413 +shocked 2414 +africa 2415 +remain 2416 +believed 2417 +fool 2418 +inner 2419 +politics 2420 +hide 2421 +reporter 2422 +presents 2423 +section 2424 +movement 2425 +provided 2426 +surreal 2427 +promising 2428 +designed 2429 +makeup 2430 +max 2431 +qualities 2432 +liners 2433 +refreshing 2434 +australian 2435 +source 2436 +14 2437 +structure 2438 +closer 2439 +drop 2440 +forgettable 2441 +touches 2442 +welles 2443 +display 2444 +angles 2445 +pile 2446 +fairy 2447 +repeated 2448 +till 2449 +texas 2450 +wayne 2451 +claims 2452 +previously 2453 +faced 2454 +sharp 2455 +deaths 2456 +ruin 2457 +accents 2458 +surprises 2459 +universal 2460 +degree 2461 +focused 2462 +propaganda 2463 +plans 2464 +serves 2465 +speaks 2466 +supernatural 2467 +highlight 2468 +service 2469 +peace 2470 +chose 2471 +related 2472 +cartoons 2473 +adventures 2474 +erotic 2475 +25 2476 +roger 2477 +suffers 2478 +blow 2479 +weekend 2480 +sisters 2481 +granted 2482 +mainstream 2483 +latest 2484 +weeks 2485 +prime 2486 +crash 2487 +cant 2488 +professor 2489 +experiences 2490 +speech 2491 +print 2492 +lesbian 2493 +harsh 2494 +deadly 2495 +veteran 2496 +mistakes 2497 +edward 2498 +routine 2499 +whoever 2500 +notch 2501 +uninteresting 2502 +realizes 2503 +invisible 2504 +combined 2505 +sympathy 2506 +accidentally 2507 +kim 2508 +twisted 2509 +brave 2510 +colors 2511 +dollars 2512 +security 2513 +draw 2514 +dogs 2515 +nude 2516 +rain 2517 +universe 2518 +struggling 2519 +dozen 2520 +teens 2521 +convince 2522 +guilty 2523 +path 2524 +appreciated 2525 +atrocious 2526 +mountain 2527 +treasure 2528 +walked 2529 +columbo 2530 +irish 2531 +frightening 2532 +would've 2533 +committed 2534 +aliens 2535 +technically 2536 +recognize 2537 +cowboy 2538 +blah 2539 +birth 2540 +enter 2541 +gritty 2542 +enemy 2543 +aka 2544 +spy 2545 +changing 2546 +magical 2547 +anderson 2548 +princess 2549 +department 2550 +gas 2551 +occasional 2552 +friday 2553 +sword 2554 +directly 2555 +false 2556 +massive 2557 +surface 2558 +narration 2559 +legendary 2560 +featured 2561 +victoria 2562 +anger 2563 +offered 2564 +paint 2565 +performed 2566 +moore 2567 +explains 2568 +abuse 2569 +suspenseful 2570 +vietnam 2571 +kinds 2572 +terror 2573 +experienced 2574 +friendly 2575 +subtitles 2576 +reputation 2577 +crying 2578 +hong 2579 +sorts 2580 +passing 2581 +junk 2582 +beach 2583 +multiple 2584 +forest 2585 +stolen 2586 +everywhere 2587 +figures 2588 +forth 2589 +statement 2590 +exact 2591 +powell 2592 +variety 2593 +required 2594 +clark 2595 +reveal 2596 +donald 2597 +regret 2598 +conversation 2599 +prior 2600 +darkness 2601 +remotely 2602 +execution 2603 +theory 2604 +trapped 2605 +proud 2606 +belief 2607 +urban 2608 +russell 2609 +lonely 2610 +placed 2611 +downright 2612 +wilson 2613 +san 2614 +fictional 2615 +melodrama 2616 +spends 2617 +insight 2618 +court 2619 +effectively 2620 +listening 2621 +grave 2622 +express 2623 +demons 2624 +crude 2625 +figured 2626 +bothered 2627 +abandoned 2628 +scares 2629 +network 2630 +unconvincing 2631 +jobs 2632 +hired 2633 +revolution 2634 +favorites 2635 +jon 2636 +wear 2637 +minds 2638 +metal 2639 +worthwhile 2640 +emma 2641 +california 2642 +dean 2643 +buying 2644 +blockbuster 2645 +lifetime 2646 +bus 2647 +paying 2648 +pulls 2649 +account 2650 +angle 2651 +happiness 2652 +von 2653 +blown 2654 +afternoon 2655 +imagery 2656 +rights 2657 +driver 2658 +alright 2659 +rolling 2660 +matrix 2661 +mexican 2662 +productions 2663 +amazed 2664 +idiot 2665 +rings 2666 +cultural 2667 +status 2668 +delivery 2669 +thankfully 2670 +grim 2671 +reveals 2672 +rule 2673 +stayed 2674 +handed 2675 +alice 2676 +stays 2677 +scenario 2678 +focuses 2679 +ha 2680 +significant 2681 +quest 2682 +rough 2683 +starred 2684 +examples 2685 +julia 2686 +jungle 2687 +sir 2688 +indie 2689 +lights 2690 +mere 2691 +views 2692 +murphy 2693 +shadow 2694 +sarah 2695 +bore 2696 +con 2697 +teeth 2698 +heavily 2699 +mature 2700 +device 2701 +table 2702 +skill 2703 +interview 2704 +caine 2705 +tight 2706 +necessarily 2707 +he'd 2708 +ron 2709 +sunday 2710 +clichéd 2711 +suffer 2712 +mexico 2713 +china 2714 +achieve 2715 +spite 2716 +understood 2717 +format 2718 +artists 2719 +position 2720 +initially 2721 +closing 2722 +campy 2723 +desperately 2724 +bound 2725 +fabulous 2726 +dress 2727 +sensitive 2728 +mgm 2729 +destroyed 2730 +hip 2731 +complicated 2732 +burns 2733 +demon 2734 +summary 2735 +seek 2736 +faithful 2737 +forgot 2738 +sun 2739 +decades 2740 +breath 2741 +gross 2742 +pitt 2743 +bourne 2744 +ghosts 2745 +titanic 2746 +cruel 2747 +murderer 2748 +stereotypical 2749 +deeper 2750 +lisa 2751 +facial 2752 +renting 2753 +ignore 2754 +pregnant 2755 +league 2756 +answers 2757 +racist 2758 +un 2759 +helping 2760 +ludicrous 2761 +beloved 2762 +flashback 2763 +slapstick 2764 +sleeping 2765 +17 2766 +dude 2767 +cell 2768 +musicals 2769 +fourth 2770 +wing 2771 +intellectual 2772 +beast 2773 +sounded 2774 +settings 2775 +environment 2776 +suck 2777 +critical 2778 +drinking 2779 +nazi 2780 +reminiscent 2781 +brad 2782 +calling 2783 +lugosi 2784 +dragon 2785 +description 2786 +susan 2787 +prefer 2788 +amazingly 2789 +task 2790 +mildly 2791 +pacino 2792 +disbelief 2793 +encounter 2794 +regarding 2795 +larry 2796 +inept 2797 +greater 2798 +learning 2799 +arms 2800 +dennis 2801 +extraordinary 2802 +turkey 2803 +storytelling 2804 +funnier 2805 +julie 2806 +halfway 2807 +ain't 2808 +expert 2809 +base 2810 +criticism 2811 +quirky 2812 +father's 2813 +leslie 2814 +warned 2815 +cabin 2816 +flight 2817 +titles 2818 +criminals 2819 +johnson 2820 +raw 2821 +praise 2822 +depiction 2823 +screening 2824 +throwing 2825 +extent 2826 +expression 2827 +kiss 2828 +jail 2829 +studios 2830 +freeman 2831 +truck 2832 +convey 2833 +originality 2834 +chan 2835 +entertain 2836 +choices 2837 +spoof 2838 +notorious 2839 +tree 2840 +raised 2841 +touched 2842 +children's 2843 +rachel 2844 +punch 2845 +experiment 2846 +daughters 2847 +prepared 2848 +comical 2849 +spoken 2850 +people's 2851 +timing 2852 +india 2853 +headed 2854 +purely 2855 +could've 2856 +basis 2857 +hoffman 2858 +bollywood 2859 +chilling 2860 +michelle 2861 +underground 2862 +dollar 2863 +via 2864 +picks 2865 +lie 2866 +inspiration 2867 +novels 2868 +wave 2869 +elizabeth 2870 +introduction 2871 +weapons 2872 +trick 2873 +lazy 2874 +jessica 2875 +graphics 2876 +breathtaking 2877 +notable 2878 +stomach 2879 +succeeds 2880 +term 2881 +crafted 2882 +join 2883 +throws 2884 +handle 2885 +strangely 2886 +properly 2887 +toy 2888 +nowadays 2889 +christ 2890 +sidney 2891 +reference 2892 +adding 2893 +claire 2894 +serve 2895 +ratings 2896 +locked 2897 +honor 2898 +wears 2899 +sitcom 2900 +ted 2901 +authentic 2902 +foster 2903 +regard 2904 +everyday 2905 +causes 2906 +maria 2907 +provoking 2908 +charge 2909 +protect 2910 +lesser 2911 +hitchcock 2912 +caring 2913 +mouse 2914 +mirror 2915 +bat 2916 +fallen 2917 +carrying 2918 +bitter 2919 +jewish 2920 +established 2921 +pet 2922 +amongst 2923 +east 2924 +shut 2925 +guard 2926 +midnight 2927 +sleazy 2928 +southern 2929 +determined 2930 +ned 2931 +challenge 2932 +daily 2933 +obnoxious 2934 +nonetheless 2935 +cases 2936 +carried 2937 +carries 2938 +wins 2939 +alas 2940 +remote 2941 +embarrassed 2942 +gruesome 2943 +hole 2944 +2006 2945 +lane 2946 +attempting 2947 +westerns 2948 +escapes 2949 +sinister 2950 +confusion 2951 +nation 2952 +tales 2953 +ironic 2954 +tradition 2955 +interpretation 2956 +arrives 2957 +busy 2958 +replaced 2959 +risk 2960 +enjoying 2961 +sold 2962 +essential 2963 +needless 2964 +aunt 2965 +hardy 2966 +burt 2967 +goofy 2968 +mass 2969 +obsession 2970 +minded 2971 +balance 2972 +flow 2973 +clips 2974 +existent 2975 +successfully 2976 +legs 2977 +presentation 2978 +screenwriter 2979 +jumps 2980 +exists 2981 +attacked 2982 +blair 2983 +laid 2984 +mentally 2985 +bbc 2986 +seeking 2987 +raise 2988 +topic 2989 +oddly 2990 +warner 2991 +inspector 2992 +horrific 2993 +fortunately 2994 +shape 2995 +marvelous 2996 +usa 2997 +intentions 2998 +buck 2999 +retarded 3000 +madness 3001 +stupidity 3002 +stops 3003 +text 3004 +stylish 3005 +stanley 3006 +che 3007 +rival 3008 +served 3009 +workers 3010 +maker 3011 +sides 3012 +ashamed 3013 +shower 3014 +packed 3015 +comedian 3016 +thrilling 3017 +wwii 3018 +interviews 3019 +nine 3020 +laura 3021 +frequently 3022 +upper 3023 +mob 3024 +mansion 3025 +bridge 3026 +remind 3027 +tongue 3028 +navy 3029 +wanna 3030 +contain 3031 +albeit 3032 +intensity 3033 +attacks 3034 +vacation 3035 +thief 3036 +delight 3037 +manager 3038 +chair 3039 +sum 3040 +hence 3041 +80 3042 +cheese 3043 +drives 3044 +2001 3045 +expressions 3046 +struggles 3047 +flawed 3048 +poignant 3049 +angels 3050 +personalities 3051 +rogers 3052 +riding 3053 +revolves 3054 +refuses 3055 +adapted 3056 +opened 3057 +greatly 3058 +credibility 3059 +philip 3060 +cooper 3061 +glass 3062 +pitch 3063 +tracy 3064 +1950s 3065 +jay 3066 +torn 3067 +dinner 3068 +bette 3069 +18 3070 +cynical 3071 +upset 3072 +pool 3073 +sin 3074 +tour 3075 +2000 3076 +internet 3077 +suspects 3078 +advantage 3079 +lessons 3080 +warn 3081 +lion 3082 +overcome 3083 +credible 3084 +wishes 3085 +thousands 3086 +spin 3087 +miller 3088 +racism 3089 +90's 3090 +mindless 3091 +wealthy 3092 +innocence 3093 +tense 3094 +broke 3095 +bugs 3096 +happily 3097 +catholic 3098 +guessing 3099 +trial 3100 +lucy 3101 +hood 3102 +hundreds 3103 +trite 3104 +physically 3105 +thrillers 3106 +cook 3107 +fish 3108 +alike 3109 +dubbing 3110 +fbi 3111 +crisis 3112 +per 3113 +pride 3114 +succeed 3115 +controversial 3116 +suffered 3117 +reed 3118 +bag 3119 +technique 3120 +wasting 3121 +dislike 3122 +medical 3123 +sexuality 3124 +countries 3125 +perform 3126 +patient 3127 +stranger 3128 +enjoyment 3129 +corner 3130 +arm 3131 +glimpse 3132 +gripping 3133 +reunion 3134 +franchise 3135 +holmes 3136 +ensemble 3137 +separate 3138 +hundred 3139 +lincoln 3140 +60's 3141 +sings 3142 +noble 3143 +shines 3144 +whereas 3145 +tied 3146 +ourselves 3147 +uncomfortable 3148 +infamous 3149 +neat 3150 +atmospheric 3151 +millions 3152 +shorts 3153 +contact 3154 +card 3155 +hint 3156 +pack 3157 +courage 3158 +irony 3159 +exceptional 3160 +plastic 3161 +storm 3162 +drink 3163 +ralph 3164 +searching 3165 +oscars 3166 +scripts 3167 +connected 3168 +italy 3169 +proof 3170 +sandler 3171 +snow 3172 +lying 3173 +flash 3174 +nose 3175 +curse 3176 +helen 3177 +sentimental 3178 +mst3k 3179 +grey 3180 +aired 3181 +holiday 3182 +steps 3183 +hills 3184 +performers 3185 +letting 3186 +chasing 3187 +suggests 3188 +dancer 3189 +tune 3190 +meaningful 3191 +idiotic 3192 +knife 3193 +quote 3194 +weapon 3195 +plague 3196 +sons 3197 +entry 3198 +kurt 3199 +fortune 3200 +cameos 3201 +consists 3202 +perfection 3203 +lovable 3204 +hoped 3205 +troubled 3206 +thousand 3207 +hiding 3208 +develops 3209 +unforgettable 3210 +accepted 3211 +noted 3212 +portrait 3213 +dear 3214 +equal 3215 +bettie 3216 +assistant 3217 +stretch 3218 +woman's 3219 +saves 3220 +colorful 3221 +annoyed 3222 +larger 3223 +attraction 3224 +condition 3225 +miscast 3226 +chases 3227 +brooks 3228 +virgin 3229 +spots 3230 +basement 3231 +host 3232 +dialogs 3233 +shoots 3234 +gain 3235 +horses 3236 +guilt 3237 +protagonists 3238 +oil 3239 +terrifying 3240 +month 3241 +cousin 3242 +neighborhood 3243 +vincent 3244 +pg 3245 +belongs 3246 +stealing 3247 +16 3248 +nelson 3249 +worry 3250 +burning 3251 +concert 3252 +ad 3253 +zone 3254 +strip 3255 +appearing 3256 +worlds 3257 +object 3258 +split 3259 +repeat 3260 +hang 3261 +boredom 3262 +destruction 3263 +thirty 3264 +redemption 3265 +hunting 3266 +encounters 3267 +imaginative 3268 +expensive 3269 +eerie 3270 +cube 3271 +seagal 3272 +jake 3273 +pie 3274 +competent 3275 +homeless 3276 +concerns 3277 +andrew 3278 +flaw 3279 +closely 3280 +bo 3281 +ultra 3282 +factory 3283 +1st 3284 +multi 3285 +civil 3286 +dramas 3287 +gag 3288 +stunts 3289 +wake 3290 +guts 3291 +sends 3292 +60 3293 +sutherland 3294 +glory 3295 +knock 3296 +matthau 3297 +massacre 3298 +letter 3299 +elsewhere 3300 +achieved 3301 +dig 3302 +checking 3303 +widmark 3304 +hooked 3305 +complaint 3306 +neck 3307 +endearing 3308 +segments 3309 +shark 3310 +sullivan 3311 +rushed 3312 +virus 3313 +ripped 3314 +charisma 3315 +incoherent 3316 +dragged 3317 +beating 3318 +dentist 3319 +essence 3320 +bears 3321 +profound 3322 +library 3323 +weight 3324 +tear 3325 +crimes 3326 +arnold 3327 +dare 3328 +appearances 3329 +solve 3330 +trade 3331 +pat 3332 +24 3333 +stanwyck 3334 +colour 3335 +teach 3336 +dorothy 3337 +roberts 3338 +rocks 3339 +fest 3340 +spell 3341 +catherine 3342 +dealt 3343 +stan 3344 +fitting 3345 +hitting 3346 +striking 3347 +pro 3348 +2005 3349 +tribute 3350 +tricks 3351 +60s 3352 +battles 3353 +believing 3354 +briefly 3355 +countless 3356 +fashioned 3357 +loser 3358 +goal 3359 +gothic 3360 +noise 3361 +techniques 3362 +n 3363 +videos 3364 +health 3365 +thumbs 3366 +attempted 3367 +scientists 3368 +st 3369 +painting 3370 +baker 3371 +strikes 3372 +inspiring 3373 +huh 3374 +sexually 3375 +birthday 3376 +secretary 3377 +curtis 3378 +jeremy 3379 +covers 3380 +pointed 3381 +slight 3382 +specific 3383 +tea 3384 +hearts 3385 +unintentionally 3386 +denzel 3387 +horrendous 3388 +charismatic 3389 +silver 3390 +surrounded 3391 +surrounding 3392 +reactions 3393 +branagh 3394 +importance 3395 +rochester 3396 +admittedly 3397 +carefully 3398 +jerk 3399 +tons 3400 +hype 3401 +relevant 3402 +they'd 3403 +walls 3404 +stood 3405 +eyed 3406 +bible 3407 +corrupt 3408 +rush 3409 +stunt 3410 +revelation 3411 +smoking 3412 +magazine 3413 +lloyd 3414 +kicks 3415 +karloff 3416 +stronger 3417 +grows 3418 +mild 3419 +hamlet 3420 +represents 3421 +dawn 3422 +andrews 3423 +intention 3424 +easier 3425 +enters 3426 +spending 3427 +scooby 3428 +fired 3429 +killings 3430 +stated 3431 +chances 3432 +shall 3433 +brand 3434 +exercise 3435 +university 3436 +increasingly 3437 +row 3438 +disagree 3439 +cardboard 3440 +winter 3441 +comics 3442 +requires 3443 +dropped 3444 +associated 3445 +world's 3446 +chuck 3447 +iii 3448 +medium 3449 +bush 3450 +projects 3451 +bride 3452 +occurs 3453 +korean 3454 +inevitable 3455 +messages 3456 +brando 3457 +le 3458 +strike 3459 +poverty 3460 +forgive 3461 +performing 3462 +stiff 3463 +attached 3464 +drags 3465 +luckily 3466 +ian 3467 +identify 3468 +1970s 3469 +gift 3470 +bobby 3471 +acceptable 3472 +resolution 3473 +eva 3474 +typically 3475 +canada 3476 +guest 3477 +nuclear 3478 +elvis 3479 +toilet 3480 +strictly 3481 +vague 3482 +spike 3483 +contract 3484 +hire 3485 +1980s 3486 +thrills 3487 +selling 3488 +hudson 3489 +homage 3490 +lab 3491 +boll 3492 +mafia 3493 +depression 3494 +sophisticated 3495 +fifteen 3496 +disease 3497 +allowing 3498 +brilliance 3499 +investigation 3500 +continued 3501 +struck 3502 +insulting 3503 +worker 3504 +instantly 3505 +useless 3506 +breasts 3507 +barry 3508 +jesse 3509 +sally 3510 +afterwards 3511 +chaplin 3512 +britain 3513 +carter 3514 +executive 3515 +handful 3516 +importantly 3517 +godfather 3518 +estate 3519 +hanks 3520 +pleased 3521 +overlooked 3522 +evident 3523 +burn 3524 +gotta 3525 +wreck 3526 +nights 3527 +2002 3528 +beings 3529 +ego 3530 +kidnapped 3531 +presumably 3532 +competition 3533 +press 3534 +partly 3535 +digital 3536 +shining 3537 +commit 3538 +tremendous 3539 +raped 3540 +menacing 3541 +silence 3542 +talked 3543 +derek 3544 +worthless 3545 +jamie 3546 +realise 3547 +ambitious 3548 +meat 3549 +wondered 3550 +photographed 3551 +sacrifice 3552 +arrested 3553 +buried 3554 +burton 3555 +threatening 3556 +smooth 3557 +aforementioned 3558 +superbly 3559 +boxing 3560 +kane 3561 +flawless 3562 +regardless 3563 +fears 3564 +creation 3565 +shy 3566 +heat 3567 +highlights 3568 +savage 3569 +persona 3570 +frustrated 3571 +drivel 3572 +conspiracy 3573 +individuals 3574 +wonders 3575 +listed 3576 +appalling 3577 +doc 3578 +'s 3579 +spiritual 3580 +pushed 3581 +returning 3582 +jumping 3583 +elvira 3584 +cox 3585 +corpse 3586 +size 3587 +characterization 3588 +bullets 3589 +walken 3590 +generous 3591 +string 3592 +rex 3593 +doors 3594 +pleasantly 3595 +bucks 3596 +relative 3597 +45 3598 +outrageous 3599 +kudos 3600 +planning 3601 +ticket 3602 +achievement 3603 +accomplished 3604 +miserably 3605 +monkey 3606 +beaten 3607 +neighbor 3608 +distant 3609 +fatal 3610 +repetitive 3611 +accused 3612 +picking 3613 +ironically 3614 +consequences 3615 +curiosity 3616 +union 3617 +admire 3618 +guide 3619 +splendid 3620 +prevent 3621 +reynolds 3622 +border 3623 +attracted 3624 +butt 3625 +clues 3626 +trap 3627 +notes 3628 +chain 3629 +opposed 3630 +watches 3631 +samurai 3632 +shortly 3633 +heston 3634 +twin 3635 +cole 3636 +glover 3637 +slightest 3638 +response 3639 +beer 3640 +territory 3641 +spooky 3642 +diamond 3643 +rap 3644 +horrors 3645 +20th 3646 +cup 3647 +dire 3648 +spirited 3649 +melodramatic 3650 +lucas 3651 +flynn 3652 +los 3653 +piano 3654 +push 3655 +revealing 3656 +spoiled 3657 +uninspired 3658 +ritter 3659 +convoluted 3660 +pulling 3661 +ken 3662 +root 3663 +they'll 3664 +streisand 3665 +motivation 3666 +directorial 3667 +installment 3668 +precious 3669 +titled 3670 +logical 3671 +documentaries 3672 +spring 3673 +lacked 3674 +suits 3675 +tall 3676 +subplot 3677 +mate 3678 +timeless 3679 +hatred 3680 +throat 3681 +blows 3682 +jealous 3683 +creators 3684 +blank 3685 +farce 3686 +spielberg 3687 +slap 3688 +ward 3689 +carol 3690 +subsequent 3691 +cared 3692 +mile 3693 +exaggerated 3694 +duke 3695 +morality 3696 +liberal 3697 +francisco 3698 +indians 3699 +psychotic 3700 +overdone 3701 +psychiatrist 3702 +astaire 3703 +intrigued 3704 +jet 3705 +blob 3706 +50's 3707 +conceived 3708 +fx 3709 +neil 3710 +aimed 3711 +remaining 3712 +doo 3713 +ignored 3714 +elderly 3715 +reasonably 3716 +mitchell 3717 +failing 3718 +sole 3719 +obscure 3720 +drunken 3721 +minimal 3722 +temple 3723 +progress 3724 +fancy 3725 +captivating 3726 +repeatedly 3727 +wes 3728 +tunes 3729 +shoes 3730 +grandmother 3731 +cia 3732 +nurse 3733 +marks 3734 +notably 3735 +emily 3736 +soviet 3737 +shirt 3738 +explore 3739 +smoke 3740 +souls 3741 +pushing 3742 +argument 3743 +distance 3744 +warrior 3745 +outcome 3746 +reduced 3747 +loosely 3748 +scientific 3749 +goldberg 3750 +gradually 3751 +bleak 3752 +timothy 3753 +manhattan 3754 +idiots 3755 +restaurant 3756 +scripted 3757 +misses 3758 +explicit 3759 +providing 3760 +elaborate 3761 +poster 3762 +lou 3763 +dignity 3764 +carpenter 3765 +norman 3766 +rid 3767 +turner 3768 +show's 3769 +davies 3770 +draws 3771 +discussion 3772 +exposed 3773 +mel 3774 +sticks 3775 +kenneth 3776 +definite 3777 +darker 3778 +laurel 3779 +intent 3780 +1950's 3781 +returned 3782 +superhero 3783 +sloppy 3784 +cried 3785 +worried 3786 +childish 3787 +shadows 3788 +craig 3789 +cruise 3790 +hysterical 3791 +imagined 3792 +reasonable 3793 +editor 3794 +ah 3795 +birds 3796 +horrid 3797 +areas 3798 +wicked 3799 +gentle 3800 +wannabe 3801 +alexander 3802 +thick 3803 +contrary 3804 +joey 3805 +empire 3806 +connect 3807 +discovery 3808 +unbearable 3809 +tortured 3810 +screams 3811 +fever 3812 +unbelievably 3813 +1930s 3814 +disc 3815 +99 3816 +load 3817 +heroic 3818 +absence 3819 +reached 3820 +ho 3821 +choreography 3822 +triumph 3823 +complain 3824 +annie 3825 +broad 3826 +improved 3827 +concerning 3828 +brazil 3829 +movements 3830 +2003 3831 +2004 3832 +dave 3833 +folk 3834 +eve 3835 +purple 3836 +commercials 3837 +futuristic 3838 +vicious 3839 +gray 3840 +freak 3841 +threat 3842 +cusack 3843 +extended 3844 +citizen 3845 +stole 3846 +anyways 3847 +glenn 3848 +existed 3849 +cheek 3850 +broadcast 3851 +photographer 3852 +translation 3853 +arrive 3854 +differences 3855 +displays 3856 +critic 3857 +slave 3858 +landscape 3859 +occurred 3860 +builds 3861 +drawing 3862 +incident 3863 +warren 3864 +burned 3865 +involvement 3866 +styles 3867 +bathroom 3868 +machines 3869 +narrator 3870 +antics 3871 +he'll 3872 +fisher 3873 +swear 3874 +australia 3875 +matthew 3876 +resembles 3877 +lily 3878 +overrated 3879 +currently 3880 +symbolism 3881 +ought 3882 +bare 3883 +audio 3884 +web 3885 +farm 3886 +contained 3887 +greek 3888 +affected 3889 +blend 3890 +q 3891 +recognized 3892 +duo 3893 +genres 3894 +population 3895 +carrie 3896 +ranks 3897 +demands 3898 +we'll 3899 +abc 3900 +prom 3901 +altogether 3902 +superficial 3903 +kitchen 3904 +pseudo 3905 +sunshine 3906 +sadness 3907 +secrets 3908 +bone 3909 +website 3910 +receive 3911 +popcorn 3912 +threw 3913 +craft 3914 +enjoys 3915 +occur 3916 +twelve 3917 +block 3918 +girl's 3919 +proceedings 3920 +dynamic 3921 +daring 3922 +swedish 3923 +argue 3924 +bite 3925 +wolf 3926 +adequate 3927 +investigate 3928 +harder 3929 +ruth 3930 +ridiculously 3931 +tap 3932 +dinosaurs 3933 +hugh 3934 +synopsis 3935 +beats 3936 +carrey 3937 +explosion 3938 +foul 3939 +merit 3940 +suited 3941 +holy 3942 +staged 3943 +journalist 3944 +pretend 3945 +composed 3946 +cagney 3947 +robots 3948 +giallo 3949 +aging 3950 +fay 3951 +sadistic 3952 +engaged 3953 +escaped 3954 +juvenile 3955 +rambo 3956 +ireland 3957 +conversations 3958 +thugs 3959 +modesty 3960 +selfish 3961 +margaret 3962 +dialogues 3963 +ease 3964 +cameras 3965 +tame 3966 +leg 3967 +rural 3968 +comfortable 3969 +nazis 3970 +clothing 3971 +innovative 3972 +terry 3973 +thrill 3974 +2nd 3975 +dancers 3976 +brosnan 3977 +explosions 3978 +bin 3979 +rage 3980 +overwhelming 3981 +jazz 3982 +vivid 3983 +coherent 3984 +bullet 3985 +odds 3986 +mountains 3987 +kidding 3988 +versus 3989 +lit 3990 +offering 3991 +mother's 3992 +trio 3993 +newspaper 3994 +pulp 3995 +ellen 3996 +dawson 3997 +bird 3998 +buddies 3999 +combat 4000 +dracula 4001 +lol 4002 +grab 4003 +orders 4004 +staff 4005 +nearby 4006 +cats 4007 +wealth 4008 +unpleasant 4009 +staying 4010 +devoted 4011 +centered 4012 +errors 4013 +disturbed 4014 +bell 4015 +atlantis 4016 +snake 4017 +felix 4018 +damage 4019 +clint 4020 +lust 4021 +groups 4022 +banned 4023 +blowing 4024 +fighter 4025 +removed 4026 +react 4027 +conventional 4028 +kapoor 4029 +intrigue 4030 +possessed 4031 +cringe 4032 +eyre 4033 +liking 4034 +implausible 4035 +philosophy 4036 +producing 4037 +abilities 4038 +seventies 4039 +bang 4040 +murderous 4041 +deliberately 4042 +gandhi 4043 +tommy 4044 +meaningless 4045 +subjects 4046 +lips 4047 +ingredients 4048 +mildred 4049 +perry 4050 +warming 4051 +causing 4052 +possibility 4053 +detailed 4054 +walker 4055 +garden 4056 +prostitute 4057 +nightmares 4058 +cameron 4059 +flop 4060 +influenced 4061 +spare 4062 +unwatchable 4063 +undoubtedly 4064 +celluloid 4065 +relies 4066 +resemblance 4067 +neo 4068 +parent 4069 +falk 4070 +uneven 4071 +unintentional 4072 +eccentric 4073 +mistaken 4074 +distracting 4075 +careers 4076 +yesterday 4077 +forbidden 4078 +panic 4079 +crack 4080 +brains 4081 +highest 4082 +occasion 4083 +signs 4084 +focusing 4085 +hollow 4086 +explored 4087 +aid 4088 +cary 4089 +scheme 4090 +shine 4091 +it'll 4092 +kirk 4093 +bedroom 4094 +satisfied 4095 +rat 4096 +passes 4097 +survival 4098 +coffee 4099 +furthermore 4100 +primary 4101 +succeeded 4102 +politically 4103 +pays 4104 +apes 4105 +stiller 4106 +dating 4107 +defeat 4108 +sport 4109 +catches 4110 +mickey 4111 +clown 4112 +roman 4113 +discuss 4114 +karen 4115 +clumsy 4116 +chaos 4117 +financial 4118 +official 4119 +trees 4120 +explaining 4121 +models 4122 +spirits 4123 +carl 4124 +jeffrey 4125 +duty 4126 +whale 4127 +funeral 4128 +secondly 4129 +sentence 4130 +2007 4131 +classes 4132 +sidekick 4133 +tracks 4134 +props 4135 +travels 4136 +flies 4137 +remarkably 4138 +smaller 4139 +wallace 4140 +awake 4141 +1996 4142 +brady 4143 +blatant 4144 +decisions 4145 +afford 4146 +notion 4147 +recorded 4148 +glorious 4149 +enterprise 4150 +maggie 4151 +consistently 4152 +toys 4153 +offended 4154 +officers 4155 +danes 4156 +backdrop 4157 +beneath 4158 +masters 4159 +measure 4160 +endings 4161 +doomed 4162 +mysteries 4163 +lifestyle 4164 +houses 4165 +portion 4166 +primarily 4167 +satan 4168 +hates 4169 +devoid 4170 +impress 4171 +outer 4172 +generic 4173 +dutch 4174 +punk 4175 +lyrics 4176 +yellow 4177 +eastwood 4178 +exotic 4179 +represent 4180 +instant 4181 +desperation 4182 +mixture 4183 +settle 4184 +frustration 4185 +unfolds 4186 +goodness 4187 +wives 4188 +directs 4189 +fetched 4190 +ape 4191 +cheating 4192 +dozens 4193 +rebel 4194 +cuba 4195 +paulie 4196 +enormous 4197 +revolutionary 4198 +hints 4199 +shelf 4200 +brooklyn 4201 +florida 4202 +dances 4203 +motives 4204 +destiny 4205 +1999 4206 +donna 4207 +hardcore 4208 +mill 4209 +wrestling 4210 +subtlety 4211 +forty 4212 +describes 4213 +drops 4214 +blake 4215 +stinker 4216 +doll 4217 +painted 4218 +fond 4219 +linda 4220 +principal 4221 +rank 4222 +ideal 4223 +kennedy 4224 +hammer 4225 +montage 4226 +hollywood's 4227 +tie 4228 +disjointed 4229 +3rd 4230 +reaches 4231 +amy 4232 +immensely 4233 +ginger 4234 +judging 4235 +companion 4236 +communist 4237 +urge 4238 +winds 4239 +developing 4240 +trailers 4241 +cliff 4242 +lawrence 4243 +stellar 4244 +topless 4245 +circle 4246 +surviving 4247 +avoided 4248 +relations 4249 +bold 4250 +hideous 4251 +voight 4252 +closet 4253 +et 4254 +surfing 4255 +melting 4256 +soccer 4257 +edie 4258 +matches 4259 +backgrounds 4260 +planned 4261 +enemies 4262 +advance 4263 +bull 4264 +authority 4265 +crush 4266 +outfit 4267 +emphasis 4268 +method 4269 +terrorist 4270 +senseless 4271 +pig 4272 +uwe 4273 +simplistic 4274 +benefit 4275 +adorable 4276 +eighties 4277 +ruthless 4278 +godzilla 4279 +blew 4280 +countryside 4281 +specifically 4282 +wont 4283 +performer 4284 +hbo 4285 +traveling 4286 +todd 4287 +practice 4288 +diane 4289 +fix 4290 +faster 4291 +1980 4292 +commented 4293 +sh 4294 +loyal 4295 +saga 4296 +ties 4297 +disappear 4298 +awe 4299 +earned 4300 +buff 4301 +rick 4302 +loads 4303 +link 4304 +angeles 4305 +corruption 4306 +forms 4307 +menace 4308 +miserable 4309 +claimed 4310 +vast 4311 +coach 4312 +divorce 4313 +hal 4314 +gadget 4315 +chorus 4316 +limits 4317 +cure 4318 +introduces 4319 +cards 4320 +solo 4321 +blues 4322 +splatter 4323 +april 4324 +endure 4325 +riveting 4326 +dedicated 4327 +tender 4328 +winters 4329 +illogical 4330 +choreographed 4331 +disappeared 4332 +unsettling 4333 +waters 4334 +guessed 4335 +lemmon 4336 +involve 4337 +transformation 4338 +depressed 4339 +rooms 4340 +lasted 4341 +displayed 4342 +weakest 4343 +leonard 4344 +philosophical 4345 +racial 4346 +interaction 4347 +arrogant 4348 +tag 4349 +rocket 4350 +similarities 4351 +hurts 4352 +thoughtful 4353 +realizing 4354 +harvey 4355 +justify 4356 +hook 4357 +survivors 4358 +represented 4359 +pot 4360 +possibilities 4361 +wore 4362 +disappoint 4363 +voiced 4364 +kicked 4365 +abysmal 4366 +hamilton 4367 +buffs 4368 +safety 4369 +widow 4370 +ears 4371 +nomination 4372 +trashy 4373 +honesty 4374 +stereotype 4375 +severe 4376 +formulaic 4377 +moody 4378 +similarly 4379 +stress 4380 +pan 4381 +chased 4382 +isolated 4383 +blond 4384 +stinks 4385 +mario 4386 +passionate 4387 +finger 4388 +shirley 4389 +march 4390 +hank 4391 +improve 4392 +mann 4393 +understandable 4394 +characters' 4395 +considerable 4396 +scope 4397 +holly 4398 +diana 4399 +grasp 4400 +command 4401 +solely 4402 +'em 4403 +concern 4404 +treats 4405 +akshay 4406 +promised 4407 +colonel 4408 +jonathan 4409 +faults 4410 +helicopter 4411 +inventive 4412 +sounding 4413 +quotes 4414 +trained 4415 +switch 4416 +celebrity 4417 +tad 4418 +swimming 4419 +orson 4420 +education 4421 +aids 4422 +nail 4423 +judy 4424 +cg 4425 +user 4426 +nervous 4427 +nostalgic 4428 +daddy 4429 +alert 4430 +amanda 4431 +facing 4432 +comparing 4433 +unhappy 4434 +preview 4435 +report 4436 +bonus 4437 +purchase 4438 +chess 4439 +wet 4440 +lately 4441 +horrifying 4442 +agrees 4443 +thru 4444 +dolls 4445 +cinematographer 4446 +ignorant 4447 +species 4448 +seed 4449 +consistent 4450 +downhill 4451 +corporate 4452 +photos 4453 +confidence 4454 +letters 4455 +berlin 4456 +dinosaur 4457 +rotten 4458 +taught 4459 +fooled 4460 +laws 4461 +nicholson 4462 +namely 4463 +shake 4464 +waited 4465 +wished 4466 +embarrassment 4467 +everyone's 4468 +boot 4469 +pretending 4470 +reaching 4471 +someone's 4472 +transfer 4473 +sits 4474 +armed 4475 +del 4476 +dub 4477 +defend 4478 +hart 4479 +35 4480 +constructed 4481 +mall 4482 +poetic 4483 +motivations 4484 +inane 4485 +behave 4486 +tonight 4487 +staring 4488 +humble 4489 +snl 4490 +elephant 4491 +agents 4492 +oz 4493 +grandfather 4494 +writes 4495 +relation 4496 +hop 4497 +delivering 4498 +fonda 4499 +edgar 4500 +cave 4501 +artificial 4502 +grinch 4503 +sappy 4504 +prize 4505 +1972 4506 +useful 4507 +buildings 4508 +li 4509 +cake 4510 +eager 4511 +closest 4512 +suitable 4513 +raising 4514 +destroying 4515 +combine 4516 +beatty 4517 +pants 4518 +cleverly 4519 +ballet 4520 +convincingly 4521 +porno 4522 +1990 4523 +miike 4524 +affect 4525 +engage 4526 +cd 4527 +conservative 4528 +wound 4529 +arrived 4530 +stevens 4531 +alcoholic 4532 +valuable 4533 +ya 4534 +reads 4535 +scottish 4536 +elegant 4537 +vegas 4538 +chest 4539 +charlotte 4540 +climactic 4541 +tiresome 4542 +z 4543 +conflicts 4544 +babe 4545 +vengeance 4546 +square 4547 +bath 4548 +secretly 4549 +airport 4550 +campbell 4551 +kingdom 4552 +september 4553 +inferior 4554 +1968 4555 +latin 4556 +plant 4557 +button 4558 +museum 4559 +maintain 4560 +wrapped 4561 +kicking 4562 +cheated 4563 +global 4564 +robbery 4565 +virginia 4566 +wells 4567 +waves 4568 +stilted 4569 +blunt 4570 +lena 4571 +boom 4572 +access 4573 +raymond 4574 +1960s 4575 +catching 4576 +nicholas 4577 +yelling 4578 +scarecrow 4579 +beliefs 4580 +paranoia 4581 +christians 4582 +vice 4583 +jumped 4584 +lay 4585 +iron 4586 +steel 4587 +lowest 4588 +reflect 4589 +closed 4590 +mummy 4591 +transition 4592 +advertising 4593 +vulnerable 4594 +abusive 4595 +1970's 4596 +spoke 4597 +plight 4598 +mars 4599 +spread 4600 +adams 4601 +wizard 4602 +poetry 4603 +im 4604 +sandra 4605 +germans 4606 +pokemon 4607 +progresses 4608 +70 4609 +00 4610 +hung 4611 +questionable 4612 +remarks 4613 +airplane 4614 +centers 4615 +potentially 4616 +bottle 4617 +chicago 4618 +guarantee 4619 +couples 4620 +messed 4621 +catchy 4622 +slick 4623 +gangsters 4624 +misery 4625 +blade 4626 +designs 4627 +construction 4628 +ethan 4629 +desired 4630 +miracle 4631 +carradine 4632 +firstly 4633 +scores 4634 +wandering 4635 +greedy 4636 +recognition 4637 +understated 4638 +restored 4639 +complexity 4640 +madonna 4641 +attitudes 4642 +rendition 4643 +hunters 4644 +intentionally 4645 +experiments 4646 +ruby 4647 +alongside 4648 +vaguely 4649 +inappropriate 4650 +copies 4651 +operation 4652 +brutally 4653 +taxi 4654 +amounts 4655 +stooges 4656 +joined 4657 +pearl 4658 +demand 4659 +crocodile 4660 +depicts 4661 +purchased 4662 +acid 4663 +myers 4664 +exploration 4665 +advise 4666 +illegal 4667 +balls 4668 +king's 4669 +gundam 4670 +disney's 4671 +gender 4672 +lengthy 4673 +survived 4674 +hopper 4675 +niro 4676 +advanced 4677 +simplicity 4678 +bela 4679 +parallel 4680 +ocean 4681 +slaughter 4682 +rising 4683 +witnesses 4684 +chicks 4685 +streep 4686 +visible 4687 +nostalgia 4688 +arguably 4689 +careful 4690 +intimate 4691 +online 4692 +floating 4693 +rubber 4694 +june 4695 +illness 4696 +resources 4697 +khan 4698 +jaw 4699 +newly 4700 +witches 4701 +showcase 4702 +signed 4703 +opinions 4704 +dust 4705 +eaten 4706 +civilization 4707 +shelley 4708 +incomprehensible 4709 +invasion 4710 +lee's 4711 +monkeys 4712 +resort 4713 +literature 4714 +junior 4715 +likewise 4716 +homosexual 4717 +family's 4718 +viewings 4719 +sue 4720 +wisdom 4721 +matched 4722 +amitabh 4723 +edition 4724 +witnessed 4725 +visits 4726 +mistress 4727 +1983 4728 +demented 4729 +basketball 4730 +neighbors 4731 +macy 4732 +fascinated 4733 +dreary 4734 +suspicious 4735 +accompanied 4736 +worn 4737 +mail 4738 +challenging 4739 +doom 4740 +ensues 4741 +manipulative 4742 +robinson 4743 +classical 4744 +olivier 4745 +agreed 4746 +appreciation 4747 +franco 4748 +montana 4749 +troops 4750 +capturing 4751 +alternate 4752 +bands 4753 +twilight 4754 +ridden 4755 +responsibility 4756 +proceeds 4757 +chapter 4758 +jenny 4759 +prisoners 4760 +pops 4761 +analysis 4762 +subplots 4763 +lively 4764 +nuts 4765 +prisoner 4766 +incompetent 4767 +damon 4768 +sellers 4769 +mayor 4770 +rats 4771 +simpson 4772 +90s 4773 +persons 4774 +feed 4775 +descent 4776 +reel 4777 +bay 4778 +assault 4779 +losers 4780 +widely 4781 +rabbit 4782 +smiling 4783 +relatives 4784 +excessive 4785 +defined 4786 +satisfy 4787 +solution 4788 +legal 4789 +molly 4790 +arrival 4791 +overacting 4792 +equivalent 4793 +iran 4794 +pit 4795 +masterful 4796 +capital 4797 +richardson 4798 +compelled 4799 +plausible 4800 +stale 4801 +scrooge 4802 +cities 4803 +francis 4804 +enthusiasm 4805 +lone 4806 +parties 4807 +tomatoes 4808 +channels 4809 +hilariously 4810 +rocky 4811 +crucial 4812 +dropping 4813 +unit 4814 +waitress 4815 +domestic 4816 +attorney 4817 +bakshi 4818 +serving 4819 +wrap 4820 +jaws 4821 +historically 4822 +3d 4823 +defense 4824 +hello 4825 +greed 4826 +1973 4827 +priceless 4828 +sincere 4829 +warmth 4830 +paltrow 4831 +gerard 4832 +tends 4833 +god's 4834 +patients 4835 +creep 4836 +counter 4837 +dalton 4838 +kay 4839 +whats 4840 +louise 4841 +peoples 4842 +exceptionally 4843 +nyc 4844 +pal 4845 +seeks 4846 +terrorists 4847 +lumet 4848 +morris 4849 +ninja 4850 +randomly 4851 +frequent 4852 +despair 4853 +irrelevant 4854 +dressing 4855 +pursuit 4856 +prequel 4857 +creativity 4858 +imitation 4859 +bumbling 4860 +hyde 4861 +property 4862 +muslim 4863 +wishing 4864 +richards 4865 +bargain 4866 +50s 4867 +creator 4868 +calm 4869 +bacall 4870 +gabriel 4871 +mentioning 4872 +rangers 4873 +methods 4874 +earl 4875 +royal 4876 +butler 4877 +justin 4878 +psychic 4879 +chooses 4880 +belong 4881 +der 4882 +photo 4883 +polanski 4884 +mundane 4885 +specially 4886 +mighty 4887 +homer 4888 +ear 4889 +masterpieces 4890 +generated 4891 +leo 4892 +improvement 4893 +poem 4894 +ham 4895 +cliche 4896 +marty 4897 +caliber 4898 +mentions 4899 +minimum 4900 +showdown 4901 +borrowed 4902 +elm 4903 +icon 4904 +brenda 4905 +polished 4906 +1984 4907 +mechanical 4908 +overlook 4909 +loaded 4910 +map 4911 +recording 4912 +craven 4913 +tiger 4914 +roth 4915 +awfully 4916 +suffice 4917 +troubles 4918 +introduce 4919 +equipment 4920 +ashley 4921 +wendy 4922 +pamela 4923 +empathy 4924 +phantom 4925 +betty 4926 +resident 4927 +unreal 4928 +ruins 4929 +performs 4930 +promises 4931 +monk 4932 +iraq 4933 +hippie 4934 +purposes 4935 +marketing 4936 +angela 4937 +keith 4938 +sink 4939 +gifted 4940 +opportunities 4941 +garbo 4942 +assigned 4943 +feminist 4944 +household 4945 +wacky 4946 +alfred 4947 +absent 4948 +sneak 4949 +popularity 4950 +trail 4951 +inducing 4952 +moronic 4953 +wounded 4954 +receives 4955 +willis 4956 +unseen 4957 +stretched 4958 +fulci 4959 +unaware 4960 +dimension 4961 +dolph 4962 +definition 4963 +testament 4964 +educational 4965 +survivor 4966 +attend 4967 +clip 4968 +contest 4969 +petty 4970 +13th 4971 +christy 4972 +respected 4973 +resist 4974 +year's 4975 +album 4976 +expressed 4977 +randy 4978 +quit 4979 +phony 4980 +unoriginal 4981 +punishment 4982 +activities 4983 +suspend 4984 +rolled 4985 +eastern 4986 +1933 4987 +instinct 4988 +distinct 4989 +championship 4990 +tech 4991 +doubts 4992 +interests 4993 +exposure 4994 +travesty 4995 +israel 4996 +sixties 4997 +pink 4998 +orange 4999 +resulting 5000 +spain 5001 +bergman 5002 +1987 5003 +verhoeven 5004 +distribution 5005 +laughably 5006 +depicting 5007 +kissing 5008 +tooth 5009 +shed 5010 +kubrick 5011 +pin 5012 +nonsensical 5013 +roots 5014 +assumed 5015 +swim 5016 +whoopi 5017 +domino 5018 +heights 5019 +spock 5020 +inevitably 5021 +abraham 5022 +stunned 5023 +businessman 5024 +correctly 5025 +deceased 5026 +buffalo 5027 +wholly 5028 +underlying 5029 +dud 5030 +othello 5031 +unpredictable 5032 +package 5033 +hopeless 5034 +teaching 5035 +valley 5036 +uplifting 5037 +peters 5038 +integrity 5039 +1993 5040 +biography 5041 +yard 5042 +brutality 5043 +america's 5044 +trademark 5045 +retired 5046 +shaw 5047 +reflection 5048 +maniac 5049 +– 5050 +meryl 5051 +accuracy 5052 +sid 5053 +compassion 5054 +dreck 5055 +2008 5056 +edgy 5057 +greatness 5058 +assassin 5059 +greg 5060 +palace 5061 +suggested 5062 +patience 5063 +landscapes 5064 +1971 5065 +mankind 5066 +supported 5067 +merits 5068 +directions 5069 +fed 5070 +romero 5071 +spider 5072 +mtv 5073 +metaphor 5074 +masses 5075 +puppet 5076 +seldom 5077 +wife's 5078 +loyalty 5079 +deaf 5080 +grayson 5081 +strangers 5082 +3000 5083 +passable 5084 +checked 5085 +connery 5086 +confess 5087 +shaky 5088 +drake 5089 +eugene 5090 +significance 5091 +pierce 5092 +unfair 5093 +maid 5094 +indulgent 5095 +comfort 5096 +orleans 5097 +willie 5098 +glasses 5099 +pressure 5100 +alec 5101 +composer 5102 +marion 5103 +nicole 5104 +tribe 5105 +fought 5106 +technicolor 5107 +watson 5108 +dee 5109 +emperor 5110 +adaptations 5111 +romp 5112 +peak 5113 +conditions 5114 +grabs 5115 +exchange 5116 +fury 5117 +immediate 5118 +women's 5119 +timon 5120 +omen 5121 +generations 5122 +barrymore 5123 +resemble 5124 +1995 5125 +1997 5126 +confrontation 5127 +landing 5128 +frustrating 5129 +demise 5130 +spacey 5131 +lackluster 5132 +disliked 5133 +kyle 5134 +y 5135 +victory 5136 +wretched 5137 +… 5138 +farrell 5139 +we'd 5140 +respectively 5141 +crazed 5142 +din 5143 +expedition 5144 +chicken 5145 +cannibal 5146 +conscious 5147 +experimental 5148 +astonishing 5149 +inability 5150 +examination 5151 +wilderness 5152 +tube 5153 +blast 5154 +nerd 5155 +legacy 5156 +companies 5157 +subjected 5158 +ships 5159 +rises 5160 +invented 5161 +stuart 5162 +ambiguous 5163 +grief 5164 +rave 5165 +cracking 5166 +unexpectedly 5167 +scotland 5168 +stargate 5169 +milk 5170 +singers 5171 +darren 5172 +billed 5173 +tripe 5174 +ordered 5175 +furious 5176 +flair 5177 +griffith 5178 +refused 5179 +fascination 5180 +tastes 5181 +owen 5182 +frightened 5183 +amused 5184 +masks 5185 +females 5186 +graham 5187 +rates 5188 +simultaneously 5189 +senses 5190 +walsh 5191 +marc 5192 +simmons 5193 +shanghai 5194 +premiere 5195 +remained 5196 +warriors 5197 +1936 5198 +josh 5199 +antwone 5200 +difficulties 5201 +shoulders 5202 +femme 5203 +alternative 5204 +sentiment 5205 +relax 5206 +ollie 5207 +leon 5208 +rooney 5209 +objective 5210 +deranged 5211 +alcohol 5212 +austin 5213 +sissy 5214 +tank 5215 +dysfunctional 5216 +vulgar 5217 +stumbled 5218 +desires 5219 +replace 5220 +dixon 5221 +claus 5222 +joel 5223 +hears 5224 +coast 5225 +poison 5226 +addicted 5227 +slice 5228 +lundgren 5229 +parade 5230 +gather 5231 +appropriately 5232 +abused 5233 +cream 5234 +challenged 5235 +awhile 5236 +tacky 5237 +interactions 5238 +function 5239 +pun 5240 +bud 5241 +filling 5242 +primitive 5243 +fishing 5244 +raises 5245 +infected 5246 +musicians 5247 +precisely 5248 +caricatures 5249 +karl 5250 +underneath 5251 +ross 5252 +alicia 5253 +prey 5254 +fingers 5255 +nephew 5256 +crystal 5257 +skull 5258 +remakes 5259 +favour 5260 +wildly 5261 +phil 5262 +phrase 5263 +julian 5264 +sopranos 5265 +complaints 5266 +presenting 5267 +noises 5268 +19th 5269 +twins 5270 +les 5271 +ramones 5272 +lands 5273 +joins 5274 +wakes 5275 +require 5276 +fifty 5277 +items 5278 +frankenstein 5279 +nathan 5280 +christianity 5281 +reid 5282 +accomplish 5283 +22 5284 +dana 5285 +wang 5286 +breed 5287 +millionaire 5288 +sums 5289 +knocked 5290 +teaches 5291 +literary 5292 +loneliness 5293 +fiancé 5294 +complaining 5295 +silliness 5296 +sharon 5297 +celebration 5298 +gentleman 5299 +ustinov 5300 +husband's 5301 +exposition 5302 +choppy 5303 +altman 5304 +minus 5305 +amusement 5306 +sugar 5307 +husbands 5308 +framed 5309 +other's 5310 +andre 5311 +unlikable 5312 +sunny 5313 +roommate 5314 +stark 5315 +absurdity 5316 +rifle 5317 +electric 5318 +posters 5319 +aspiring 5320 +conscience 5321 +fields 5322 +hackneyed 5323 +downey 5324 +buster 5325 +edit 5326 +straightforward 5327 +misleading 5328 +carell 5329 +murdering 5330 +credited 5331 +sung 5332 +releases 5333 +muddled 5334 +raines 5335 +coincidence 5336 +unfold 5337 +rude 5338 +charged 5339 +weakness 5340 +quietly 5341 +pitiful 5342 +marshall 5343 +objects 5344 +shared 5345 +inexplicably 5346 +automatically 5347 +heartfelt 5348 +agenda 5349 +dresses 5350 +trend 5351 +acclaimed 5352 +blacks 5353 +murray 5354 +beverly 5355 +asylum 5356 +belushi 5357 +en 5358 +moreover 5359 +shoddy 5360 +bernard 5361 +teachers 5362 +devices 5363 +cattle 5364 +preston 5365 +dont 5366 +grotesque 5367 +visited 5368 +discovering 5369 +roof 5370 +spark 5371 +realised 5372 +handling 5373 +adopted 5374 +bread 5375 +haired 5376 +ethnic 5377 +encourage 5378 +lock 5379 +conviction 5380 +imaginable 5381 +fog 5382 +crawford 5383 +firm 5384 +servant 5385 +invites 5386 +dirt 5387 +cancer 5388 +fantasies 5389 +rely 5390 +biased 5391 +occasions 5392 +dose 5393 +industrial 5394 +harm 5395 +hungry 5396 +vance 5397 +kansas 5398 +active 5399 +preposterous 5400 +profanity 5401 +positively 5402 +prepare 5403 +ladder 5404 +sketch 5405 +alison 5406 +controlled 5407 +squad 5408 +outfits 5409 +deniro 5410 +canyon 5411 +babies 5412 +frankie 5413 +referred 5414 +kumar 5415 +regarded 5416 +designer 5417 +1988 5418 +paradise 5419 +comedians 5420 +russia 5421 +fido 5422 +provocative 5423 +behaviour 5424 +region 5425 +1930's 5426 +baldwin 5427 +laurence 5428 +translated 5429 +tracking 5430 +clock 5431 +1939 5432 +chills 5433 +hawke 5434 +cue 5435 +heist 5436 +citizens 5437 +da 5438 +1978 5439 +mode 5440 +hk 5441 +counts 5442 +riot 5443 +uncut 5444 +musician 5445 +accepts 5446 +shoulder 5447 +heartbreaking 5448 +secondary 5449 +option 5450 +75 5451 +roller 5452 +1980's 5453 +fathers 5454 +mclaglen 5455 +hopelessly 5456 +tasteless 5457 +bye 5458 +challenges 5459 +bitch 5460 +additional 5461 +backs 5462 +should've 5463 +swing 5464 +betrayal 5465 +labor 5466 +lush 5467 +morbid 5468 +abrupt 5469 +gambling 5470 +historic 5471 +iv 5472 +insurance 5473 +1986 5474 +fade 5475 +screens 5476 +bike 5477 +damme 5478 +pages 5479 +nut 5480 +admirable 5481 +rejected 5482 +skits 5483 +lip 5484 +ignorance 5485 +chainsaw 5486 +cassidy 5487 +suspension 5488 +respective 5489 +nod 5490 +chuckle 5491 +recommendation 5492 +guitar 5493 +youngest 5494 +reign 5495 +1970 5496 +biko 5497 +severely 5498 +affection 5499 +coaster 5500 +visiting 5501 +kid's 5502 +darn 5503 +refer 5504 +boxer 5505 +naughty 5506 +macarthur 5507 +deserted 5508 +amazon 5509 +paramount 5510 +files 5511 +corpses 5512 +realm 5513 +nemesis 5514 +1979 5515 +sabrina 5516 +address 5517 +beware 5518 +shares 5519 +tomorrow 5520 +prejudice 5521 +el 5522 +guaranteed 5523 +wwe 5524 +sooner 5525 +reluctant 5526 +1989 5527 +invited 5528 +aim 5529 +dickens 5530 +evidently 5531 +lindsay 5532 +hyped 5533 +penny 5534 +praised 5535 +jews 5536 +sympathize 5537 +barrel 5538 +disappears 5539 +guests 5540 +anticipation 5541 +conventions 5542 +outs 5543 +tail 5544 +deleted 5545 +freaks 5546 +rome 5547 +indication 5548 +bunny 5549 +actor's 5550 +19 5551 +fist 5552 +mayhem 5553 +1969 5554 +policeman 5555 +cannon 5556 +thread 5557 +basinger 5558 +bridget 5559 +selection 5560 +palma 5561 +inconsistent 5562 +saint 5563 +stopping 5564 +gut 5565 +burst 5566 +visions 5567 +angst 5568 +daughter's 5569 +beside 5570 +reader 5571 +sentinel 5572 +nails 5573 +promote 5574 +weaknesses 5575 +heading 5576 +www 5577 +venture 5578 +malone 5579 +misguided 5580 +1960's 5581 +muppet 5582 +uh 5583 +drove 5584 +overlong 5585 +gal 5586 +cope 5587 +mccoy 5588 +threatens 5589 +iconic 5590 +rita 5591 +stages 5592 +underworld 5593 +adolescent 5594 +tip 5595 +previews 5596 +depending 5597 +hammy 5598 +behold 5599 +steady 5600 +circus 5601 +filler 5602 +conveys 5603 +glowing 5604 +vader 5605 +shades 5606 +acceptance 5607 +psychology 5608 +bent 5609 +banal 5610 +receiving 5611 +palance 5612 +reflects 5613 +cruelty 5614 +guy's 5615 +tyler 5616 +insipid 5617 +posted 5618 +hack 5619 +curly 5620 +sassy 5621 +nicolas 5622 +harmless 5623 +morally 5624 +affairs 5625 +macho 5626 +understands 5627 +fluff 5628 +demonstrates 5629 +exceptions 5630 +bow 5631 +investigating 5632 +widescreen 5633 +30's 5634 +remade 5635 +studies 5636 +records 5637 +bros 5638 +unexplained 5639 +sirk 5640 +oldest 5641 +firing 5642 +vein 5643 +explores 5644 +completed 5645 +eternal 5646 +marvel 5647 +preachy 5648 +triple 5649 +schlock 5650 +min 5651 +employed 5652 +campaign 5653 +difficulty 5654 +strongest 5655 +gregory 5656 +grainy 5657 +popping 5658 +disguise 5659 +filth 5660 +dates 5661 +obligatory 5662 +robbins 5663 +terrified 5664 +portrayals 5665 +commander 5666 +hokey 5667 +emerges 5668 +confident 5669 +connections 5670 +lifted 5671 +artsy 5672 +height 5673 +entitled 5674 +outing 5675 +rukh 5676 +hopkins 5677 +pounds 5678 +sending 5679 +hapless 5680 +physics 5681 +phenomenon 5682 +assuming 5683 +unrelated 5684 +kitty 5685 +repeating 5686 +stores 5687 +attract 5688 +fifties 5689 +assured 5690 +clan 5691 +insists 5692 +interestingly 5693 +patricia 5694 +mentality 5695 +knight 5696 +1981 5697 +bug 5698 +paxton 5699 +pole 5700 +hughes 5701 +communicate 5702 +sox 5703 +rhythm 5704 +nolan 5705 +bitten 5706 +despicable 5707 +slimy 5708 +predict 5709 +recognizable 5710 +rounded 5711 +shakespeare's 5712 +gate 5713 +1945 5714 +recycled 5715 +conclude 5716 +casual 5717 +disgusted 5718 +comparisons 5719 +zombi 5720 +couch 5721 +offs 5722 +vital 5723 +representation 5724 +rod 5725 +duck 5726 +martha 5727 +danish 5728 +yawn 5729 +studying 5730 +1976 5731 +clarke 5732 +woo 5733 +route 5734 +prominent 5735 +tarantino 5736 +legends 5737 +paintings 5738 +suitably 5739 +someday 5740 +snakes 5741 +absorbed 5742 +stairs 5743 +redeem 5744 +gear 5745 +shortcomings 5746 +agency 5747 +tempted 5748 +rapist 5749 +inexplicable 5750 +locals 5751 +http 5752 +clueless 5753 +pleasing 5754 +vibrant 5755 +independence 5756 +marries 5757 +clad 5758 +charms 5759 +rendered 5760 +heartwarming 5761 +melody 5762 +shouting 5763 +wig 5764 +defeated 5765 +friend's 5766 +stack 5767 +lois 5768 +novak 5769 +coup 5770 +globe 5771 +soup 5772 +claustrophobic 5773 +eats 5774 +flashy 5775 +trivia 5776 +spinal 5777 +thompson 5778 +considerably 5779 +forcing 5780 +befriends 5781 +grudge 5782 +chavez 5783 +net 5784 +shopping 5785 +gems 5786 +claiming 5787 +foxx 5788 +muppets 5789 +discussing 5790 +boston 5791 +ingenious 5792 +flowers 5793 +harold 5794 +feeding 5795 +eternity 5796 +norm 5797 +sharing 5798 +meg 5799 +quinn 5800 +election 5801 +camcorder 5802 +limit 5803 +genie 5804 +daniels 5805 +quaid 5806 +bacon 5807 +runner 5808 +tierney 5809 +champion 5810 +stallone 5811 +minister 5812 +publicity 5813 +static 5814 +springer 5815 +info 5816 +screw 5817 +inhabitants 5818 +'70s 5819 +renaissance 5820 +carla 5821 +screwed 5822 +delicate 5823 +marlon 5824 +weather 5825 +deserving 5826 +incidentally 5827 +depends 5828 +winchester 5829 +boyle 5830 +gina 5831 +immature 5832 +lift 5833 +wings 5834 +partners 5835 +rope 5836 +ace 5837 +phillips 5838 +kathryn 5839 +elite 5840 +pete 5841 +brother's 5842 +glamorous 5843 +transformed 5844 +blatantly 5845 +symbolic 5846 +traffic 5847 +belt 5848 +strings 5849 +excess 5850 +stalker 5851 +smiles 5852 +ton 5853 +politician 5854 +keen 5855 +esther 5856 +ambition 5857 +surgery 5858 +ants 5859 +audrey 5860 +housewife 5861 +ish 5862 +lasting 5863 +allen's 5864 +dvds 5865 +schools 5866 +concepts 5867 +hilarity 5868 +newman 5869 +shaking 5870 +28 5871 +programs 5872 +frames 5873 +coupled 5874 +cheer 5875 +disorder 5876 +salt 5877 +beatles 5878 +fuller 5879 +shorter 5880 +voted 5881 +toronto 5882 +raj 5883 +1940 5884 +exploring 5885 +debate 5886 +yeti 5887 +layers 5888 +fontaine 5889 +backwards 5890 +continually 5891 +feat 5892 +georges 5893 +organized 5894 +destined 5895 +bombs 5896 +differently 5897 +nope 5898 +bend 5899 +towers 5900 +mothers 5901 +partially 5902 +outdated 5903 +punches 5904 +stumbles 5905 +bully 5906 +threatened 5907 +thrilled 5908 +leigh 5909 +charlton 5910 +wax 5911 +bondage 5912 +kolchak 5913 +spree 5914 +assassination 5915 +doctors 5916 +remove 5917 +claude 5918 +europa 5919 +wire 5920 +leather 5921 +messy 5922 +item 5923 +institution 5924 +departure 5925 +centre 5926 +else's 5927 +detectives 5928 +triangle 5929 +lifeless 5930 +handles 5931 +hides 5932 +wanders 5933 +dudley 5934 +accurately 5935 +duration 5936 +hum 5937 +harrison 5938 +damaged 5939 +satirical 5940 +1950 5941 +minority 5942 +suggestion 5943 +insightful 5944 +hangs 5945 +btw 5946 +preferred 5947 +sorely 5948 +windows 5949 +formed 5950 +profession 5951 +boy's 5952 +commenting 5953 +newer 5954 +landed 5955 +colin 5956 +tenant 5957 +goers 5958 +gunga 5959 +uniformly 5960 +neurotic 5961 +trials 5962 +authorities 5963 +oriented 5964 +swept 5965 +northern 5966 +computers 5967 +dylan 5968 +racing 5969 +kline 5970 +95 5971 +vocal 5972 +steele 5973 +1990s 5974 +viewer's 5975 +bridges 5976 +proving 5977 +entered 5978 +demonic 5979 +natives 5980 +seeming 5981 +brendan 5982 +reeves 5983 +obtain 5984 +rear 5985 +evolution 5986 +ie 5987 +christine 5988 +token 5989 +elevator 5990 +braveheart 5991 +garner 5992 +ripping 5993 +refuse 5994 +firmly 5995 +outright 5996 +mermaid 5997 +exquisite 5998 +mutual 5999 +posey 6000 +biblical 6001 +disastrous 6002 +sleaze 6003 +bars 6004 +helpful 6005 +wendigo 6006 +eleven 6007 +choosing 6008 +neatly 6009 +engrossing 6010 +kidman 6011 +freddy's 6012 +earn 6013 +tops 6014 +uma 6015 +anton 6016 +justified 6017 +wtf 6018 +demanding 6019 +mannerisms 6020 +inspire 6021 +speeches 6022 +containing 6023 +pacific 6024 +myth 6025 +sleeps 6026 +reliable 6027 +fifth 6028 +gillian 6029 +setup 6030 +vile 6031 +cookie 6032 +4th 6033 +hitler's 6034 +bowl 6035 +she'll 6036 +sincerely 6037 +tapes 6038 +vanessa 6039 +insanity 6040 +casts 6041 +ratso 6042 +brooding 6043 +disgrace 6044 +luis 6045 +helpless 6046 +1991 6047 +mirrors 6048 +label 6049 +emerge 6050 +kent 6051 +altered 6052 +forgiven 6053 +predecessor 6054 +heels 6055 +skit 6056 +contempt 6057 +activity 6058 +crossing 6059 +describing 6060 +1985 6061 +duvall 6062 +rampage 6063 +healthy 6064 +knightley 6065 +mercy 6066 +undead 6067 +cemetery 6068 +spies 6069 +mesmerizing 6070 +homicide 6071 +cons 6072 +frontal 6073 +ariel 6074 +restrained 6075 +valentine 6076 +approaches 6077 +startling 6078 +cerebral 6079 +vain 6080 +rooting 6081 +destroys 6082 +preparing 6083 +subtly 6084 +1977 6085 +1974 6086 +jordan 6087 +hats 6088 +grateful 6089 +pc 6090 +boasts 6091 +gere 6092 +regards 6093 +creek 6094 +survives 6095 +mixing 6096 +realities 6097 +conan 6098 +topics 6099 +educated 6100 +shaped 6101 +insights 6102 +melissa 6103 +carey 6104 +tunnel 6105 +artwork 6106 +hulk 6107 +hartley 6108 +radical 6109 +deny 6110 +modest 6111 +unlikeable 6112 +compete 6113 +1994 6114 +sometime 6115 +statue 6116 +grounds 6117 +weaker 6118 +seedy 6119 +mitch 6120 +breakfast 6121 +inspirational 6122 +jess 6123 +hugely 6124 +leaders 6125 +coat 6126 +miami 6127 +scariest 6128 +owners 6129 +casino 6130 +miniseries 6131 +freeze 6132 +akin 6133 +timberlake 6134 +deer 6135 +jared 6136 +bulk 6137 +conrad 6138 +wardrobe 6139 +poker 6140 +crashes 6141 +hers 6142 +rapidly 6143 +applaud 6144 +tara 6145 +nominations 6146 +wrenching 6147 +votes 6148 +contribution 6149 +candidate 6150 +loretta 6151 +affects 6152 +homes 6153 +cinemas 6154 +dubious 6155 +child's 6156 +stare 6157 +banter 6158 +exploits 6159 +advertised 6160 +21st 6161 +guards 6162 +vastly 6163 +relentless 6164 +disguised 6165 +masterfully 6166 +critique 6167 +dim 6168 +located 6169 +refers 6170 +narrow 6171 +des 6172 +washed 6173 +origin 6174 +puppets 6175 +addict 6176 +internal 6177 +error 6178 +disgust 6179 +injured 6180 +cartoonish 6181 +bronson 6182 +gods 6183 +alvin 6184 +30s 6185 +shell 6186 +owes 6187 +repulsive 6188 +gimmick 6189 +boris 6190 +linear 6191 +randolph 6192 +photographs 6193 +rides 6194 +ingrid 6195 +scifi 6196 +abruptly 6197 +limitations 6198 +joker 6199 +youthful 6200 +dandy 6201 +unsure 6202 +dazzling 6203 +gained 6204 +arab 6205 +detract 6206 +underwear 6207 +christina 6208 +caricature 6209 +bloom 6210 +continuing 6211 +lasts 6212 +inaccurate 6213 +where's 6214 +swallow 6215 +standout 6216 +motive 6217 +nations 6218 +convicted 6219 +bravo 6220 +youtube 6221 +nolte 6222 +lauren 6223 +holocaust 6224 +vehicles 6225 +bones 6226 +thirties 6227 +audition 6228 +factors 6229 +headache 6230 +growth 6231 +natured 6232 +mason 6233 +expertly 6234 +spine 6235 +hires 6236 +zizek 6237 +undeniably 6238 +bates 6239 +excellently 6240 +highway 6241 +nina 6242 +screenwriters 6243 +buzz 6244 +chronicles 6245 +insults 6246 +corn 6247 +stunningly 6248 +dread 6249 +homosexuality 6250 +perception 6251 +antonio 6252 +lukas 6253 +reward 6254 +decline 6255 +son's 6256 +las 6257 +mol 6258 +unsuspecting 6259 +strengths 6260 +convinces 6261 +spit 6262 +entering 6263 +natalie 6264 +tossed 6265 +toni 6266 +colours 6267 +ronald 6268 +mathieu 6269 +implied 6270 +teams 6271 +resolved 6272 +tower 6273 +entirety 6274 +confront 6275 +wander 6276 +derivative 6277 +missile 6278 +definitive 6279 +gates 6280 +supply 6281 +bachelor 6282 +anyone's 6283 +divorced 6284 +attenborough 6285 +males 6286 +promptly 6287 +painter 6288 +sinking 6289 +polly 6290 +origins 6291 +endlessly 6292 +nerves 6293 +1959 6294 +wagner 6295 +carmen 6296 +judd 6297 +poe 6298 +walt 6299 +unimaginative 6300 +anil 6301 +mice 6302 +1940s 6303 +confronted 6304 +200 6305 +lend 6306 +authenticity 6307 +siblings 6308 +longest 6309 +repressed 6310 +alexandre 6311 +span 6312 +sergeant 6313 +stardom 6314 +cassavetes 6315 +vividly 6316 +salvation 6317 +yep 6318 +jacket 6319 +users 6320 +jarring 6321 +enhanced 6322 +puerto 6323 +colleagues 6324 +referring 6325 +jedi 6326 +tokyo 6327 +niece 6328 +published 6329 +jackson's 6330 +mates 6331 +cbs 6332 +damned 6333 +sgt 6334 +delicious 6335 +uniform 6336 +dominated 6337 +judgment 6338 +juliet 6339 +accessible 6340 +bsg 6341 +exterior 6342 +misfortune 6343 +zane 6344 +phillip 6345 +ally 6346 +giants 6347 +netflix 6348 +energetic 6349 +austen 6350 +unattractive 6351 +devil's 6352 +mobile 6353 +underwater 6354 +stalking 6355 +disabled 6356 +depict 6357 +offbeat 6358 +earnest 6359 +servants 6360 +jill 6361 +bruno 6362 +cliches 6363 +crisp 6364 +nerve 6365 +peck 6366 +wounds 6367 +hepburn 6368 +terminator 6369 +sized 6370 +suburban 6371 +depths 6372 +buys 6373 +hindi 6374 +sticking 6375 +literal 6376 +playboy 6377 +gable 6378 +meandering 6379 +belly 6380 +sensible 6381 +lighter 6382 +21 6383 +stranded 6384 +yokai 6385 +pray 6386 +mutant 6387 +sale 6388 +exit 6389 +estranged 6390 +anyhow 6391 +identical 6392 +foolish 6393 +eventual 6394 +errol 6395 +separated 6396 +bashing 6397 +cushing 6398 +soylent 6399 +antonioni 6400 +galaxy 6401 +glued 6402 +imo 6403 +tormented 6404 +syndrome 6405 +biting 6406 +dragons 6407 +macabre 6408 +dealer 6409 +filthy 6410 +residents 6411 +victorian 6412 +witchcraft 6413 +cents 6414 +improbable 6415 +inherent 6416 +alley 6417 +lester 6418 +readers 6419 +scratch 6420 +pirate 6421 +cher 6422 +pickford 6423 +astounding 6424 +devastating 6425 +breathing 6426 +clash 6427 +approaching 6428 +severed 6429 +owned 6430 +interact 6431 +cleaning 6432 +characteristics 6433 +expects 6434 +guinness 6435 +dismal 6436 +sniper 6437 +lance 6438 +sand 6439 +respectable 6440 +budgets 6441 +sought 6442 +scoop 6443 +slide 6444 +butch 6445 +nightclub 6446 +yours 6447 +blooded 6448 +she'd 6449 +appeals 6450 +ebert 6451 +harriet 6452 +farmer 6453 +stylized 6454 +owns 6455 +noticeable 6456 +kurosawa 6457 +dustin 6458 +id 6459 +balanced 6460 +fragile 6461 +sublime 6462 +salman 6463 +answered 6464 +penn 6465 +amrita 6466 +adore 6467 +logan 6468 +demonstrate 6469 +concentrate 6470 +exploit 6471 +races 6472 +laden 6473 +psychopath 6474 +affleck 6475 +1982 6476 +garland 6477 +worms 6478 +23 6479 +filmmaking 6480 +pattern 6481 +habit 6482 +incapable 6483 +isolation 6484 +fatale 6485 +decidedly 6486 +steam 6487 +jules 6488 +ford's 6489 +asia 6490 +possess 6491 +senior 6492 +reminder 6493 +cheaply 6494 +principals 6495 +immortal 6496 +christie 6497 +monty 6498 +sf 6499 +evelyn 6500 +denis 6501 +corporation 6502 +turd 6503 +soderbergh 6504 +deliverance 6505 +subway 6506 +potter 6507 +breakdown 6508 +flimsy 6509 +packs 6510 +judged 6511 +wisely 6512 +moe 6513 +bogus 6514 +enthusiastic 6515 +cries 6516 +conveyed 6517 +escaping 6518 +plotting 6519 +wilder 6520 +pale 6521 +deliberate 6522 +dvd's 6523 +informed 6524 +promoted 6525 +axe 6526 +flashes 6527 +cypher 6528 +tremendously 6529 +esquire 6530 +1944 6531 +feast 6532 +glaring 6533 +irene 6534 +spectacle 6535 +chopped 6536 +cyborg 6537 +assembled 6538 +drinks 6539 +dump 6540 +celebrated 6541 +quarter 6542 +boyer 6543 +clara 6544 +arguing 6545 +selected 6546 +numbing 6547 +romeo 6548 +volume 6549 +truman 6550 +combines 6551 +embrace 6552 +troma 6553 +expose 6554 +laurie 6555 +kidnapping 6556 +debt 6557 +contribute 6558 +ominous 6559 +jodie 6560 +magician 6561 +o'hara 6562 +conveniently 6563 +outline 6564 +excruciatingly 6565 +accounts 6566 +pound 6567 +pixar 6568 +pierre 6569 +hackman 6570 +lightning 6571 +absorbing 6572 +copied 6573 +clone 6574 +lola 6575 +ugh 6576 +burke 6577 +cecil 6578 +jan 6579 +mitchum 6580 +jealousy 6581 +advised 6582 +40s 6583 +ensure 6584 +collect 6585 +rewarding 6586 +updated 6587 +freaky 6588 +attacking 6589 +rescued 6590 +lex 6591 +1975 6592 +dilemma 6593 +colored 6594 +beowulf 6595 +hi 6596 +melvyn 6597 +ps 6598 +pocket 6599 +passengers 6600 +accepting 6601 +sydney 6602 +classy 6603 +whiny 6604 +loy 6605 +experiencing 6606 +exorcist 6607 +destructive 6608 +300 6609 +goods 6610 +spencer 6611 +corbett 6612 +shepherd 6613 +reports 6614 +expectation 6615 +sophie 6616 +sentimentality 6617 +pause 6618 +sidewalk 6619 +karate 6620 +quantum 6621 +intricate 6622 +tax 6623 +scarface 6624 +crippled 6625 +longing 6626 +nbc 6627 +reeve 6628 +vintage 6629 +crown 6630 +1998 6631 +quentin 6632 +obsessive 6633 +immense 6634 +knocks 6635 +bounty 6636 +indiana 6637 +adaption 6638 +delighted 6639 +er 6640 +naschy 6641 +liam 6642 +establish 6643 +addiction 6644 +europeans 6645 +tool 6646 +stroke 6647 +overblown 6648 +goldblum 6649 +jaded 6650 +pursue 6651 +sucker 6652 +slip 6653 +theories 6654 +rookie 6655 +havoc 6656 +1953 6657 +anticipated 6658 +dukes 6659 +principle 6660 +voyage 6661 +gamera 6662 +swearing 6663 +unsatisfying 6664 +wonderland 6665 +frontier 6666 +parallels 6667 +crashing 6668 +downs 6669 +incorrect 6670 +erika 6671 +aggressive 6672 +divine 6673 +paula 6674 +dashing 6675 +turmoil 6676 +suspected 6677 +aided 6678 +grass 6679 +story's 6680 +distract 6681 +cape 6682 +snuff 6683 +bach 6684 +comprehend 6685 +werewolves 6686 +masterson 6687 +resulted 6688 +miranda 6689 +tendency 6690 +fright 6691 +spaghetti 6692 +goals 6693 +rainy 6694 +reviewing 6695 +juliette 6696 +establishment 6697 +redundant 6698 +switched 6699 +taped 6700 +sarcastic 6701 +arguments 6702 +rider 6703 +peaceful 6704 +barbra 6705 +butcher 6706 +shootout 6707 +bubble 6708 +routines 6709 +demonstrated 6710 +spice 6711 +backed 6712 +polish 6713 +cultures 6714 +parsons 6715 +distress 6716 +hero's 6717 +chill 6718 +morons 6719 +slugs 6720 +subtext 6721 +ultimatum 6722 +intentional 6723 +virtual 6724 +morals 6725 +cutter 6726 +hayworth 6727 +mouthed 6728 +fleshed 6729 +fascist 6730 +dramatically 6731 +passage 6732 +realization 6733 +slaves 6734 +gentlemen 6735 +liu 6736 +hyper 6737 +peculiar 6738 +avoiding 6739 +lavish 6740 +adrian 6741 +vanilla 6742 +boiled 6743 +admired 6744 +thieves 6745 +moron 6746 +sixth 6747 +'cause 6748 +arranged 6749 +climb 6750 +horny 6751 +approached 6752 +alleged 6753 +pumbaa 6754 +predictably 6755 +wielding 6756 +armstrong 6757 +commitment 6758 +seymour 6759 +serum 6760 +odyssey 6761 +hybrid 6762 +messing 6763 +begging 6764 +alter 6765 +establishing 6766 +toby 6767 +whining 6768 +canceled 6769 +collective 6770 +define 6771 +dame 6772 +bikini 6773 +afterward 6774 +mystical 6775 +tourist 6776 +furniture 6777 +fairbanks 6778 +casper 6779 +revolt 6780 +remembering 6781 +exploding 6782 +consideration 6783 +arrest 6784 +inmates 6785 +1934 6786 +shift 6787 +aiming 6788 +samantha 6789 +puzzle 6790 +ghetto 6791 +arc 6792 +traits 6793 +apply 6794 +olds 6795 +sang 6796 +distraction 6797 +hateful 6798 +fools 6799 +anytime 6800 +reviewed 6801 +enhance 6802 +lunch 6803 +coke 6804 +upside 6805 +papers 6806 +insist 6807 +medieval 6808 +wine 6809 +vega 6810 +insomnia 6811 +arriving 6812 +keaton's 6813 +phenomenal 6814 +fills 6815 +graveyard 6816 +stella 6817 +exploited 6818 +writer's 6819 +acquired 6820 +strict 6821 +slapped 6822 +jewel 6823 +thelma 6824 +mcqueen 6825 +pedestrian 6826 +cal 6827 +anthology 6828 +vince 6829 +mythology 6830 +consciousness 6831 +kinnear 6832 +life's 6833 +carnage 6834 +courtroom 6835 +tolerable 6836 +populated 6837 +huston 6838 +contributed 6839 +poses 6840 +actors' 6841 +optimistic 6842 +verdict 6843 +rebellious 6844 +trace 6845 +whites 6846 +commits 6847 +kelly's 6848 +mouths 6849 +stream 6850 +respects 6851 +leap 6852 +sickening 6853 +puppy 6854 +overboard 6855 +diverse 6856 +monologue 6857 +tuned 6858 +corman 6859 +gypo 6860 +skilled 6861 +seasoned 6862 +settled 6863 +horrified 6864 +remembers 6865 +relentlessly 6866 +dj 6867 +— 6868 +jersey 6869 +psychologist 6870 +borders 6871 +lethal 6872 +tony's 6873 +shoe 6874 +smash 6875 +taboo 6876 +wiped 6877 +excuses 6878 +crosses 6879 +salesman 6880 +ritual 6881 +mormon 6882 +achieves 6883 +thunderbirds 6884 +scored 6885 +vanity 6886 +pad 6887 +aussie 6888 +explodes 6889 +ira 6890 +dynamics 6891 +preminger 6892 +franklin 6893 +verbal 6894 +feminine 6895 +policy 6896 +flavor 6897 +expense 6898 +suggesting 6899 +trains 6900 +instincts 6901 +nuances 6902 +dumber 6903 +flock 6904 +feeble 6905 +deanna 6906 +hoot 6907 +cuban 6908 +kathy 6909 +possession 6910 +document 6911 +cohen 6912 +foundation 6913 +diary 6914 +guinea 6915 +covering 6916 +vomit 6917 +readily 6918 +fluid 6919 +cigarette 6920 +tactics 6921 +deliciously 6922 +seductive 6923 +circles 6924 +phase 6925 +themed 6926 +busey 6927 +marilyn 6928 +amidst 6929 +posing 6930 +lean 6931 +cooking 6932 +deputy 6933 +duel 6934 +brainless 6935 +mute 6936 +meantime 6937 +unsympathetic 6938 +wheel 6939 +update 6940 +immigrant 6941 +weary 6942 +basket 6943 +attending 6944 +mortal 6945 +clive 6946 +regularly 6947 +delightfully 6948 +possesses 6949 +newcomer 6950 +porter 6951 +invention 6952 +sources 6953 +wash 6954 +contestants 6955 +shockingly 6956 +wheelchair 6957 +stephanie 6958 +ritchie 6959 +wong 6960 +pushes 6961 +ricky 6962 +audience's 6963 +einstein 6964 +controlling 6965 +mama 6966 +encountered 6967 +pathos 6968 +zorro 6969 +mysteriously 6970 +korea 6971 +bachchan 6972 +jury 6973 +keys 6974 +skinny 6975 +sells 6976 +satisfaction 6977 +romances 6978 +meal 6979 +explosive 6980 +defies 6981 +drab 6982 +clerk 6983 +pfeiffer 6984 +sunrise 6985 +symbol 6986 +pirates 6987 +otto 6988 +novelty 6989 +jacques 6990 +void 6991 +herbert 6992 +narrated 6993 +lionel 6994 +targets 6995 +august 6996 +razor 6997 +rivers 6998 +admitted 6999 +mum 7000 +sundance 7001 +lends 7002 +cliched 7003 +screwball 7004 +serials 7005 +neglected 7006 +olivia 7007 +truths 7008 +sided 7009 +steer 7010 +flower 7011 +indifferent 7012 +dumped 7013 +lucille 7014 +mole 7015 +products 7016 +beg 7017 +releasing 7018 +niven 7019 +stewart's 7020 +ordeal 7021 +darth 7022 +um 7023 +crosby 7024 +statements 7025 +followers 7026 +psyche 7027 +excruciating 7028 +noteworthy 7029 +swinging 7030 +deed 7031 +aftermath 7032 +ranch 7033 +consist 7034 +embarrassingly 7035 +unusually 7036 +convention 7037 +shifts 7038 +produces 7039 +motorcycle 7040 +tickets 7041 +wider 7042 +longoria 7043 +gwyneth 7044 +employee 7045 +instances 7046 +parking 7047 +intact 7048 +starters 7049 +rapid 7050 +arrow 7051 +thurman 7052 +debbie 7053 +dumbest 7054 +wastes 7055 +sarandon 7056 +economic 7057 +israeli 7058 +additionally 7059 +fanatic 7060 +planes 7061 +pursued 7062 +legitimate 7063 +discussed 7064 +forties 7065 +introducing 7066 +anxious 7067 +cannes 7068 +biker 7069 +deciding 7070 +sanders 7071 +fuzzy 7072 +agony 7073 +alot 7074 +assignment 7075 +stones 7076 +scorsese 7077 +caron 7078 +degrees 7079 +medicine 7080 +hannah 7081 +reverse 7082 +inaccuracies 7083 +july 7084 +attended 7085 +gilbert 7086 +forgetting 7087 +jane's 7088 +gielgud 7089 +angie 7090 +milo 7091 +laputa 7092 +branagh's 7093 +motions 7094 +auto 7095 +controversy 7096 +grandma 7097 +cunningham 7098 +professionals 7099 +criticize 7100 +kidnap 7101 +artistry 7102 +sarcasm 7103 +fishburne 7104 +brow 7105 +bogart 7106 +columbia 7107 +incidents 7108 +vera 7109 +meteor 7110 +georgia 7111 +arty 7112 +freaking 7113 +hadley 7114 +suspicion 7115 +scott's 7116 +coffin 7117 +juan 7118 +crossed 7119 +idol 7120 +grip 7121 +obstacles 7122 +mentor 7123 +consequently 7124 +begs 7125 +stating 7126 +ambitions 7127 +muslims 7128 +executives 7129 +daisy 7130 +manners 7131 +warns 7132 +1948 7133 +jolie 7134 +arquette 7135 +distracted 7136 +centuries 7137 +abound 7138 +jose 7139 +factual 7140 +goodbye 7141 +trigger 7142 +breast 7143 +invite 7144 +tcm 7145 +unanswered 7146 +indicate 7147 +shepard 7148 +session 7149 +daylight 7150 +minnelli 7151 +cindy 7152 +funding 7153 +pains 7154 +predator 7155 +flames 7156 +fried 7157 +scripting 7158 +rational 7159 +stabbed 7160 +collette 7161 +'i 7162 +compliment 7163 +hooker 7164 +cliffhanger 7165 +inclusion 7166 +debra 7167 +roughly 7168 +moss 7169 +1967 7170 +awakening 7171 +viewpoint 7172 +kazan 7173 +rejects 7174 +toned 7175 +sentences 7176 +denise 7177 +originals 7178 +cycle 7179 +informative 7180 +pros 7181 +harlow 7182 +stern 7183 +corey 7184 +stalked 7185 +foil 7186 +plodding 7187 +varied 7188 +sweden 7189 +detroit 7190 +misunderstood 7191 +clay 7192 +relevance 7193 +depictions 7194 +blamed 7195 +paints 7196 +pointing 7197 +click 7198 +stance 7199 +protest 7200 +chamber 7201 +robbers 7202 +gooding 7203 +soprano 7204 +likeable 7205 +exclusively 7206 +slim 7207 +campus 7208 +haines 7209 +cheadle 7210 +cap 7211 +cab 7212 +rambling 7213 +paranoid 7214 +seats 7215 +frances 7216 +rowlands 7217 +101 7218 +consequence 7219 +murky 7220 +abandon 7221 +gap 7222 +berkeley 7223 +ruining 7224 +stink 7225 +denouement 7226 +penelope 7227 +intro 7228 +abortion 7229 +tomei 7230 +replies 7231 +antagonist 7232 +gloria 7233 +stardust 7234 +tomb 7235 +gallery 7236 +bug's 7237 +determination 7238 +40's 7239 +c'mon 7240 +translate 7241 +bait 7242 +killer's 7243 +eagerly 7244 +relating 7245 +iranian 7246 +rips 7247 +momentum 7248 +uncanny 7249 +frozen 7250 +begun 7251 +generate 7252 +uniforms 7253 +intensely 7254 +dreamy 7255 +martian 7256 +festivals 7257 +grabbed 7258 +mock 7259 +jenna 7260 +che's 7261 +schedule 7262 +surroundings 7263 +coma 7264 +imaginary 7265 +schneider 7266 +gus 7267 +foremost 7268 +composition 7269 +robertson 7270 +politicians 7271 +services 7272 +hysterically 7273 +snowman 7274 +maureen 7275 +omar 7276 +republic 7277 +lurking 7278 +pans 7279 +alliance 7280 +hostel 7281 +diner 7282 +sheen 7283 +injury 7284 +rupert 7285 +hippies 7286 +rosario 7287 +chamberlain 7288 +ww2 7289 +scenarios 7290 +participants 7291 +realistically 7292 +communication 7293 +kris 7294 +sg 7295 +kathleen 7296 +brat 7297 +redneck 7298 +launch 7299 +therapy 7300 +quasi 7301 +miyazaki 7302 +hmmm 7303 +85 7304 +faux 7305 +geisha 7306 +bauer 7307 +mick 7308 +enigmatic 7309 +1951 7310 +phones 7311 +shaggy 7312 +hostage 7313 +destination 7314 +lens 7315 +glimpses 7316 +1943 7317 +lastly 7318 +rehash 7319 +gestures 7320 +shotgun 7321 +casablanca 7322 +dismiss 7323 +sights 7324 +periods 7325 +burnt 7326 +bats 7327 +resembling 7328 +charlie's 7329 +apt 7330 +linked 7331 +widowed 7332 +dominic 7333 +glance 7334 +cow 7335 +tho 7336 +traps 7337 +curiously 7338 +heath 7339 +envy 7340 +playwright 7341 +gigantic 7342 +paths 7343 +bleed 7344 +ambiguity 7345 +gaps 7346 +bosses 7347 +hayes 7348 +sterling 7349 +necessity 7350 +comeback 7351 +sketches 7352 +sondra 7353 +ignoring 7354 +revolving 7355 +apocalyptic 7356 +reiser 7357 +sailor 7358 +saloon 7359 +frantic 7360 +resistance 7361 +pegg 7362 +overs 7363 +precise 7364 +herman 7365 +rounds 7366 +arkin 7367 +gloomy 7368 +pressed 7369 +haunt 7370 +1992 7371 +enchanted 7372 +iturbi 7373 +fuel 7374 +blaise 7375 +mabel 7376 +laboratory 7377 +county 7378 +veterans 7379 +studied 7380 +cheers 7381 +bearing 7382 +eh 7383 +sunset 7384 +reflected 7385 +rolls 7386 +investigator 7387 +adele 7388 +pen 7389 +maintains 7390 +capacity 7391 +kubrick's 7392 +unstable 7393 +avid 7394 +midst 7395 +man' 7396 +qualify 7397 +bonnie 7398 +person's 7399 +mins 7400 +geek 7401 +nun 7402 +jude 7403 +angelina 7404 +galactica 7405 +sufficient 7406 +substantial 7407 +incest 7408 +handicapped 7409 +trier 7410 +ample 7411 +doctor's 7412 +warden 7413 +supreme 7414 +hinted 7415 +slashers 7416 +rewarded 7417 +rice 7418 +complications 7419 +trauma 7420 +biopic 7421 +sebastian 7422 +'80s 7423 +characterizations 7424 +awareness 7425 +popped 7426 +sparks 7427 +vignettes 7428 +psychedelic 7429 +unclear 7430 +kells 7431 +tightly 7432 +existing 7433 +du 7434 +entrance 7435 +offend 7436 +goldie 7437 +guardian 7438 +collins 7439 +targeted 7440 +talky 7441 +extensive 7442 +ny 7443 +benefits 7444 +epics 7445 +pilots 7446 +payoff 7447 +stadium 7448 +october 7449 +stake 7450 +characterisation 7451 +applied 7452 +applies 7453 +pivotal 7454 +lowe 7455 +gathering 7456 +marisa 7457 +brent 7458 +upcoming 7459 +1963 7460 +overbearing 7461 +eli 7462 +occult 7463 +joking 7464 +ol' 7465 +graduate 7466 +beckinsale 7467 +nuanced 7468 +homicidal 7469 +addressed 7470 +evans 7471 +lunatic 7472 +parrot 7473 +edith 7474 +revival 7475 +convict 7476 +ignores 7477 +safely 7478 +plate 7479 +sour 7480 +turkish 7481 +favourites 7482 +ajay 7483 +boundaries 7484 +northam 7485 +profile 7486 +russ 7487 +skeptical 7488 +frog 7489 +invested 7490 +repeats 7491 +bias 7492 +'60s 7493 +drowned 7494 +iq 7495 +diversity 7496 +outlandish 7497 +nightmarish 7498 +dynamite 7499 +unfolding 7500 +convent 7501 +clooney 7502 +observations 7503 +johansson 7504 +1955 7505 +enchanting 7506 +tire 7507 +stabbing 7508 +disco 7509 +excellence 7510 +27 7511 +clunky 7512 +valid 7513 +array 7514 +engine 7515 +sammo 7516 +doug 7517 +sly 7518 +interior 7519 +resolve 7520 +hating 7521 +olsen 7522 +interviewed 7523 +chong 7524 +protection 7525 +maximum 7526 +nauseating 7527 +versa 7528 +apocalypse 7529 +exploitative 7530 +observation 7531 +murderers 7532 +questioning 7533 +gosh 7534 +stereotyped 7535 +flag 7536 +shore 7537 +pose 7538 +acknowledge 7539 +fruit 7540 +caretaker 7541 +rosemary's 7542 +interpretations 7543 +shin 7544 +stations 7545 +flavia 7546 +nutshell 7547 +announced 7548 +assure 7549 +silverman 7550 +duh 7551 +sonny 7552 +1958 7553 +blockbusters 7554 +pornography 7555 +vivian 7556 +sensibility 7557 +courtesy 7558 +battlestar 7559 +macdonald 7560 +boots 7561 +brides 7562 +reunite 7563 +brooke 7564 +controls 7565 +masked 7566 +phantasm 7567 +prophecy 7568 +slower 7569 +relying 7570 +sweat 7571 +divided 7572 +mannered 7573 +marked 7574 +witnessing 7575 +girlfriends 7576 +snipes 7577 +fortunate 7578 +watcher 7579 +brett 7580 +ernie 7581 +villainous 7582 +strung 7583 +rebels 7584 +candle 7585 +counting 7586 +mccarthy 7587 +rodriguez 7588 +bonham 7589 +portuguese 7590 +daytime 7591 +rea 7592 +insert 7593 +misty 7594 +displaying 7595 +substitute 7596 +satanic 7597 +wayans 7598 +magically 7599 +sincerity 7600 +owl 7601 +cocaine 7602 +spotlight 7603 +inter 7604 +chewing 7605 +lopez 7606 +chiba 7607 +progressed 7608 +entries 7609 +demille 7610 +chuckles 7611 +climbing 7612 +26 7613 +chaotic 7614 +criticized 7615 +confined 7616 +sanity 7617 +goat 7618 +unhinged 7619 +bittersweet 7620 +collar 7621 +realises 7622 +peril 7623 +bust 7624 +smell 7625 +turtle 7626 +wartime 7627 +admits 7628 +commanding 7629 +evokes 7630 +beard 7631 +seduce 7632 +harrowing 7633 +janet 7634 +phoenix 7635 +stiles 7636 +interrupted 7637 +whore 7638 +shocks 7639 +inadvertently 7640 +jar 7641 +wright 7642 +fart 7643 +resume 7644 +lynch's 7645 +needing 7646 +delirious 7647 +upstairs 7648 +obscurity 7649 +famed 7650 +palm 7651 +weekly 7652 +replacement 7653 +monotonous 7654 +smug 7655 +preaching 7656 +projected 7657 +randall 7658 +enduring 7659 +hmm 7660 +organization 7661 +landmark 7662 +thereby 7663 +fundamental 7664 +ripoff 7665 +rightly 7666 +ins 7667 +chew 7668 +slavery 7669 +unnatural 7670 +arrogance 7671 +waking 7672 +manipulation 7673 +jagger 7674 +reserved 7675 +blazing 7676 +finishes 7677 +somethings 7678 +observe 7679 +raging 7680 +thrust 7681 +trivial 7682 +madsen 7683 +carlos 7684 +samuel 7685 +tones 7686 +commendable 7687 +crushed 7688 +similarity 7689 +deemed 7690 +choir 7691 +imagining 7692 +unappealing 7693 +understatement 7694 +apple 7695 +discipline 7696 +thailand 7697 +colleague 7698 +convenient 7699 +rendering 7700 +hines 7701 +cena 7702 +mandy 7703 +testing 7704 +motel 7705 +subsequently 7706 +fassbinder 7707 +reluctantly 7708 +platform 7709 +men's 7710 +egyptian 7711 +aesthetic 7712 +hooper 7713 +accompanying 7714 +protective 7715 +penned 7716 +fetish 7717 +kirsten 7718 +herd 7719 +layered 7720 +scarecrows 7721 +incestuous 7722 +thunder 7723 +boogie 7724 +participate 7725 +forgiveness 7726 +baddies 7727 +hardened 7728 +forgets 7729 +comparable 7730 +combs 7731 +understandably 7732 +shahid 7733 +laying 7734 +marine 7735 +recover 7736 +scheming 7737 +cancelled 7738 +vargas 7739 +stumble 7740 +celebrities 7741 +merry 7742 +russo 7743 +frost 7744 +unfamiliar 7745 +madeleine 7746 +isabelle 7747 +crooks 7748 +python 7749 +filmography 7750 +explode 7751 +sylvia 7752 +article 7753 +climatic 7754 +achievements 7755 +conductor 7756 +pizza 7757 +reminding 7758 +remark 7759 +lo 7760 +gackt 7761 +traumatic 7762 +benjamin 7763 +stuffed 7764 +accidental 7765 +travis 7766 +govinda 7767 +must've 7768 +quintessential 7769 +deathtrap 7770 +cheerful 7771 +hostile 7772 +orchestra 7773 +ninety 7774 +gorilla 7775 +marcel 7776 +cameraman 7777 +shred 7778 +sholay 7779 +wrestler 7780 +customers 7781 +hallmark 7782 +beers 7783 +glossy 7784 +despise 7785 +anita 7786 +goings 7787 +spontaneous 7788 +1932 7789 +fleet 7790 +shameless 7791 +charges 7792 +camping 7793 +finishing 7794 +district 7795 +sins 7796 +dallas 7797 +file 7798 +yell 7799 +serbian 7800 +myrna 7801 +wholesome 7802 +titular 7803 +boo 7804 +o'brien 7805 +implies 7806 +sack 7807 +flip 7808 +salvage 7809 +annoy 7810 +restraint 7811 +imho 7812 +creations 7813 +affecting 7814 +pornographic 7815 +spoiling 7816 +bonanza 7817 +ala 7818 +raid 7819 +raunchy 7820 +sales 7821 +cheering 7822 +captivated 7823 +je 7824 +espionage 7825 +license 7826 +defining 7827 +beforehand 7828 +se 7829 +conclusions 7830 +bakshi's 7831 +hawn 7832 +sherlock 7833 +caprica 7834 +ruled 7835 +unconventional 7836 +diego 7837 +awry 7838 +verge 7839 +krueger 7840 +grin 7841 +whimsical 7842 +ideals 7843 +meyer 7844 +surround 7845 +characteristic 7846 +digging 7847 +shameful 7848 +coolest 7849 +philo 7850 +cells 7851 +reagan 7852 +seattle 7853 +infinitely 7854 +sickness 7855 +excels 7856 +2009 7857 +novelist 7858 +1946 7859 +burial 7860 +fades 7861 +faded 7862 +shannon 7863 +traditions 7864 +fraud 7865 +perverted 7866 +sheets 7867 +voodoo 7868 +desk 7869 +abundance 7870 +flashing 7871 +hunted 7872 +betrayed 7873 +admission 7874 +gershwin 7875 +rampant 7876 +relaxed 7877 +fires 7878 +polar 7879 +kindly 7880 +tits 7881 +melancholy 7882 +drowning 7883 +semblance 7884 +temper 7885 +cracks 7886 +tide 7887 +oblivious 7888 +miraculously 7889 +clarity 7890 +elliott 7891 +inserted 7892 +considers 7893 +constraints 7894 +drift 7895 +sunk 7896 +distributed 7897 +unnecessarily 7898 +welles' 7899 +flows 7900 +sexist 7901 +beckham 7902 +summed 7903 +henchmen 7904 +tools 7905 +transparent 7906 +devotion 7907 +hitchcock's 7908 +earliest 7909 +scarlett 7910 +dangerously 7911 +taut 7912 +dafoe 7913 +dreaming 7914 +seth 7915 +prop 7916 +cain 7917 +wesley 7918 +adapt 7919 +openly 7920 +sane 7921 +hugo 7922 +creasy 7923 +chops 7924 +pitched 7925 +juice 7926 +riff 7927 +blandings 7928 +shah 7929 +screened 7930 +tashan 7931 +meredith 7932 +doyle 7933 +mud 7934 +zodiac 7935 +regime 7936 +irritated 7937 +eagle 7938 +paycheck 7939 +egypt 7940 +spiral 7941 +letdown 7942 +wherever 7943 +madison 7944 +deeds 7945 +robotic 7946 +faint 7947 +outrageously 7948 +sheep 7949 +elsa 7950 +baron 7951 +overtones 7952 +searched 7953 +unleashed 7954 +sporting 7955 +lennon 7956 +gangs 7957 +dahmer 7958 +peggy 7959 +vapid 7960 +heap 7961 +circa 7962 +simpsons 7963 +slater 7964 +permanent 7965 +voyager 7966 +presidential 7967 +compensate 7968 +deepest 7969 +reject 7970 +uneasy 7971 +ghastly 7972 +gretchen 7973 +sophia 7974 +warehouse 7975 +switching 7976 +cedric 7977 +lara 7978 +evoke 7979 +flame 7980 +automatic 7981 +submarine 7982 +plug 7983 +programme 7984 +sucking 7985 +pursuing 7986 +avoids 7987 +assistance 7988 +assumes 7989 +orphan 7990 +mart 7991 +practical 7992 +joining 7993 +failures 7994 +liner 7995 +garfield 7996 +dwight 7997 +slut 7998 +oprah 7999 +committing 8000 +intend 8001 +ealing 8002 +shirts 8003 +locke 8004 +admirer 8005 +awaiting 8006 +ram 8007 +fritz 8008 +melbourne 8009 +contestant 8010 +timmy 8011 +rivals 8012 +buffy 8013 +clouds 8014 +ambiance 8015 +babes 8016 +ensue 8017 +coburn 8018 +occupied 8019 +sergio 8020 +sitcoms 8021 +variation 8022 +censorship 8023 +ferrell 8024 +radiation 8025 +snap 8026 +underdeveloped 8027 +takashi 8028 +hobgoblins 8029 +finney 8030 +listened 8031 +fiancée 8032 +complained 8033 +pauline 8034 +kinski 8035 +alarm 8036 +engineer 8037 +chloe 8038 +proceed 8039 +demeanor 8040 +suzanne 8041 +battlefield 8042 +rebellion 8043 +criticisms 8044 +remainder 8045 +ghostly 8046 +spaceship 8047 +howling 8048 +motivated 8049 +joint 8050 +carpenter's 8051 +fodder 8052 +bert 8053 +dominate 8054 +monks 8055 +dragging 8056 +inclined 8057 +upbeat 8058 +encouraged 8059 +networks 8060 +han 8061 +loren 8062 +brazilian 8063 +atlantic 8064 +flowing 8065 +progression 8066 +tess 8067 +meek 8068 +darkly 8069 +disappearance 8070 +colman 8071 +crashed 8072 +caper 8073 +solved 8074 +fairness 8075 +distinction 8076 +sensual 8077 +feinstone 8078 +sho 8079 +warrant 8080 +grease 8081 +visitor 8082 +marijuana 8083 +sections 8084 +avenge 8085 +tv's 8086 +croc 8087 +sober 8088 +badness 8089 +who've 8090 +ninjas 8091 +myrtle 8092 +runaway 8093 +helmet 8094 +scratching 8095 +quaint 8096 +busby 8097 +defending 8098 +buttons 8099 +artemisia 8100 +cloak 8101 +noting 8102 +confuse 8103 +experts 8104 +whip 8105 +borrow 8106 +barney 8107 +garage 8108 +happenings 8109 +mega 8110 +1990's 8111 +disregard 8112 +bean 8113 +aaron 8114 +edges 8115 +diving 8116 +investment 8117 +wee 8118 +electronic 8119 +gena 8120 +gypsy 8121 +suave 8122 +mustache 8123 +toxic 8124 +mira 8125 +bartender 8126 +prologue 8127 +transport 8128 +atrocity 8129 +everett 8130 +bernsen 8131 +notices 8132 +jo 8133 +boogeyman 8134 +knees 8135 +1966 8136 +1000 8137 +robbed 8138 +epitome 8139 +bennett 8140 +vcr 8141 +who'd 8142 +'a 8143 +detached 8144 +brit 8145 +hometown 8146 +jack's 8147 +prone 8148 +enormously 8149 +gilliam 8150 +jackman 8151 +dom 8152 +impending 8153 +bloodbath 8154 +mister 8155 +macmurray 8156 +vigilante 8157 +offense 8158 +prostitutes 8159 +fashions 8160 +idealistic 8161 +pigs 8162 +abomination 8163 +carpet 8164 +battling 8165 +principles 8166 +paz 8167 +pretends 8168 +awarded 8169 +admiration 8170 +incidental 8171 +tin 8172 +pairing 8173 +woefully 8174 +chip 8175 +classmates 8176 +timed 8177 +budding 8178 +gandolfini 8179 +revolver 8180 +liberty 8181 +associate 8182 +padding 8183 +colony 8184 +zelah 8185 +drum 8186 +vincenzo 8187 +secure 8188 +palestinian 8189 +girls' 8190 +blames 8191 +torment 8192 +kids' 8193 +framing 8194 +tackle 8195 +tended 8196 +peers 8197 +policemen 8198 +facility 8199 +ostensibly 8200 +harron 8201 +prank 8202 +lindy 8203 +bimbo 8204 +1957 8205 +saints 8206 +capote 8207 +shrek 8208 +breathe 8209 +nineties 8210 +worrying 8211 +believability 8212 +paragraph 8213 +mediocrity 8214 +influences 8215 +reported 8216 +conveying 8217 +programming 8218 +stoned 8219 +val 8220 +barnes 8221 +sharks 8222 +unravel 8223 +courageous 8224 +deck 8225 +giovanna 8226 +grating 8227 +britney 8228 +distinctive 8229 +blondell 8230 +spoofs 8231 +brush 8232 +effortlessly 8233 +riders 8234 +midget 8235 +annoyance 8236 +counterparts 8237 +economy 8238 +rivalry 8239 +stab 8240 +knights 8241 +socially 8242 +symbols 8243 +bodyguard 8244 +qualifies 8245 +connie 8246 +acclaim 8247 +managing 8248 +vibe 8249 +monroe 8250 +frat 8251 +baked 8252 +combining 8253 +martians 8254 +boobs 8255 +prostitution 8256 +closure 8257 +senator 8258 +outset 8259 +magazines 8260 +respond 8261 +interiors 8262 +division 8263 +slam 8264 +celebrate 8265 +elected 8266 +zu 8267 +monica 8268 +dillinger 8269 +brashear 8270 +cohesive 8271 +clinic 8272 +gig 8273 +tacked 8274 +coward 8275 +parodies 8276 +greene 8277 +billing 8278 +weirdness 8279 +dunst 8280 +rourke 8281 +manipulated 8282 +concentration 8283 +sinks 8284 +dreyfuss 8285 +asset 8286 +duchovny 8287 +superstar 8288 +clyde 8289 +december 8290 +pompous 8291 +fabric 8292 +placement 8293 +gibson 8294 +bless 8295 +boards 8296 +troopers 8297 +reese 8298 +goodman 8299 +transplant 8300 +shocker 8301 +examine 8302 +chock 8303 +scarlet 8304 +informs 8305 +responds 8306 +collapse 8307 +data 8308 +swiss 8309 +reasoning 8310 +confines 8311 +categories 8312 +injustice 8313 +laser 8314 +dish 8315 +employees 8316 +smith's 8317 +em 8318 +gasp 8319 +sacrifices 8320 +maurice 8321 +worship 8322 +screenplays 8323 +tolerate 8324 +pee 8325 +overshadowed 8326 +dern 8327 +reunited 8328 +brick 8329 +loner 8330 +holt 8331 +sites 8332 +uncertain 8333 +theatres 8334 +morse 8335 +yells 8336 +sibling 8337 +cheech 8338 +butchered 8339 +mae 8340 +ernest 8341 +sensibilities 8342 +500 8343 +ali 8344 +irving 8345 +castro 8346 +influential 8347 +terrorism 8348 +strained 8349 +derived 8350 +chandler 8351 +slept 8352 +perspectives 8353 +bleeding 8354 +madman 8355 +1942 8356 +inconsistencies 8357 +sensitivity 8358 +jam 8359 +hans 8360 +sustain 8361 +systems 8362 +armor 8363 +burgess 8364 +fiery 8365 +queens 8366 +katie 8367 +gruff 8368 +ewoks 8369 +faye 8370 +tramp 8371 +brandon 8372 +lighthearted 8373 +inform 8374 +cursed 8375 +retro 8376 +250 8377 +malden 8378 +cody 8379 +spelled 8380 +manic 8381 +labeled 8382 +perverse 8383 +collector 8384 +drain 8385 +shelter 8386 +spade 8387 +fallon 8388 +ang 8389 +gino 8390 +kareena 8391 +depardieu 8392 +apollo 8393 +officially 8394 +playful 8395 +informer 8396 +banks 8397 +retirement 8398 +booth 8399 +replacing 8400 +transforms 8401 +surrender 8402 +shield 8403 +jigsaw 8404 +fiend 8405 +predecessors 8406 +judgement 8407 +bing 8408 +englund 8409 +ads 8410 +damsel 8411 +stirring 8412 +structured 8413 +patty 8414 +poet 8415 +signature 8416 +tolerance 8417 +bites 8418 +dash 8419 +seriousness 8420 +casted 8421 +mercifully 8422 +edison 8423 +advances 8424 +padded 8425 +czech 8426 +lingering 8427 +sensational 8428 +crowded 8429 +bigfoot 8430 +captive 8431 +plotted 8432 +premiered 8433 +dictator 8434 +locale 8435 +bastard 8436 +manga 8437 +fighters 8438 +sophistication 8439 +lifts 8440 +yarn 8441 +spelling 8442 +uptight 8443 +farrah 8444 +drummer 8445 +amid 8446 +kidnaps 8447 +peaks 8448 +drastically 8449 +cringing 8450 +coop 8451 +dealers 8452 +geoffrey 8453 +rousing 8454 +supermarket 8455 +standpoint 8456 +thereafter 8457 +portions 8458 +latino 8459 +henchman 8460 +berenger 8461 +slash 8462 +sandy 8463 +lurid 8464 +coal 8465 +interplay 8466 +stares 8467 +willingly 8468 +mines 8469 +ss 8470 +ceremony 8471 +inexperienced 8472 +awfulness 8473 +condemned 8474 +benny 8475 +alba 8476 +mythical 8477 +spotted 8478 +sara 8479 +fierce 8480 +thereof 8481 +bloodshed 8482 +enthralling 8483 +geniuses 8484 +lars 8485 +rant 8486 +theodore 8487 +heather 8488 +echoes 8489 +maintaining 8490 +bombed 8491 +bitchy 8492 +fiasco 8493 +powered 8494 +tina 8495 +ossessione 8496 +worm 8497 +godard 8498 +observed 8499 +staging 8500 +attendant 8501 +anxiety 8502 +villa 8503 +varying 8504 +stepmother 8505 +aircraft 8506 +david's 8507 +justification 8508 +identified 8509 +downfall 8510 +anguish 8511 +shoved 8512 +allan 8513 +bliss 8514 +caution 8515 +transported 8516 +impressions 8517 +miike's 8518 +alexandra 8519 +shout 8520 +functions 8521 +imitate 8522 +norris 8523 +dwarf 8524 +nearest 8525 +funky 8526 +drugged 8527 +stabs 8528 +marrying 8529 +hallucinations 8530 +allies 8531 +communism 8532 +fixed 8533 +sorrow 8534 +orlando 8535 +register 8536 +surf 8537 +scarier 8538 +freed 8539 +tasty 8540 +baddie 8541 +vet 8542 +attic 8543 +representing 8544 +widower 8545 +cunning 8546 +plagued 8547 +hunky 8548 +apartheid 8549 +cockney 8550 +luc 8551 +islands 8552 +fur 8553 +emphasize 8554 +confession 8555 +ceiling 8556 +hairy 8557 +warhols 8558 +stricken 8559 +presume 8560 +rosenstrasse 8561 +meadows 8562 +distorted 8563 +virtue 8564 +natali 8565 +forrest 8566 +starship 8567 +lampoon 8568 +depend 8569 +marvin 8570 +mixes 8571 +jewelry 8572 +correctness 8573 +nest 8574 +myra 8575 +rockets 8576 +russians 8577 +glenda 8578 +byron 8579 +sammy 8580 +grandpa 8581 +monday 8582 +entertains 8583 +adultery 8584 +egg 8585 +massey 8586 +drawings 8587 +travolta 8588 +tricked 8589 +abu 8590 +bio 8591 +lin 8592 +fagin 8593 +cowardly 8594 +overwrought 8595 +determine 8596 +throne 8597 +ratio 8598 +tsui 8599 +paired 8600 +cannibals 8601 +fuss 8602 +client 8603 +animator 8604 +hurry 8605 +romania 8606 +foreboding 8607 +pub 8608 +earns 8609 +bon 8610 +gen 8611 +della 8612 +photograph 8613 +pecker 8614 +censors 8615 +groundbreaking 8616 +predicted 8617 +crooked 8618 +engagement 8619 +arnie 8620 +torturing 8621 +towns 8622 +intellectually 8623 +bald 8624 +finely 8625 +confirmed 8626 +natasha 8627 +hale 8628 +chemical 8629 +spells 8630 +loony 8631 +richly 8632 +edmund 8633 +groove 8634 +vaudeville 8635 +bills 8636 +ma 8637 +millennium 8638 +gladiator 8639 +icy 8640 +irrational 8641 +ballroom 8642 +daria 8643 +conflicted 8644 +clarence 8645 +subdued 8646 +sigh 8647 +artistically 8648 +keanu 8649 +laced 8650 +potent 8651 +representative 8652 +gently 8653 +reckless 8654 +dopey 8655 +jerky 8656 +deborah 8657 +decency 8658 +grossly 8659 +predictability 8660 +consumed 8661 +belle 8662 +blessed 8663 +parks 8664 +curtain 8665 +dukakis 8666 +federal 8667 +analyze 8668 +echo 8669 +contributes 8670 +accomplishment 8671 +cheesiness 8672 +romanian 8673 +almighty 8674 +continuously 8675 +gathered 8676 +dive 8677 +undercover 8678 +diaz 8679 +profoundly 8680 +identities 8681 +crypt 8682 +downbeat 8683 +1949 8684 +gusto 8685 +missions 8686 +sasquatch 8687 +locate 8688 +borrows 8689 +maturity 8690 +harbor 8691 +denial 8692 +emmy 8693 +arch 8694 +animations 8695 +airing 8696 +superfluous 8697 +lists 8698 +officials 8699 +steaming 8700 +operate 8701 +threads 8702 +significantly 8703 +aniston 8704 +goldsworthy 8705 +anchors 8706 +disappoints 8707 +collaboration 8708 +trusted 8709 +lays 8710 +sync 8711 +1920s 8712 +wrongly 8713 +lindsey 8714 +optimism 8715 +vertigo 8716 +abroad 8717 +judges 8718 +continent 8719 +lizard 8720 +muni 8721 +helena 8722 +hartley's 8723 +zeta 8724 +denying 8725 +proportions 8726 +winners 8727 +ll 8728 +monologues 8729 +gravity 8730 +forbes 8731 +launched 8732 +robbing 8733 +mash 8734 +mocking 8735 +confronts 8736 +mutants 8737 +beetle 8738 +nifty 8739 +fence 8740 +horn 8741 +luxury 8742 +athletic 8743 +imprisoned 8744 +scriptwriter 8745 +mack 8746 +handy 8747 +pia 8748 +uninspiring 8749 +rhyme 8750 +1964 8751 +promoting 8752 +73 8753 +flew 8754 +98 8755 +corbin 8756 +chevy 8757 +mobster 8758 +altman's 8759 +extraordinarily 8760 +applause 8761 +abstract 8762 +switches 8763 +garde 8764 +icons 8765 +showcases 8766 +intelligently 8767 +capitalism 8768 +developments 8769 +lions 8770 +hanzo 8771 +hypnotic 8772 +temptation 8773 +dedication 8774 +opposition 8775 +sensation 8776 +kristofferson 8777 +barton 8778 +lds 8779 +bothers 8780 +satisfactory 8781 +nora 8782 +genetic 8783 +moonstruck 8784 +illustrate 8785 +notwithstanding 8786 +elephants 8787 +stripper 8788 +grendel 8789 +fulfilling 8790 +languages 8791 +hilton 8792 +autobiography 8793 +pleasures 8794 +lightweight 8795 +increasing 8796 +preferably 8797 +shifting 8798 +bearable 8799 +prefers 8800 +idiocy 8801 +heroin 8802 +manipulate 8803 +uncredited 8804 +sheridan 8805 +conniving 8806 +surgeon 8807 +nonexistent 8808 +deservedly 8809 +clutter 8810 +bullies 8811 +penalty 8812 +scattered 8813 +owe 8814 +lawn 8815 +upbringing 8816 +increase 8817 +oblivion 8818 +fanning 8819 +shiny 8820 +cynicism 8821 +kings 8822 +hazzard 8823 +preacher 8824 +ongoing 8825 +luthor 8826 +sister's 8827 +quirks 8828 +michaels 8829 +transitions 8830 +ravishing 8831 +reno 8832 +corridors 8833 +shady 8834 +cloth 8835 +liotta 8836 +spinning 8837 +sleeper 8838 +auteur 8839 +plummer 8840 +appalled 8841 +reportedly 8842 +dodgy 8843 +todays 8844 +harilal 8845 +kilmer 8846 +blackmail 8847 +toss 8848 +distinctly 8849 +violently 8850 +ebay 8851 +limp 8852 +marines 8853 +lesbians 8854 +vaughn 8855 +bart 8856 +knocking 8857 +palma's 8858 +boost 8859 +aboard 8860 +defy 8861 +civilians 8862 +brunette 8863 +fewer 8864 +cinematographic 8865 +liberties 8866 +shrill 8867 +youngsters 8868 +strain 8869 +hammerhead 8870 +inhabit 8871 +thug 8872 +dyke 8873 +euro 8874 +cassie 8875 +fellini 8876 +puzzled 8877 +chop 8878 +sweeping 8879 +throats 8880 +thirds 8881 +billion 8882 +witted 8883 +operating 8884 +atomic 8885 +lt 8886 +supportive 8887 +henderson 8888 +profit 8889 +prolific 8890 +sore 8891 +virginity 8892 +sleepy 8893 +golf 8894 +outlaw 8895 +unnerving 8896 +expresses 8897 +mills 8898 +forsythe 8899 +authors 8900 +behaving 8901 +visconti 8902 +efficient 8903 +visceral 8904 +glow 8905 +jones' 8906 +melinda 8907 +muscle 8908 +pepper 8909 +heavenly 8910 +unwilling 8911 +1965 8912 +roach 8913 +marcus 8914 +tables 8915 +shelves 8916 +dunne 8917 +tedium 8918 +illustrated 8919 +explanations 8920 +snowy 8921 +patriotic 8922 +alcoholism 8923 +whipped 8924 +ledger 8925 +slaughtered 8926 +redford 8927 +percent 8928 +rapes 8929 +disasters 8930 +dickinson 8931 +examined 8932 +cradle 8933 +fleeing 8934 +healing 8935 +lightly 8936 +nerdy 8937 +torch 8938 +rodney 8939 +believer 8940 +teddy 8941 +meyers 8942 +lorre 8943 +denver 8944 +dangers 8945 +architect 8946 +vulnerability 8947 +knives 8948 +dillon 8949 +goo 8950 +numbingly 8951 +inch 8952 +compositions 8953 +flipping 8954 +amoral 8955 +wrath 8956 +rack 8957 +imply 8958 +bonds 8959 +pistol 8960 +perceived 8961 +aura 8962 +tobe 8963 +seventh 8964 +verhoeven's 8965 +insignificant 8966 +simpler 8967 +shatner 8968 +mac 8969 +kornbluth 8970 +barbarian 8971 +zoom 8972 +proudly 8973 +hawaii 8974 +hustler 8975 +penguin 8976 +supports 8977 +thumb 8978 +segal 8979 +fulfill 8980 +bothering 8981 +jurassic 8982 +compromise 8983 +annoyingly 8984 +kenny 8985 +scandal 8986 +overtly 8987 +fleeting 8988 +metropolis 8989 +guru 8990 +rotting 8991 +sixteen 8992 +deadpan 8993 +retrieve 8994 +moderately 8995 +chat 8996 +lang 8997 +simon's 8998 +illusion 8999 +heartless 9000 +backwoods 9001 +climate 9002 +righteous 9003 +beth 9004 +grisly 9005 +prejudices 9006 +immigrants 9007 +alienation 9008 +muscular 9009 +astonishingly 9010 +doses 9011 +traveled 9012 +happier 9013 +electricity 9014 +succession 9015 +cousins 9016 +mandatory 9017 +dental 9018 +breakthrough 9019 +freaked 9020 +clockwork 9021 +ursula 9022 +recurring 9023 +notions 9024 +mechanic 9025 +recovering 9026 +zhang 9027 +comprised 9028 +coverage 9029 +elder 9030 +afghanistan 9031 +trendy 9032 +keeper 9033 +hungarian 9034 +attributes 9035 +brennan 9036 +protecting 9037 +priests 9038 +aztec 9039 +ranger 9040 +recipe 9041 +vienna 9042 +ogre 9043 +farnsworth 9044 +tasks 9045 +romero's 9046 +purse 9047 +subtitled 9048 +lansbury 9049 +pickup 9050 +pals 9051 +unconscious 9052 +animators 9053 +legion 9054 +meanings 9055 +needlessly 9056 +sleuth 9057 +association 9058 +slips 9059 +doris 9060 +pond 9061 +improvised 9062 +relates 9063 +mcdowell 9064 +volumes 9065 +ranging 9066 +zany 9067 +irresistible 9068 +elisha 9069 +herrings 9070 +coppola 9071 +prolonged 9072 +relaxing 9073 +1931 9074 +1938 9075 +rudd 9076 +heir 9077 +innuendo 9078 +urgency 9079 +bloke 9080 +flamboyant 9081 +muriel 9082 +prophet 9083 +reruns 9084 +christensen 9085 +lure 9086 +cracker 9087 +levy 9088 +shakespearean 9089 +encourages 9090 +mockery 9091 +swords 9092 +penis 9093 +pam 9094 +welcomed 9095 +rugged 9096 +academic 9097 +honeymoon 9098 +climbs 9099 +snatch 9100 +overwhelmed 9101 +gays 9102 +roommates 9103 +jolly 9104 +heavens 9105 +placing 9106 +watered 9107 +fable 9108 +zealand 9109 +carnival 9110 +gee 9111 +archer 9112 +locales 9113 +thorn 9114 +smarmy 9115 +kiddie 9116 +farewell 9117 +cheat 9118 +hopeful 9119 +backdrops 9120 +treating 9121 +kamal 9122 +irresponsible 9123 +behalf 9124 +benoit 9125 +unemployed 9126 +backyard 9127 +norton 9128 +stumbling 9129 +theirs 9130 +anonymous 9131 +temporary 9132 +distinguished 9133 +moore's 9134 +inhabited 9135 +wwi 9136 +eastwood's 9137 +pranks 9138 +custody 9139 +yearning 9140 +interspersed 9141 +agatha 9142 +chocolate 9143 +hug 9144 +guided 9145 +martino 9146 +steamy 9147 +feared 9148 +opponents 9149 +crawl 9150 +mans 9151 +jew 9152 +bombing 9153 +assortment 9154 +poke 9155 +imitating 9156 +management 9157 +keitel 9158 +frenzy 9159 +mcadams 9160 +architecture 9161 +spitting 9162 +48 9163 +hector 9164 +fitzgerald 9165 +rko 9166 +redgrave 9167 +induced 9168 +plants 9169 +rusty 9170 +janitor 9171 +weaver 9172 +recreate 9173 +islam 9174 +rogue 9175 +roads 9176 +rewrite 9177 +dodge 9178 +balloon 9179 +honey 9180 +neeson 9181 +conquest 9182 +slug 9183 +wolves 9184 +neglect 9185 +shawn 9186 +concentrated 9187 +tested 9188 +existential 9189 +expanded 9190 +worldwide 9191 +truthful 9192 +unlucky 9193 +liz 9194 +compassionate 9195 +limbs 9196 +impeccable 9197 +dogma 9198 +shattering 9199 +sailors 9200 +peterson 9201 +jock 9202 +rizzo 9203 +kalifornia 9204 +mcdermott 9205 +versatile 9206 +400 9207 +michael's 9208 +naval 9209 +burden 9210 +cheung 9211 +largest 9212 +culkin 9213 +retelling 9214 +muted 9215 +leaps 9216 +theo 9217 +passive 9218 +bucket 9219 +pertwee 9220 +eddy 9221 +rapture 9222 +continuous 9223 +gage 9224 +stretches 9225 +giggle 9226 +marx 9227 +concludes 9228 +stalks 9229 +amok 9230 +adequately 9231 +melt 9232 +stature 9233 +counted 9234 +borderline 9235 +mastermind 9236 +boxes 9237 +posh 9238 +taker 9239 +counterpart 9240 +izzard 9241 +straw 9242 +toe 9243 +shamelessly 9244 +crenna 9245 +tango 9246 +pour 9247 +behaves 9248 +sematary 9249 +expand 9250 +azumi 9251 +country's 9252 +stimulating 9253 +grady 9254 +expressing 9255 +payne 9256 +crass 9257 +intellect 9258 +booker 9259 +dani 9260 +parents' 9261 +lotr 9262 +miyazaki's 9263 +wits 9264 +waving 9265 +traumatized 9266 +illiterate 9267 +chan's 9268 +puzzling 9269 +splitting 9270 +subtleties 9271 +seduction 9272 +condescending 9273 +rebecca 9274 +inherited 9275 +seal 9276 +consisted 9277 +stubborn 9278 +didnt 9279 +lieutenant 9280 +slows 9281 +john's 9282 +glee 9283 +honorable 9284 +'73 9285 +valerie 9286 +smoothly 9287 +poo 9288 +evolved 9289 +darling 9290 +planted 9291 +mold 9292 +supremacy 9293 +opener 9294 +seuss 9295 +craven's 9296 +celine 9297 +hesitate 9298 +conception 9299 +supporters 9300 +revolting 9301 +practices 9302 +orgy 9303 +cheaper 9304 +town's 9305 +forgivable 9306 +nutty 9307 +speechless 9308 +nailed 9309 +associates 9310 +platoon 9311 +disdain 9312 +waits 9313 +knox 9314 +it´s 9315 +collecting 9316 +alligator 9317 +hispanic 9318 +mutated 9319 +woven 9320 +hardest 9321 +lubitsch 9322 +january 9323 +apprentice 9324 +uber 9325 +sarne 9326 +pets 9327 +fawcett 9328 +marred 9329 +elevate 9330 +drivers 9331 +creepiness 9332 +revive 9333 +harlem 9334 +vivah 9335 +kindness 9336 +marathon 9337 +bishop 9338 +gannon 9339 +carole 9340 +brits 9341 +submit 9342 +embarrass 9343 +boyfriends 9344 +dreadfully 9345 +oppressive 9346 +discernible 9347 +intruder 9348 +tourists 9349 +conduct 9350 +rehearsal 9351 +bolivia 9352 +astronaut 9353 +joanna 9354 +grounded 9355 +sessions 9356 +cocktail 9357 +stir 9358 +gimmicks 9359 +archive 9360 +stereotyping 9361 +aweigh 9362 +18th 9363 +undeveloped 9364 +rico 9365 +concentrates 9366 +bruckheimer 9367 +psychiatric 9368 +incompetence 9369 +villagers 9370 +customs 9371 +alienate 9372 +slew 9373 +footsteps 9374 +approximately 9375 +discussions 9376 +blink 9377 +vault 9378 +transformers 9379 +sloane 9380 +choke 9381 +infidelity 9382 +relied 9383 +undertaker 9384 +lovingly 9385 +casually 9386 +luzhin 9387 +disappearing 9388 +historians 9389 +shaolin 9390 +mastroianni 9391 +midler 9392 +atrocities 9393 +bash 9394 +inc 9395 +hedy 9396 +drums 9397 +bonding 9398 +entertainer 9399 +revelations 9400 +holland 9401 +floriane 9402 +downtown 9403 +denied 9404 +connor 9405 +stupidest 9406 +tel 9407 +sinatra's 9408 +lyrical 9409 +woke 9410 +knack 9411 +dripping 9412 +saddest 9413 +loathing 9414 +insects 9415 +hoover 9416 +apologize 9417 +premises 9418 +elmer 9419 +screamed 9420 +lecture 9421 +skipping 9422 +bursts 9423 +noam 9424 +passions 9425 +cocky 9426 +prevalent 9427 +regrets 9428 +suspended 9429 +shack 9430 +democracy 9431 +overacts 9432 +enhances 9433 +deathstalker 9434 +1960 9435 +choreographer 9436 +keeler 9437 +cillian 9438 +contemplate 9439 +smarter 9440 +marlene 9441 +philadelphia 9442 +sammi 9443 +kingsley 9444 +micheal 9445 +mpaa 9446 +duryea 9447 +creeps 9448 +capsule 9449 +converted 9450 +zabriskie 9451 +perceive 9452 +confronting 9453 +administration 9454 +arizona 9455 +viggo 9456 +ecstasy 9457 +candidates 9458 +branch 9459 +passenger 9460 +benson 9461 +sans 9462 +victoria's 9463 +callahan 9464 +intestines 9465 +swamp 9466 +sparse 9467 +request 9468 +overseas 9469 +bass 9470 +surpasses 9471 +organs 9472 +rohmer 9473 +montages 9474 +joshua 9475 +ella 9476 +maguire 9477 +rhys 9478 +cloud 9479 +stripped 9480 +rushes 9481 +kentucky 9482 +tensions 9483 +mom's 9484 +operas 9485 +chapters 9486 +monstrous 9487 +usage 9488 +fugitive 9489 +shaun 9490 +slipped 9491 +documents 9492 +email 9493 +classified 9494 +norwegian 9495 +reception 9496 +ash 9497 +sacrificed 9498 +switzerland 9499 +rightfully 9500 +cruella 9501 +psychologically 9502 +bury 9503 +liar 9504 +clumsily 9505 +crow 9506 +mindset 9507 +untrue 9508 +barker 9509 +lange 9510 +toro 9511 +ahmad 9512 +wipe 9513 +sixty 9514 +brink 9515 +insanely 9516 +mourning 9517 +vets 9518 +wu 9519 +1956 9520 +restless 9521 +loop 9522 +fanatics 9523 +rests 9524 +guevara 9525 +connecting 9526 +city's 9527 +friendships 9528 +satellite 9529 +empathize 9530 +surfers 9531 +immersed 9532 +mostel 9533 +squeeze 9534 +backing 9535 +admirably 9536 +confirm 9537 +equals 9538 +vengeful 9539 +pauses 9540 +snippets 9541 +mamet 9542 +that'll 9543 +anchorman 9544 +dense 9545 +strikingly 9546 +daphne 9547 +misplaced 9548 +1941 9549 +streak 9550 +shrink 9551 +garnered 9552 +breathless 9553 +hiv 9554 +delve 9555 +grain 9556 +spectrum 9557 +dusty 9558 +durbin 9559 +locks 9560 +november 9561 +o'neill 9562 +crook 9563 +render 9564 +participation 9565 +deception 9566 +replay 9567 +apartments 9568 +sr 9569 +lawyers 9570 +requisite 9571 +telly 9572 +basil 9573 +kinky 9574 +assist 9575 +spectacularly 9576 +scantily 9577 +prevented 9578 +obscene 9579 +reincarnation 9580 +morgana 9581 +bout 9582 +looney 9583 +adventurous 9584 +sykes 9585 +maverick 9586 +lucio 9587 +travelling 9588 +diabolical 9589 +capt 9590 +promotion 9591 +partial 9592 +eater 9593 +dime 9594 +bathing 9595 +criminally 9596 +underdog 9597 +interpret 9598 +suggestive 9599 +springs 9600 +graves 9601 +spielberg's 9602 +technological 9603 +wan 9604 +cortez 9605 +proverbial 9606 +granger 9607 +phrases 9608 +societies 9609 +thankful 9610 +palette 9611 +outrage 9612 +betrays 9613 +lung 9614 +marquis 9615 +ing 9616 +regal 9617 +oriental 9618 +duties 9619 +whacked 9620 +kerr 9621 +documented 9622 +700 9623 +stoic 9624 +fairytale 9625 +listing 9626 +acknowledged 9627 +allison 9628 +matching 9629 +longtime 9630 +garcia 9631 +elliot 9632 +33 9633 +adopt 9634 +flea 9635 +carlito's 9636 +1940's 9637 +coleman 9638 +draft 9639 +witless 9640 +kramer 9641 +haha 9642 +lap 9643 +alternately 9644 +1930 9645 +sentenced 9646 +harry's 9647 +daisies 9648 +overt 9649 +mining 9650 +stepped 9651 +eliminate 9652 +chains 9653 +regain 9654 +nuance 9655 +italians 9656 +hurting 9657 +honour 9658 +sealed 9659 +societal 9660 +indifference 9661 +lombard 9662 +teamed 9663 +cathy 9664 +its' 9665 +unfinished 9666 +floors 9667 +downside 9668 +tucker 9669 +paperhouse 9670 +compound 9671 +eggs 9672 +underused 9673 +incarnation 9674 +hunk 9675 +goer 9676 +presumed 9677 +caruso 9678 +interpreted 9679 +colourful 9680 +stills 9681 +caroline 9682 +keyboard 9683 +claw 9684 +snappy 9685 +camps 9686 +crop 9687 +sheet 9688 +overnight 9689 +dung 9690 +booze 9691 +risks 9692 +rub 9693 +oddball 9694 +exhibit 9695 +anchor 9696 +fireworks 9697 +batwoman 9698 +gesture 9699 +skinned 9700 +undertones 9701 +achieving 9702 +lanza 9703 +goofs 9704 +flee 9705 +recalls 9706 +stable 9707 +fantastically 9708 +exposing 9709 +shakes 9710 +addressing 9711 +prototype 9712 +carface 9713 +hes 9714 +competently 9715 +retain 9716 +schemes 9717 +hogan 9718 +voting 9719 +episodic 9720 +occurring 9721 +topped 9722 +1954 9723 +norma 9724 +chore 9725 +chang 9726 +shouts 9727 +rainer 9728 +colonial 9729 +recreation 9730 +forum 9731 +companions 9732 +apologies 9733 +insulted 9734 +holidays 9735 +throwaway 9736 +tepid 9737 +darkest 9738 +pulse 9739 +pita 9740 +superiors 9741 +grumpy 9742 +illustrates 9743 +sweetheart 9744 +showtime 9745 +aiello 9746 +btk 9747 +cbc 9748 +baseketball 9749 +horizon 9750 +eliminated 9751 +weirdo 9752 +welch 9753 +stepping 9754 +leno 9755 +beau 9756 +affections 9757 +leopold 9758 +inheritance 9759 +masturbation 9760 +itchy 9761 +locker 9762 +universally 9763 +shadowy 9764 +employ 9765 +skywalker 9766 +grips 9767 +gardens 9768 +sorvino 9769 +expertise 9770 +irwin 9771 +t'aime 9772 +babysitter 9773 +bryan 9774 +positions 9775 +coarse 9776 +tremors 9777 +iceberg 9778 +monumental 9779 +thinner 9780 +allegedly 9781 +dominick 9782 +allied 9783 +bogdanovich 9784 +raving 9785 +supplies 9786 +kaufman 9787 +sacred 9788 +shootings 9789 +primal 9790 +hiring 9791 +hockey 9792 +flamenco 9793 +thirteen 9794 +carlito 9795 +polite 9796 +exudes 9797 +gaining 9798 +darius 9799 +quarters 9800 +willem 9801 +crummy 9802 +duff 9803 +sorta 9804 +rigid 9805 +eponymous 9806 +smitten 9807 +attributed 9808 +variations 9809 +mischievous 9810 +unborn 9811 +wayne's 9812 +circuit 9813 +integrated 9814 +unimpressive 9815 +carson 9816 +150 9817 +siege 9818 +endured 9819 +surrogate 9820 +gifts 9821 +practicing 9822 +disgruntled 9823 +drifter 9824 +renowned 9825 +chef 9826 +operatic 9827 +maiden 9828 +frenetic 9829 +wal 9830 +roaring 9831 +author's 9832 +wondrous 9833 +greta 9834 +gamut 9835 +marital 9836 +gym 9837 +offerings 9838 +zatoichi 9839 +emerged 9840 +exaggeration 9841 +planets 9842 +raft 9843 +connolly 9844 +mcintire 9845 +strangest 9846 +marvellous 9847 +runtime 9848 +misfire 9849 +extremes 9850 +swift 9851 +seinfeld 9852 +jackass 9853 +harmony 9854 +plantation 9855 +bravery 9856 +pavarotti 9857 +catastrophe 9858 +malcolm 9859 +portman 9860 +solving 9861 +albums 9862 +winston 9863 +corky 9864 +allegory 9865 +spears 9866 +saif 9867 +goof 9868 +outta 9869 +virtues 9870 +monstrosity 9871 +ideology 9872 +edits 9873 +celebrating 9874 +adapting 9875 +ferry 9876 +desolate 9877 +jessie 9878 +inflicted 9879 +rocker 9880 +projection 9881 +irs 9882 +cambodia 9883 +enthralled 9884 +ensuing 9885 +leia 9886 +o'toole 9887 +transferred 9888 +exposes 9889 +competing 9890 +yourselves 9891 +sentiments 9892 +kisses 9893 +stray 9894 +turgid 9895 +declares 9896 +nuns 9897 +mercilessly 9898 +it'd 9899 +exceedingly 9900 +ted's 9901 +insecure 9902 +ben's 9903 +tanks 9904 +kusturica 9905 +spaces 9906 +spliced 9907 +sheila 9908 +crowds 9909 +balcony 9910 +menu 9911 +lamas 9912 +diver 9913 +secluded 9914 +integral 9915 +redeemed 9916 +halt 9917 +decapitated 9918 +stealth 9919 +budgeted 9920 +voters 9921 +overweight 9922 +praying 9923 +stevenson 9924 +cleveland 9925 +stakes 9926 +mattei 9927 +charity 9928 +stalk 9929 +olympia 9930 +olympic 9931 +aspirations 9932 +decoration 9933 +slack 9934 +bullying 9935 +bum 9936 +mo 9937 +capitalize 9938 +jameson 9939 +skimpy 9940 +wicker 9941 +starving 9942 +frenchman 9943 +frye 9944 +ate 9945 +monastery 9946 +wb 9947 +hayden 9948 +banana 9949 +grandparents 9950 +vacuous 9951 +willy 9952 +darkman 9953 +neutral 9954 +rumors 9955 +somber 9956 +aunts 9957 +amateurs 9958 +radar 9959 +ounce 9960 +bagdad 9961 +stud 9962 +closeups 9963 +insisted 9964 +jed 9965 +geeky 9966 +64 9967 +aims 9968 +complains 9969 +ewan 9970 +exhausted 9971 +day's 9972 +weaves 9973 +gladly 9974 +misogynistic 9975 +soles 9976 +michel 9977 +uniquely 9978 +interminable 9979 +aristocrat 9980 +paul's 9981 +everybody's 9982 +avant 9983 +answering 9984 +smallest 9985 +contacts 9986 +enlightenment 9987 +murphy's 9988 +employs 9989 +unforgivable 9990 +punchline 9991 +culminating 9992 +talentless 9993 +grabbing 9994 +soulless 9995 +unfairly 9996 +grail 9997 +retrospect 9998 +edged 9999 diff --git a/mediapipe/tasks/testdata/text/BUILD b/mediapipe/tasks/testdata/text/BUILD index 14999a03e..081e63c2c 100644 --- a/mediapipe/tasks/testdata/text/BUILD +++ b/mediapipe/tasks/testdata/text/BUILD @@ -28,6 +28,7 @@ mediapipe_files(srcs = [ "bert_text_classifier.tflite", "mobilebert_embedding_with_metadata.tflite", "mobilebert_with_metadata.tflite", + "regex_one_embedding_with_metadata.tflite", "test_model_text_classifier_bool_output.tflite", "test_model_text_classifier_with_regex_tokenizer.tflite", "universal_sentence_encoder_qa_with_metadata.tflite", @@ -92,6 +93,11 @@ filegroup( srcs = ["mobilebert_embedding_with_metadata.tflite"], ) +filegroup( + name = "regex_embedding_with_metadata", + srcs = ["regex_one_embedding_with_metadata.tflite"], +) + filegroup( name = "universal_sentence_encoder_qa", data = ["universal_sentence_encoder_qa_with_metadata.tflite"], diff --git a/mediapipe/tasks/testdata/vision/BUILD b/mediapipe/tasks/testdata/vision/BUILD index e23c4a66c..55d386185 100644 --- a/mediapipe/tasks/testdata/vision/BUILD +++ b/mediapipe/tasks/testdata/vision/BUILD @@ -144,8 +144,13 @@ filegroup( ) # Gestures related models. Visible to model_maker. +# TODO: Upload canned gesture model and gesture embedding model to GCS after Model Card approval filegroup( name = "test_gesture_models", + srcs = [ + "hand_landmark_full.tflite", + "palm_detection_full.tflite", + ], visibility = [ "//mediapipe/model_maker:__subpackages__", "//mediapipe/tasks:internal", diff --git a/mediapipe/tasks/web/BUILD b/mediapipe/tasks/web/BUILD new file mode 100644 index 000000000..9e4b52417 --- /dev/null +++ b/mediapipe/tasks/web/BUILD @@ -0,0 +1,106 @@ +# This contains the MediaPipe Tasks NPM package definitions. + +load("//mediapipe/framework/port:build_config.bzl", "mediapipe_ts_library") +load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") +load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") + +package(default_visibility = ["//mediapipe/tasks:internal"]) + +# Audio + +mediapipe_ts_library( + name = "audio_lib", + srcs = ["audio.ts"], + deps = ["//mediapipe/tasks/web/audio:audio_lib"], +) + +rollup_bundle( + name = "audio_bundle", + config_file = "rollup.config.mjs", + entry_point = "audio.ts", + output_dir = False, + deps = [ + ":audio_lib", + "@npm//@rollup/plugin-commonjs", + "@npm//@rollup/plugin-node-resolve", + ], +) + +pkg_npm( + name = "audio_pkg", + package_name = "__PACKAGE_NAME__", + srcs = ["package.json"], + substitutions = { + "__PACKAGE_NAME__": "@mediapipe/tasks-audio", + "__DESCRIPTION__": "MediaPipe Audio Tasks", + "__BUNDLE__": "audio_bundle.js", + }, + tgz = "audio.tgz", + deps = [":audio_bundle"], +) + +# Text + +mediapipe_ts_library( + name = "text_lib", + srcs = ["text.ts"], + deps = ["//mediapipe/tasks/web/text:text_lib"], +) + +rollup_bundle( + name = "text_bundle", + config_file = "rollup.config.mjs", + entry_point = "text.ts", + output_dir = False, + deps = [ + ":text_lib", + "@npm//@rollup/plugin-commonjs", + "@npm//@rollup/plugin-node-resolve", + ], +) + +pkg_npm( + name = "text_pkg", + package_name = "__PACKAGE_NAME__", + srcs = ["package.json"], + substitutions = { + "__PACKAGE_NAME__": "@mediapipe/tasks-text", + "__DESCRIPTION__": "MediaPipe Text Tasks", + "__BUNDLE__": "text_bundle.js", + }, + tgz = "text.tgz", + deps = [":text_bundle"], +) + +# Vision + +mediapipe_ts_library( + name = "vision_lib", + srcs = ["vision.ts"], + deps = ["//mediapipe/tasks/web/vision:vision_lib"], +) + +rollup_bundle( + name = "vision_bundle", + config_file = "rollup.config.mjs", + entry_point = "vision.ts", + output_dir = False, + deps = [ + ":vision_lib", + "@npm//@rollup/plugin-commonjs", + "@npm//@rollup/plugin-node-resolve", + ], +) + +pkg_npm( + name = "vision_pkg", + package_name = "__PACKAGE_NAME__", + srcs = ["package.json"], + substitutions = { + "__PACKAGE_NAME__": "@mediapipe/tasks-vision", + "__DESCRIPTION__": "MediaPipe Vision Tasks", + "__BUNDLE__": "vision_bundle.js", + }, + tgz = "vision.tgz", + deps = [":vision_bundle"], +) diff --git a/mediapipe/tasks/web/audio.ts b/mediapipe/tasks/web/audio.ts new file mode 100644 index 000000000..4a3b80594 --- /dev/null +++ b/mediapipe/tasks/web/audio.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2022 The MediaPipe Authors. All Rights Reserved. + * + * 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. + */ + +export * from '../../tasks/web/audio/index'; diff --git a/mediapipe/tasks/web/audio/BUILD b/mediapipe/tasks/web/audio/BUILD index 0263738e6..4f6e48b28 100644 --- a/mediapipe/tasks/web/audio/BUILD +++ b/mediapipe/tasks/web/audio/BUILD @@ -2,6 +2,8 @@ load("//mediapipe/framework/port:build_config.bzl", "mediapipe_ts_library") +package(default_visibility = ["//mediapipe/tasks:internal"]) + mediapipe_ts_library( name = "audio_lib", srcs = ["index.ts"], diff --git a/mediapipe/tasks/web/audio/audio_classifier/audio_classifier.ts b/mediapipe/tasks/web/audio/audio_classifier/audio_classifier.ts index fd79487a4..3d3ca5ae7 100644 --- a/mediapipe/tasks/web/audio/audio_classifier/audio_classifier.ts +++ b/mediapipe/tasks/web/audio/audio_classifier/audio_classifier.ts @@ -119,8 +119,8 @@ export class AudioClassifier extends TaskRunner { */ async setOptions(options: AudioClassifierOptions): Promise { if (options.baseOptions) { - const baseOptionsProto = - await convertBaseOptionsToProto(options.baseOptions); + const baseOptionsProto = await convertBaseOptionsToProto( + options.baseOptions, this.options.getBaseOptions()); this.options.setBaseOptions(baseOptionsProto); } @@ -198,7 +198,7 @@ export class AudioClassifier extends TaskRunner { classifierNode.addInputStream('AUDIO:' + AUDIO_STREAM); classifierNode.addInputStream('SAMPLE_RATE:' + SAMPLE_RATE_STREAM); classifierNode.addOutputStream( - 'CLASSIFICATION_RESULT:' + CLASSIFICATION_RESULT_STREAM); + 'CLASSIFICATIONS:' + CLASSIFICATION_RESULT_STREAM); classifierNode.setOptions(calculatorOptions); graphConfig.addNode(classifierNode); diff --git a/mediapipe/tasks/web/components/processors/BUILD b/mediapipe/tasks/web/components/processors/BUILD index e6b9adf20..cd7190dd9 100644 --- a/mediapipe/tasks/web/components/processors/BUILD +++ b/mediapipe/tasks/web/components/processors/BUILD @@ -26,6 +26,8 @@ mediapipe_ts_library( name = "base_options", srcs = ["base_options.ts"], deps = [ + "//mediapipe/calculators/tensor:inference_calculator_jspb_proto", + "//mediapipe/tasks/cc/core/proto:acceleration_jspb_proto", "//mediapipe/tasks/cc/core/proto:base_options_jspb_proto", "//mediapipe/tasks/cc/core/proto:external_file_jspb_proto", "//mediapipe/tasks/web/core", diff --git a/mediapipe/tasks/web/components/processors/base_options.ts b/mediapipe/tasks/web/components/processors/base_options.ts index 2f7d0db37..a7f7bd280 100644 --- a/mediapipe/tasks/web/components/processors/base_options.ts +++ b/mediapipe/tasks/web/components/processors/base_options.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import {InferenceCalculatorOptions} from '../../../../calculators/tensor/inference_calculator_pb'; +import {Acceleration} from '../../../../tasks/cc/core/proto/acceleration_pb'; import {BaseOptions as BaseOptionsProto} from '../../../../tasks/cc/core/proto/base_options_pb'; import {ExternalFile} from '../../../../tasks/cc/core/proto/external_file_pb'; import {BaseOptions} from '../../../../tasks/web/core/base_options'; @@ -25,26 +27,60 @@ import {BaseOptions} from '../../../../tasks/web/core/base_options'; * Converts a BaseOptions API object to its Protobuf representation. * @throws If neither a model assset path or buffer is provided */ -export async function convertBaseOptionsToProto(baseOptions: BaseOptions): - Promise { - if (baseOptions.modelAssetPath && baseOptions.modelAssetBuffer) { - throw new Error( - 'Cannot set both baseOptions.modelAssetPath and baseOptions.modelAssetBuffer'); +export async function convertBaseOptionsToProto( + updatedOptions: BaseOptions, + currentOptions?: BaseOptionsProto): Promise { + const result = + currentOptions ? currentOptions.clone() : new BaseOptionsProto(); + + await configureExternalFile(updatedOptions, result); + configureAcceleration(updatedOptions, result); + + return result; +} + +/** + * Configues the `externalFile` option and validates that a single model is + * provided. + */ +async function configureExternalFile( + options: BaseOptions, proto: BaseOptionsProto) { + const externalFile = proto.getModelAsset() || new ExternalFile(); + proto.setModelAsset(externalFile); + + if (options.modelAssetPath || options.modelAssetBuffer) { + if (options.modelAssetPath && options.modelAssetBuffer) { + throw new Error( + 'Cannot set both baseOptions.modelAssetPath and baseOptions.modelAssetBuffer'); + } + + let modelAssetBuffer = options.modelAssetBuffer; + if (!modelAssetBuffer) { + const response = await fetch(options.modelAssetPath!.toString()); + modelAssetBuffer = new Uint8Array(await response.arrayBuffer()); + } + externalFile.setFileContent(modelAssetBuffer); } - if (!baseOptions.modelAssetPath && !baseOptions.modelAssetBuffer) { + + if (!externalFile.hasFileContent()) { throw new Error( 'Either baseOptions.modelAssetPath or baseOptions.modelAssetBuffer must be set'); } - - let modelAssetBuffer = baseOptions.modelAssetBuffer; - if (!modelAssetBuffer) { - const response = await fetch(baseOptions.modelAssetPath!.toString()); - modelAssetBuffer = new Uint8Array(await response.arrayBuffer()); - } - - const proto = new BaseOptionsProto(); - const externalFile = new ExternalFile(); - externalFile.setFileContent(modelAssetBuffer); - proto.setModelAsset(externalFile); - return proto; +} + +/** Configues the `acceleration` option. */ +function configureAcceleration(options: BaseOptions, proto: BaseOptionsProto) { + if ('delegate' in options) { + const acceleration = new Acceleration(); + if (options.delegate === 'cpu') { + acceleration.setXnnpack( + new InferenceCalculatorOptions.Delegate.Xnnpack()); + proto.setAcceleration(acceleration); + } else if (options.delegate === 'gpu') { + acceleration.setGpu(new InferenceCalculatorOptions.Delegate.Gpu()); + proto.setAcceleration(acceleration); + } else { + proto.clearAcceleration(); + } + } } diff --git a/mediapipe/tasks/web/core/base_options.d.ts b/mediapipe/tasks/web/core/base_options.d.ts index 02a288a87..54a59a21b 100644 --- a/mediapipe/tasks/web/core/base_options.d.ts +++ b/mediapipe/tasks/web/core/base_options.d.ts @@ -22,10 +22,14 @@ export interface BaseOptions { * The model path to the model asset file. Only one of `modelAssetPath` or * `modelAssetBuffer` can be set. */ - modelAssetPath?: string; + modelAssetPath?: string|undefined; + /** * A buffer containing the model aaset. Only one of `modelAssetPath` or * `modelAssetBuffer` can be set. */ - modelAssetBuffer?: Uint8Array; + modelAssetBuffer?: Uint8Array|undefined; + + /** Overrides the default backend to use for the provided model. */ + delegate?: 'cpu'|'gpu'|undefined; } diff --git a/mediapipe/tasks/web/package.json b/mediapipe/tasks/web/package.json new file mode 100644 index 000000000..d3bd8b669 --- /dev/null +++ b/mediapipe/tasks/web/package.json @@ -0,0 +1,15 @@ +{ + "name": "__PACKAGE_NAME__", + "version": "__VERSION__", + "description": "__DESCRIPTION__", + "main": "__BUNDLE__", + "module": "__BUNDLE__", + "author": "mediapipe@google.com", + "license": "Apache-2.0", + "type": "module", + "dependencies": { + "google-protobuf": "^3.21.2" + }, + "homepage": "http://mediapipe.dev", + "keywords": [ "AR", "ML", "Augmented", "MediaPipe", "MediaPipe Tasks" ] +} diff --git a/mediapipe/tasks/web/rollup.config.mjs b/mediapipe/tasks/web/rollup.config.mjs new file mode 100644 index 000000000..392b235fc --- /dev/null +++ b/mediapipe/tasks/web/rollup.config.mjs @@ -0,0 +1,9 @@ +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; + +export default { + plugins: [ + resolve(), + commonjs() + ] +} diff --git a/mediapipe/tasks/web/text.ts b/mediapipe/tasks/web/text.ts new file mode 100644 index 000000000..f8a0b6457 --- /dev/null +++ b/mediapipe/tasks/web/text.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2022 The MediaPipe Authors. All Rights Reserved. + * + * 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. + */ + +export * from '../../tasks/web/text/index'; diff --git a/mediapipe/tasks/web/text/BUILD b/mediapipe/tasks/web/text/BUILD index d3a797f83..a369d0af0 100644 --- a/mediapipe/tasks/web/text/BUILD +++ b/mediapipe/tasks/web/text/BUILD @@ -2,6 +2,8 @@ load("//mediapipe/framework/port:build_config.bzl", "mediapipe_ts_library") +package(default_visibility = ["//mediapipe/tasks:internal"]) + mediapipe_ts_library( name = "text_lib", srcs = ["index.ts"], diff --git a/mediapipe/tasks/web/text/text_classifier/BUILD b/mediapipe/tasks/web/text/text_classifier/BUILD index e984a9554..25a8817d4 100644 --- a/mediapipe/tasks/web/text/text_classifier/BUILD +++ b/mediapipe/tasks/web/text/text_classifier/BUILD @@ -13,8 +13,8 @@ mediapipe_ts_library( name = "text_classifier", srcs = [ "text_classifier.ts", - "text_classifier_options.d.ts", - "text_classifier_result.d.ts", + "text_classifier_options.ts", + "text_classifier_result.ts", ], deps = [ "//mediapipe/framework:calculator_jspb_proto", diff --git a/mediapipe/tasks/web/text/text_classifier/text_classifier.ts b/mediapipe/tasks/web/text/text_classifier/text_classifier.ts index ff36bb9e0..d92248b80 100644 --- a/mediapipe/tasks/web/text/text_classifier/text_classifier.ts +++ b/mediapipe/tasks/web/text/text_classifier/text_classifier.ts @@ -111,8 +111,8 @@ export class TextClassifier extends TaskRunner { */ async setOptions(options: TextClassifierOptions): Promise { if (options.baseOptions) { - const baseOptionsProto = - await convertBaseOptionsToProto(options.baseOptions); + const baseOptionsProto = await convertBaseOptionsToProto( + options.baseOptions, this.options.getBaseOptions()); this.options.setBaseOptions(baseOptionsProto); } diff --git a/mediapipe/tasks/web/text/text_classifier/text_classifier_options.d.ts b/mediapipe/tasks/web/text/text_classifier/text_classifier_options.ts similarity index 100% rename from mediapipe/tasks/web/text/text_classifier/text_classifier_options.d.ts rename to mediapipe/tasks/web/text/text_classifier/text_classifier_options.ts diff --git a/mediapipe/tasks/web/text/text_classifier/text_classifier_result.d.ts b/mediapipe/tasks/web/text/text_classifier/text_classifier_result.ts similarity index 100% rename from mediapipe/tasks/web/text/text_classifier/text_classifier_result.d.ts rename to mediapipe/tasks/web/text/text_classifier/text_classifier_result.ts diff --git a/mediapipe/tasks/web/vision.ts b/mediapipe/tasks/web/vision.ts new file mode 100644 index 000000000..6ff8f725b --- /dev/null +++ b/mediapipe/tasks/web/vision.ts @@ -0,0 +1,17 @@ +/** + * Copyright 2022 The MediaPipe Authors. All Rights Reserved. + * + * 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. + */ + +export * from '../../tasks/web/vision/index'; diff --git a/mediapipe/tasks/web/vision/BUILD b/mediapipe/tasks/web/vision/BUILD index 3684f88ef..abdbc54ea 100644 --- a/mediapipe/tasks/web/vision/BUILD +++ b/mediapipe/tasks/web/vision/BUILD @@ -2,6 +2,8 @@ load("//mediapipe/framework/port:build_config.bzl", "mediapipe_ts_library") +package(default_visibility = ["//mediapipe/tasks:internal"]) + mediapipe_ts_library( name = "vision_lib", srcs = ["index.ts"], diff --git a/mediapipe/tasks/web/vision/gesture_recognizer/BUILD b/mediapipe/tasks/web/vision/gesture_recognizer/BUILD index 8988c4794..6b99f6ce4 100644 --- a/mediapipe/tasks/web/vision/gesture_recognizer/BUILD +++ b/mediapipe/tasks/web/vision/gesture_recognizer/BUILD @@ -13,8 +13,8 @@ mediapipe_ts_library( name = "gesture_recognizer", srcs = [ "gesture_recognizer.ts", - "gesture_recognizer_options.d.ts", - "gesture_recognizer_result.d.ts", + "gesture_recognizer_options.ts", + "gesture_recognizer_result.ts", ], deps = [ "//mediapipe/framework:calculator_jspb_proto", diff --git a/mediapipe/tasks/web/vision/gesture_recognizer/gesture_recognizer.ts b/mediapipe/tasks/web/vision/gesture_recognizer/gesture_recognizer.ts index ad8db1477..1275ae875 100644 --- a/mediapipe/tasks/web/vision/gesture_recognizer/gesture_recognizer.ts +++ b/mediapipe/tasks/web/vision/gesture_recognizer/gesture_recognizer.ts @@ -171,8 +171,8 @@ export class GestureRecognizer extends TaskRunner { */ async setOptions(options: GestureRecognizerOptions): Promise { if (options.baseOptions) { - const baseOptionsProto = - await convertBaseOptionsToProto(options.baseOptions); + const baseOptionsProto = await convertBaseOptionsToProto( + options.baseOptions, this.options.getBaseOptions()); this.options.setBaseOptions(baseOptionsProto); } diff --git a/mediapipe/tasks/web/vision/gesture_recognizer/gesture_recognizer_options.d.ts b/mediapipe/tasks/web/vision/gesture_recognizer/gesture_recognizer_options.ts similarity index 100% rename from mediapipe/tasks/web/vision/gesture_recognizer/gesture_recognizer_options.d.ts rename to mediapipe/tasks/web/vision/gesture_recognizer/gesture_recognizer_options.ts diff --git a/mediapipe/tasks/web/vision/gesture_recognizer/gesture_recognizer_result.d.ts b/mediapipe/tasks/web/vision/gesture_recognizer/gesture_recognizer_result.ts similarity index 100% rename from mediapipe/tasks/web/vision/gesture_recognizer/gesture_recognizer_result.d.ts rename to mediapipe/tasks/web/vision/gesture_recognizer/gesture_recognizer_result.ts diff --git a/mediapipe/tasks/web/vision/image_classifier/image_classifier.ts b/mediapipe/tasks/web/vision/image_classifier/image_classifier.ts index 39674e85c..cb63874c4 100644 --- a/mediapipe/tasks/web/vision/image_classifier/image_classifier.ts +++ b/mediapipe/tasks/web/vision/image_classifier/image_classifier.ts @@ -114,8 +114,8 @@ export class ImageClassifier extends TaskRunner { */ async setOptions(options: ImageClassifierOptions): Promise { if (options.baseOptions) { - const baseOptionsProto = - await convertBaseOptionsToProto(options.baseOptions); + const baseOptionsProto = await convertBaseOptionsToProto( + options.baseOptions, this.options.getBaseOptions()); this.options.setBaseOptions(baseOptionsProto); } diff --git a/mediapipe/tasks/web/vision/object_detector/BUILD b/mediapipe/tasks/web/vision/object_detector/BUILD index 888537bd1..095a84b52 100644 --- a/mediapipe/tasks/web/vision/object_detector/BUILD +++ b/mediapipe/tasks/web/vision/object_detector/BUILD @@ -13,8 +13,8 @@ mediapipe_ts_library( name = "object_detector", srcs = [ "object_detector.ts", - "object_detector_options.d.ts", - "object_detector_result.d.ts", + "object_detector_options.ts", + "object_detector_result.ts", ], deps = [ "//mediapipe/framework:calculator_jspb_proto", diff --git a/mediapipe/tasks/web/vision/object_detector/object_detector.ts b/mediapipe/tasks/web/vision/object_detector/object_detector.ts index c3bb21baa..022bf6301 100644 --- a/mediapipe/tasks/web/vision/object_detector/object_detector.ts +++ b/mediapipe/tasks/web/vision/object_detector/object_detector.ts @@ -112,8 +112,8 @@ export class ObjectDetector extends TaskRunner { */ async setOptions(options: ObjectDetectorOptions): Promise { if (options.baseOptions) { - const baseOptionsProto = - await convertBaseOptionsToProto(options.baseOptions); + const baseOptionsProto = await convertBaseOptionsToProto( + options.baseOptions, this.options.getBaseOptions()); this.options.setBaseOptions(baseOptionsProto); } diff --git a/mediapipe/tasks/web/vision/object_detector/object_detector_options.d.ts b/mediapipe/tasks/web/vision/object_detector/object_detector_options.ts similarity index 100% rename from mediapipe/tasks/web/vision/object_detector/object_detector_options.d.ts rename to mediapipe/tasks/web/vision/object_detector/object_detector_options.ts diff --git a/mediapipe/tasks/web/vision/object_detector/object_detector_result.d.ts b/mediapipe/tasks/web/vision/object_detector/object_detector_result.ts similarity index 100% rename from mediapipe/tasks/web/vision/object_detector/object_detector_result.d.ts rename to mediapipe/tasks/web/vision/object_detector/object_detector_result.ts diff --git a/package.json b/package.json index f8478a159..298157cbc 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,16 @@ "version": "0.0.0-alphga", "description": "MediaPipe GitHub repo", "devDependencies": { + "@bazel/rollup": "^5.7.1", "@bazel/typescript": "^5.7.1", + "@rollup/plugin-commonjs": "^23.0.2", + "@rollup/plugin-node-resolve": "^15.0.1", "@types/google-protobuf": "^3.15.6", "@types/offscreencanvas": "^2019.7.0", "google-protobuf": "^3.21.2", "protobufjs": "^7.1.2", "protobufjs-cli": "^1.0.2", + "rollup": "^2.3.0", "ts-protoc-gen": "^0.15.0", "typescript": "^4.8.4" } diff --git a/third_party/external_files.bzl b/third_party/external_files.bzl index e47dc9812..d5ddc8d78 100644 --- a/third_party/external_files.bzl +++ b/third_party/external_files.bzl @@ -346,6 +346,18 @@ def external_files(): urls = ["https://storage.googleapis.com/mediapipe-assets/input_image_tensor_unsupported_meta.json?generation=1665422835757143"], ) + http_file( + name = "com_google_mediapipe_input_text_tensor_default_meta_json", + sha256 = "9723e59960b0e6ca60d120494c32e798b054ea6e5a441b359c84f759bd2b3a36", + urls = ["https://storage.googleapis.com/mediapipe-assets/input_text_tensor_default_meta.json?generation=1667855382021347"], + ) + + http_file( + name = "com_google_mediapipe_input_text_tensor_meta_json", + sha256 = "c6782f676220e2cc89b70bacccb649fc848c18e33bedc449bf49f5d839b3cc6c", + urls = ["https://storage.googleapis.com/mediapipe-assets/input_text_tensor_meta.json?generation=1667855384891533"], + ) + http_file( name = "com_google_mediapipe_iris_and_gaze_tflite", sha256 = "b6dcb860a92a3c7264a8e50786f46cecb529672cdafc17d39c78931257da661d", @@ -391,7 +403,7 @@ def external_files(): http_file( name = "com_google_mediapipe_labels_txt", sha256 = "536feacc519de3d418de26b2effb4d75694a8c4c0063e36499a46fa8061e2da9", - urls = ["https://storage.googleapis.com/mediapipe-assets/labels.txt?generation=1665988394538324"], + urls = ["https://storage.googleapis.com/mediapipe-assets/labels.txt?generation=1667888034706429"], ) http_file( @@ -538,6 +550,24 @@ def external_files(): urls = ["https://storage.googleapis.com/mediapipe-assets/model_without_metadata.tflite?generation=1661875850966737"], ) + http_file( + name = "com_google_mediapipe_movie_review_json", + sha256 = "c09b88af05844cad5133b49744fed3a0bd514d4a1c75b9d2f23e9a40bd7bc04e", + urls = ["https://storage.googleapis.com/mediapipe-assets/movie_review.json?generation=1667888039053188"], + ) + + http_file( + name = "com_google_mediapipe_movie_review_labels_txt", + sha256 = "4b9b26392f765e7a872372131cd4cee8ad7c02e496b5a1228279619b138c4b7a", + urls = ["https://storage.googleapis.com/mediapipe-assets/movie_review_labels.txt?generation=1667888041670721"], + ) + + http_file( + name = "com_google_mediapipe_movie_review_tflite", + sha256 = "3935ee73b13d435327d05af4d6f37dc3c146e117e1c3d572ae4d2ae0f5f412fe", + urls = ["https://storage.googleapis.com/mediapipe-assets/movie_review.tflite?generation=1667855395736217"], + ) + http_file( name = "com_google_mediapipe_mozart_square_jpg", sha256 = "4feb4dadc5d6f853ade57b8c9d4c9a1f5ececd6469616c8e505f9a14823392b6", @@ -664,6 +694,18 @@ def external_files(): urls = ["https://storage.googleapis.com/mediapipe-assets/README.md?generation=1661875904887163"], ) + http_file( + name = "com_google_mediapipe_regex_one_embedding_with_metadata_tflite", + sha256 = "b8f5d6d090c2c73984b2b92cd2fda27e5630562741a93d127b9a744d60505bc0", + urls = ["https://storage.googleapis.com/mediapipe-assets/regex_one_embedding_with_metadata.tflite?generation=1667888045310541"], + ) + + http_file( + name = "com_google_mediapipe_regex_vocab_txt", + sha256 = "b1134b10927a53ce4224bbc30ccf075c9969c94ebf40c368966d1dcf445ca923", + urls = ["https://storage.googleapis.com/mediapipe-assets/regex_vocab.txt?generation=1667888047885461"], + ) + http_file( name = "com_google_mediapipe_right_hands_jpg", sha256 = "240c082e80128ff1ca8a83ce645e2ba4d8bc30f0967b7991cf5fa375bab489e1", diff --git a/yarn.lock b/yarn.lock index e6398fb1f..a5ec6fb13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,9 +3,16 @@ "@babel/parser@^7.9.4": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.1.tgz#3e045a92f7b4623cafc2425eddcb8cf2e54f9cc5" - integrity sha512-hp0AYxaZJhxULfM1zyp7Wgr+pSUKBcP3M+PHnSzWGdXOzg/kHWIgiUWARvubhUKGOEw3xqY4x+lyZ9ytBVcELw== + version "7.20.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.3.tgz#5358cf62e380cf69efcb87a7bb922ff88bfac6e2" + integrity sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg== + +"@bazel/rollup@^5.7.1": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-5.7.1.tgz#6f644c2d493a5bd9cd3724a6f239e609585c6e37" + integrity sha512-LLNogoK2Qx9GIJVywQ+V/czjud8236mnaRX//g7qbOyXoWZDQvAEgsxRHq+lS/XX9USbh+zJJlfb+Dfp/PXx4A== + dependencies: + "@bazel/worker" "5.7.1" "@bazel/typescript@^5.7.1": version "5.7.1" @@ -77,6 +84,44 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== +"@rollup/plugin-commonjs@^23.0.2": + version "23.0.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-23.0.2.tgz#3a3a5b7b1b1cb29037eb4992edcaae997d7ebd92" + integrity sha512-e9ThuiRf93YlVxc4qNIurvv+Hp9dnD+4PjOqQs5vAYfcZ3+AXSrcdzXnVjWxcGQOa6KGJFcRZyUI3ktWLavFjg== + dependencies: + "@rollup/pluginutils" "^5.0.1" + commondir "^1.0.1" + estree-walker "^2.0.2" + glob "^8.0.3" + is-reference "1.2.1" + magic-string "^0.26.4" + +"@rollup/plugin-node-resolve@^15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz#72be449b8e06f6367168d5b3cd5e2802e0248971" + integrity sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg== + dependencies: + "@rollup/pluginutils" "^5.0.1" + "@types/resolve" "1.20.2" + deepmerge "^4.2.2" + is-builtin-module "^3.2.0" + is-module "^1.0.0" + resolve "^1.22.1" + +"@rollup/pluginutils@^5.0.1": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33" + integrity sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + +"@types/estree@*", "@types/estree@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + "@types/google-protobuf@^3.15.6": version "3.15.6" resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.6.tgz#674a69493ef2c849b95eafe69167ea59079eb504" @@ -110,6 +155,11 @@ resolved "https://registry.yarnpkg.com/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz#e4a932069db47bb3eabeb0b305502d01586fa90d" integrity sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg== +"@types/resolve@1.20.2": + version "1.20.2" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" + integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -162,6 +212,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + catharsis@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.9.0.tgz#40382a168be0e6da308c277d3a2b3eb40c7d2121" @@ -189,6 +244,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -199,6 +259,11 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + entities@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" @@ -227,9 +292,9 @@ eslint-visitor-keys@^3.3.0: integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== espree@^9.0.0: - version "9.4.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a" - integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw== + version "9.4.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.1.tgz#51d6092615567a2c2cff7833445e37c28c0065bd" + integrity sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg== dependencies: acorn "^8.8.0" acorn-jsx "^5.3.2" @@ -250,6 +315,11 @@ estraverse@^5.1.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -265,6 +335,16 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + glob@^7.1.3: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -277,7 +357,7 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.0: +glob@^8.0.0, glob@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== @@ -303,6 +383,13 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -316,6 +403,32 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +is-builtin-module@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.0.tgz#bb0310dfe881f144ca83f30100ceb10cf58835e0" + integrity sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw== + dependencies: + builtin-modules "^3.3.0" + +is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + dependencies: + has "^1.0.3" + +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== + +is-reference@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" + integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== + dependencies: + "@types/estree" "*" + js2xmlparser@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-4.0.2.tgz#2a1fdf01e90585ef2ae872a01bc169c6a8d5e60a" @@ -372,9 +485,9 @@ lodash@^4.17.14, lodash@^4.17.15: integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== long@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/long/-/long-5.2.0.tgz#2696dadf4b4da2ce3f6f6b89186085d94d52fd61" - integrity sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w== + version "5.2.1" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.1.tgz#e27595d0083d103d2fa2c20c7699f8e0c92b897f" + integrity sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A== lru-cache@^6.0.0: version "6.0.0" @@ -383,6 +496,13 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +magic-string@^0.26.4: + version "0.26.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.7.tgz#caf7daf61b34e9982f8228c4527474dac8981d6f" + integrity sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow== + dependencies: + sourcemap-codec "^1.4.8" + markdown-it-anchor@^8.4.1: version "8.6.5" resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz#30c4bc5bbff327f15ce3c429010ec7ba75e7b5f8" @@ -400,9 +520,9 @@ markdown-it@^12.3.2: uc.micro "^1.0.5" marked@^4.0.10: - version "4.2.1" - resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.1.tgz#eaa32594e45b4e58c02e4d118531fd04345de3b4" - integrity sha512-VK1/jNtwqDLvPktNpL0Fdg3qoeUZhmRsuiIjPEy/lHwXW4ouLoZfO4XoWd4ClDt+hupV1VLpkZhEovjU0W/kqA== + version "4.2.2" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.2.tgz#1d2075ad6cdfe42e651ac221c32d949a26c0672a" + integrity sha512-JjBTFTAvuTgANXx82a5vzK9JLSMoV6V3LBVn4Uhdso6t7vXrGx7g1Cd2r6NYSsxrYbQGFCMqBDhFHyK5q2UvcQ== mdurl@^1.0.1: version "1.0.1" @@ -457,6 +577,16 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -503,6 +633,15 @@ requizzle@^0.2.3: dependencies: lodash "^4.17.14" +resolve@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + rimraf@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -510,6 +649,13 @@ rimraf@^3.0.0: dependencies: glob "^7.1.3" +rollup@^2.3.0: + version "2.79.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" + integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== + optionalDependencies: + fsevents "~2.3.2" + semver@5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" @@ -535,6 +681,11 @@ source-map@^0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +sourcemap-codec@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + strip-json-comments@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -547,6 +698,11 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + taffydb@2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268"