Compare commits
34
Commits
@@ -157,19 +157,19 @@ http_archive(
|
|||||||
# 2020-08-21
|
# 2020-08-21
|
||||||
http_archive(
|
http_archive(
|
||||||
name = "com_github_glog_glog",
|
name = "com_github_glog_glog",
|
||||||
strip_prefix = "glog-0.6.0",
|
strip_prefix = "glog-3a0d4d22c5ae0b9a2216988411cfa6bf860cc372",
|
||||||
sha256 = "8a83bf982f37bb70825df71a9709fa90ea9f4447fb3c099e1d720a439d88bad6",
|
sha256 = "170d08f80210b82d95563f4723a15095eff1aad1863000e8eeb569c96a98fefb",
|
||||||
urls = [
|
urls = [
|
||||||
"https://github.com/google/glog/archive/v0.6.0.tar.gz",
|
"https://github.com/google/glog/archive/3a0d4d22c5ae0b9a2216988411cfa6bf860cc372.zip",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
http_archive(
|
http_archive(
|
||||||
name = "com_github_glog_glog_no_gflags",
|
name = "com_github_glog_glog_no_gflags",
|
||||||
strip_prefix = "glog-0.6.0",
|
strip_prefix = "glog-3a0d4d22c5ae0b9a2216988411cfa6bf860cc372",
|
||||||
sha256 = "8a83bf982f37bb70825df71a9709fa90ea9f4447fb3c099e1d720a439d88bad6",
|
sha256 = "170d08f80210b82d95563f4723a15095eff1aad1863000e8eeb569c96a98fefb",
|
||||||
build_file = "@//third_party:glog_no_gflags.BUILD",
|
build_file = "@//third_party:glog_no_gflags.BUILD",
|
||||||
urls = [
|
urls = [
|
||||||
"https://github.com/google/glog/archive/v0.6.0.tar.gz",
|
"https://github.com/google/glog/archive/3a0d4d22c5ae0b9a2216988411cfa6bf860cc372.zip",
|
||||||
],
|
],
|
||||||
patches = [
|
patches = [
|
||||||
"@//third_party:com_github_glog_glog.diff",
|
"@//third_party:com_github_glog_glog.diff",
|
||||||
|
|||||||
@@ -406,8 +406,13 @@ cc_library(
|
|||||||
alwayslink = 1,
|
alwayslink = 1,
|
||||||
)
|
)
|
||||||
|
|
||||||
# This dependency removed tensorflow_jellyfish_deps and xprofilez_with_server because they failed
|
# This dependency removed the following 3 targets because they failed Boq conformance test:
|
||||||
# Boq conformance test. Weigh your use case to see if this will work for you.
|
#
|
||||||
|
# tensorflow_jellyfish_deps
|
||||||
|
# jfprof_lib
|
||||||
|
# xprofilez_with_server
|
||||||
|
#
|
||||||
|
# If you need them plz consider tensorflow_inference_calculator_no_envelope_loader.
|
||||||
cc_library(
|
cc_library(
|
||||||
name = "tensorflow_inference_calculator_for_boq",
|
name = "tensorflow_inference_calculator_for_boq",
|
||||||
srcs = ["tensorflow_inference_calculator.cc"],
|
srcs = ["tensorflow_inference_calculator.cc"],
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ package_group(
|
|||||||
package_group(
|
package_group(
|
||||||
name = "1p_client",
|
name = "1p_client",
|
||||||
packages = [
|
packages = [
|
||||||
|
"//cloud/ml/applications/vision/model_garden/model_oss/mediapipe/...",
|
||||||
"//research/privacy/learning/fl_eval/pcvr/...",
|
"//research/privacy/learning/fl_eval/pcvr/...",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ class TFRecordCacheFiles:
|
|||||||
num_shards: int = 1
|
num_shards: int = 1
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
|
if not tf.io.gfile.exists(self.cache_dir):
|
||||||
|
tf.io.gfile.makedirs(self.cache_dir)
|
||||||
if not self.cache_prefix_filename:
|
if not self.cache_prefix_filename:
|
||||||
raise ValueError('cache_prefix_filename cannot be empty.')
|
raise ValueError('cache_prefix_filename cannot be empty.')
|
||||||
if self.num_shards <= 0:
|
if self.num_shards <= 0:
|
||||||
@@ -79,8 +81,6 @@ class TFRecordCacheFiles:
|
|||||||
Returns:
|
Returns:
|
||||||
Array of TFRecordWriter objects
|
Array of TFRecordWriter objects
|
||||||
"""
|
"""
|
||||||
if not tf.io.gfile.exists(self.cache_dir):
|
|
||||||
tf.io.gfile.makedirs(self.cache_dir)
|
|
||||||
return [tf.io.TFRecordWriter(path) for path in self.tfrecord_files]
|
return [tf.io.TFRecordWriter(path) for path in self.tfrecord_files]
|
||||||
|
|
||||||
def save_metadata(self, metadata):
|
def save_metadata(self, metadata):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import Optional
|
from typing import Mapping, Optional
|
||||||
|
|
||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
|
|
||||||
@@ -36,6 +36,8 @@ class BaseHParams:
|
|||||||
steps_per_epoch: An optional integer indicate the number of training steps
|
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. If not set, the training pipeline calculates the default steps
|
||||||
per epoch as the training dataset size divided by batch size.
|
per epoch as the training dataset size divided by batch size.
|
||||||
|
class_weights: An optional mapping of indices to weights for weighting the
|
||||||
|
loss function during training.
|
||||||
shuffle: True if the dataset is shuffled before training.
|
shuffle: True if the dataset is shuffled before training.
|
||||||
export_dir: The location of the model checkpoint files.
|
export_dir: The location of the model checkpoint files.
|
||||||
distribution_strategy: A string specifying which Distribution Strategy to
|
distribution_strategy: A string specifying which Distribution Strategy to
|
||||||
@@ -57,6 +59,7 @@ class BaseHParams:
|
|||||||
batch_size: int
|
batch_size: int
|
||||||
epochs: int
|
epochs: int
|
||||||
steps_per_epoch: Optional[int] = None
|
steps_per_epoch: Optional[int] = None
|
||||||
|
class_weights: Optional[Mapping[int, float]] = None
|
||||||
|
|
||||||
# Dataset-related parameters
|
# Dataset-related parameters
|
||||||
shuffle: bool = False
|
shuffle: bool = False
|
||||||
|
|||||||
@@ -110,7 +110,9 @@ class Classifier(custom_model.CustomModel):
|
|||||||
# dataset is exhausted even if there are epochs remaining.
|
# dataset is exhausted even if there are epochs remaining.
|
||||||
steps_per_epoch=None,
|
steps_per_epoch=None,
|
||||||
validation_data=validation_dataset,
|
validation_data=validation_dataset,
|
||||||
callbacks=self._callbacks)
|
callbacks=self._callbacks,
|
||||||
|
class_weight=self._hparams.class_weights,
|
||||||
|
)
|
||||||
|
|
||||||
def evaluate(self, data: dataset.Dataset, batch_size: int = 32) -> Any:
|
def evaluate(self, data: dataset.Dataset, batch_size: int = 32) -> Any:
|
||||||
"""Evaluates the classifier with the provided evaluation dataset.
|
"""Evaluates the classifier with the provided evaluation dataset.
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class FocalLoss(tf.keras.losses.Loss):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, gamma, class_weight: Optional[Sequence[float]] = None):
|
def __init__(self, gamma, class_weight: Optional[Sequence[float]] = None):
|
||||||
"""Constructor.
|
"""Initializes FocalLoss.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
gamma: Focal loss gamma, as described in class docs.
|
gamma: Focal loss gamma, as described in class docs.
|
||||||
@@ -115,6 +115,51 @@ class FocalLoss(tf.keras.losses.Loss):
|
|||||||
return tf.reduce_sum(losses) / batch_size
|
return tf.reduce_sum(losses) / batch_size
|
||||||
|
|
||||||
|
|
||||||
|
class SparseFocalLoss(FocalLoss):
|
||||||
|
"""Sparse implementation of Focal Loss.
|
||||||
|
|
||||||
|
This is the same as FocalLoss, except the labels are expected to be class ids
|
||||||
|
instead of 1-hot encoded vectors. See FocalLoss class documentation defined
|
||||||
|
in this same file for more details.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
>>> y_true = [1, 2]
|
||||||
|
>>> y_pred = [[0.05, 0.95, 0], [0.1, 0.8, 0.1]]
|
||||||
|
>>> gamma = 2
|
||||||
|
>>> focal_loss = SparseFocalLoss(gamma, 3)
|
||||||
|
>>> focal_loss(y_true, y_pred).numpy()
|
||||||
|
0.9326
|
||||||
|
|
||||||
|
>>> # Calling with 'sample_weight'.
|
||||||
|
>>> focal_loss(y_true, y_pred, sample_weight=tf.constant([0.3, 0.7])).numpy()
|
||||||
|
0.6528
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, gamma, num_classes, class_weight: Optional[Sequence[float]] = None
|
||||||
|
):
|
||||||
|
"""Initializes SparseFocalLoss.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gamma: Focal loss gamma, as described in class docs.
|
||||||
|
num_classes: Number of classes.
|
||||||
|
class_weight: A weight to apply to the loss, one for each class. The
|
||||||
|
weight is applied for each input where the ground truth label matches.
|
||||||
|
"""
|
||||||
|
super().__init__(gamma, class_weight=class_weight)
|
||||||
|
self._num_classes = num_classes
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
y_true: tf.Tensor,
|
||||||
|
y_pred: tf.Tensor,
|
||||||
|
sample_weight: Optional[tf.Tensor] = None,
|
||||||
|
) -> tf.Tensor:
|
||||||
|
y_true = tf.cast(tf.reshape(y_true, [-1]), tf.int32)
|
||||||
|
y_true_one_hot = tf.one_hot(y_true, self._num_classes)
|
||||||
|
return super().__call__(y_true_one_hot, y_pred, sample_weight=sample_weight)
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class PerceptualLossWeight:
|
class PerceptualLossWeight:
|
||||||
"""The weight for each perceptual loss.
|
"""The weight for each perceptual loss.
|
||||||
|
|||||||
@@ -101,6 +101,23 @@ class FocalLossTest(tf.test.TestCase, parameterized.TestCase):
|
|||||||
self.assertNear(loss, expected_loss, 1e-4)
|
self.assertNear(loss, expected_loss, 1e-4)
|
||||||
|
|
||||||
|
|
||||||
|
class SparseFocalLossTest(tf.test.TestCase):
|
||||||
|
|
||||||
|
def test_sparse_focal_loss_matches_focal_loss(self):
|
||||||
|
num_classes = 2
|
||||||
|
y_pred = tf.constant([[0.8, 0.2], [0.3, 0.7]])
|
||||||
|
y_true = tf.constant([1, 0])
|
||||||
|
y_true_one_hot = tf.one_hot(y_true, num_classes)
|
||||||
|
for gamma in [0.0, 0.5, 1.0]:
|
||||||
|
expected_loss_fn = loss_functions.FocalLoss(gamma=gamma)
|
||||||
|
loss_fn = loss_functions.SparseFocalLoss(
|
||||||
|
gamma=gamma, num_classes=num_classes
|
||||||
|
)
|
||||||
|
expected_loss = expected_loss_fn(y_true_one_hot, y_pred)
|
||||||
|
loss = loss_fn(y_true, y_pred)
|
||||||
|
self.assertNear(loss, expected_loss, 1e-4)
|
||||||
|
|
||||||
|
|
||||||
class MockPerceptualLoss(loss_functions.PerceptualLoss):
|
class MockPerceptualLoss(loss_functions.PerceptualLoss):
|
||||||
"""A mock class with implementation of abstract methods for testing."""
|
"""A mock class with implementation of abstract methods for testing."""
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
# Placeholder for internal Python strict library and test compatibility macro.
|
# Placeholder for internal Python strict binary and library compatibility macro.
|
||||||
# Placeholder for internal Python strict test compatibility macro.
|
# Placeholder for internal Python strict test compatibility macro.
|
||||||
|
|
||||||
package(default_visibility = ["//mediapipe:__subpackages__"])
|
package(default_visibility = ["//mediapipe:__subpackages__"])
|
||||||
@@ -76,7 +76,10 @@ py_test(
|
|||||||
py_library(
|
py_library(
|
||||||
name = "dataset",
|
name = "dataset",
|
||||||
srcs = ["dataset.py"],
|
srcs = ["dataset.py"],
|
||||||
deps = ["//mediapipe/model_maker/python/core/data:classification_dataset"],
|
deps = [
|
||||||
|
"//mediapipe/model_maker/python/core/data:cache_files",
|
||||||
|
"//mediapipe/model_maker/python/core/data:classification_dataset",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
py_test(
|
py_test(
|
||||||
@@ -88,7 +91,10 @@ py_test(
|
|||||||
py_library(
|
py_library(
|
||||||
name = "preprocessor",
|
name = "preprocessor",
|
||||||
srcs = ["preprocessor.py"],
|
srcs = ["preprocessor.py"],
|
||||||
deps = [":dataset"],
|
deps = [
|
||||||
|
":dataset",
|
||||||
|
"//mediapipe/model_maker/python/core/data:cache_files",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
py_test(
|
py_test(
|
||||||
@@ -99,6 +105,7 @@ py_test(
|
|||||||
":dataset",
|
":dataset",
|
||||||
":model_spec",
|
":model_spec",
|
||||||
":preprocessor",
|
":preprocessor",
|
||||||
|
"//mediapipe/model_maker/python/core/data:cache_files",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -124,6 +131,7 @@ py_library(
|
|||||||
":text_classifier_options",
|
":text_classifier_options",
|
||||||
"//mediapipe/model_maker/python/core/data:dataset",
|
"//mediapipe/model_maker/python/core/data:dataset",
|
||||||
"//mediapipe/model_maker/python/core/tasks:classifier",
|
"//mediapipe/model_maker/python/core/tasks:classifier",
|
||||||
|
"//mediapipe/model_maker/python/core/utils:loss_functions",
|
||||||
"//mediapipe/model_maker/python/core/utils:metrics",
|
"//mediapipe/model_maker/python/core/utils:metrics",
|
||||||
"//mediapipe/model_maker/python/core/utils:model_util",
|
"//mediapipe/model_maker/python/core/utils:model_util",
|
||||||
"//mediapipe/model_maker/python/core/utils:quantization",
|
"//mediapipe/model_maker/python/core/utils:quantization",
|
||||||
@@ -147,6 +155,7 @@ py_test(
|
|||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
":text_classifier_import",
|
":text_classifier_import",
|
||||||
|
"//mediapipe/model_maker/python/core/utils:loss_functions",
|
||||||
"//mediapipe/tasks/python/test:test_utils",
|
"//mediapipe/tasks/python/test:test_utils",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,11 +15,15 @@
|
|||||||
|
|
||||||
import csv
|
import csv
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
import random
|
import random
|
||||||
|
import tempfile
|
||||||
|
from typing import List, Optional, Sequence
|
||||||
|
|
||||||
from typing import Optional, Sequence
|
|
||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
|
|
||||||
|
from mediapipe.model_maker.python.core.data import cache_files as cache_files_lib
|
||||||
from mediapipe.model_maker.python.core.data import classification_dataset
|
from mediapipe.model_maker.python.core.data import classification_dataset
|
||||||
|
|
||||||
|
|
||||||
@@ -46,21 +50,49 @@ class CSVParameters:
|
|||||||
class Dataset(classification_dataset.ClassificationDataset):
|
class Dataset(classification_dataset.ClassificationDataset):
|
||||||
"""Dataset library for text classifier."""
|
"""Dataset library for text classifier."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
dataset: tf.data.Dataset,
|
||||||
|
label_names: List[str],
|
||||||
|
tfrecord_cache_files: Optional[cache_files_lib.TFRecordCacheFiles] = None,
|
||||||
|
size: Optional[int] = None,
|
||||||
|
):
|
||||||
|
super().__init__(dataset, label_names, size)
|
||||||
|
if not tfrecord_cache_files:
|
||||||
|
tfrecord_cache_files = cache_files_lib.TFRecordCacheFiles(
|
||||||
|
cache_prefix_filename="tfrecord", num_shards=1
|
||||||
|
)
|
||||||
|
self.tfrecord_cache_files = tfrecord_cache_files
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_csv(cls,
|
def from_csv(
|
||||||
filename: str,
|
cls,
|
||||||
csv_params: CSVParameters,
|
filename: str,
|
||||||
shuffle: bool = True) -> "Dataset":
|
csv_params: CSVParameters,
|
||||||
|
shuffle: bool = True,
|
||||||
|
cache_dir: Optional[str] = None,
|
||||||
|
num_shards: int = 1,
|
||||||
|
) -> "Dataset":
|
||||||
"""Loads text with labels from a CSV file.
|
"""Loads text with labels from a CSV file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
filename: Name of the CSV file.
|
filename: Name of the CSV file.
|
||||||
csv_params: Parameters used for reading the CSV file.
|
csv_params: Parameters used for reading the CSV file.
|
||||||
shuffle: If True, randomly shuffle the data.
|
shuffle: If True, randomly shuffle the data.
|
||||||
|
cache_dir: Optional parameter to specify where to store the preprocessed
|
||||||
|
dataset. Only used for BERT models.
|
||||||
|
num_shards: Optional parameter for num shards of the preprocessed dataset.
|
||||||
|
Note that using more than 1 shard will reorder the dataset. Only used
|
||||||
|
for BERT models.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dataset containing (text, label) pairs and other related info.
|
Dataset containing (text, label) pairs and other related info.
|
||||||
"""
|
"""
|
||||||
|
if cache_dir is None:
|
||||||
|
cache_dir = tempfile.mkdtemp()
|
||||||
|
# calculate hash for cache based off of files
|
||||||
|
hasher = hashlib.md5()
|
||||||
|
hasher.update(os.path.basename(filename).encode("utf-8"))
|
||||||
with tf.io.gfile.GFile(filename, "r") as f:
|
with tf.io.gfile.GFile(filename, "r") as f:
|
||||||
reader = csv.DictReader(
|
reader = csv.DictReader(
|
||||||
f,
|
f,
|
||||||
@@ -69,6 +101,9 @@ class Dataset(classification_dataset.ClassificationDataset):
|
|||||||
quotechar=csv_params.quotechar)
|
quotechar=csv_params.quotechar)
|
||||||
|
|
||||||
lines = list(reader)
|
lines = list(reader)
|
||||||
|
for line in lines:
|
||||||
|
hasher.update(str(line).encode("utf-8"))
|
||||||
|
|
||||||
if shuffle:
|
if shuffle:
|
||||||
random.shuffle(lines)
|
random.shuffle(lines)
|
||||||
|
|
||||||
@@ -81,9 +116,18 @@ class Dataset(classification_dataset.ClassificationDataset):
|
|||||||
index_by_label[line[csv_params.label_column]] for line in lines
|
index_by_label[line[csv_params.label_column]] for line in lines
|
||||||
]
|
]
|
||||||
label_index_ds = tf.data.Dataset.from_tensor_slices(
|
label_index_ds = tf.data.Dataset.from_tensor_slices(
|
||||||
tf.cast(label_indices, tf.int64))
|
tf.cast(label_indices, tf.int64)
|
||||||
|
)
|
||||||
text_label_ds = tf.data.Dataset.zip((text_ds, label_index_ds))
|
text_label_ds = tf.data.Dataset.zip((text_ds, label_index_ds))
|
||||||
|
|
||||||
|
hasher.update(str(num_shards).encode("utf-8"))
|
||||||
|
cache_prefix_filename = hasher.hexdigest()
|
||||||
|
tfrecord_cache_files = cache_files_lib.TFRecordCacheFiles(
|
||||||
|
cache_prefix_filename, cache_dir, num_shards
|
||||||
|
)
|
||||||
return Dataset(
|
return Dataset(
|
||||||
dataset=text_label_ds, label_names=label_names, size=len(texts)
|
dataset=text_label_ds,
|
||||||
|
label_names=label_names,
|
||||||
|
tfrecord_cache_files=tfrecord_cache_files,
|
||||||
|
size=len(texts),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class DatasetTest(tf.test.TestCase):
|
|||||||
|
|
||||||
def test_split(self):
|
def test_split(self):
|
||||||
ds = tf.data.Dataset.from_tensor_slices(['good', 'bad', 'neutral', 'odd'])
|
ds = tf.data.Dataset.from_tensor_slices(['good', 'bad', 'neutral', 'odd'])
|
||||||
data = dataset.Dataset(ds, ['pos', 'neg'], 4)
|
data = dataset.Dataset(ds, ['pos', 'neg'], size=4)
|
||||||
train_data, test_data = data.split(0.5)
|
train_data, test_data = data.split(0.5)
|
||||||
expected_train_data = [b'good', b'bad']
|
expected_train_data = [b'good', b'bad']
|
||||||
expected_test_data = [b'neutral', b'odd']
|
expected_test_data = [b'neutral', b'odd']
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import enum
|
import enum
|
||||||
from typing import Union
|
from typing import Sequence, Union
|
||||||
|
|
||||||
from mediapipe.model_maker.python.core import hyperparameters as hp
|
from mediapipe.model_maker.python.core import hyperparameters as hp
|
||||||
|
|
||||||
@@ -39,16 +39,34 @@ class BertHParams(hp.BaseHParams):
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
learning_rate: Learning rate to use for gradient descent training.
|
learning_rate: Learning rate to use for gradient descent training.
|
||||||
batch_size: Batch size for training.
|
end_learning_rate: End learning rate for linear decay. Defaults to 0.
|
||||||
epochs: Number of training iterations over the dataset.
|
batch_size: Batch size for training. Defaults to 48.
|
||||||
optimizer: Optimizer to use for training. Only supported values are "adamw"
|
epochs: Number of training iterations over the dataset. Defaults to 2.
|
||||||
and "lamb".
|
optimizer: Optimizer to use for training. Supported values are defined in
|
||||||
|
BertOptimizer enum: ADAMW and LAMB.
|
||||||
|
weight_decay: Weight decay of the optimizer. Defaults to 0.01.
|
||||||
|
desired_precisions: If specified, adds a RecallAtPrecision metric per
|
||||||
|
desired_precisions[i] entry which tracks the recall given the constraint
|
||||||
|
on precision. Only supported for binary classification.
|
||||||
|
desired_recalls: If specified, adds a PrecisionAtRecall metric per
|
||||||
|
desired_recalls[i] entry which tracks the precision given the constraint
|
||||||
|
on recall. Only supported for binary classification.
|
||||||
|
gamma: Gamma parameter for focal loss. To use cross entropy loss, set this
|
||||||
|
value to 0. Defaults to 2.0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
learning_rate: float = 3e-5
|
learning_rate: float = 3e-5
|
||||||
|
end_learning_rate: float = 0.0
|
||||||
|
|
||||||
batch_size: int = 48
|
batch_size: int = 48
|
||||||
epochs: int = 2
|
epochs: int = 2
|
||||||
optimizer: BertOptimizer = BertOptimizer.ADAMW
|
optimizer: BertOptimizer = BertOptimizer.ADAMW
|
||||||
|
weight_decay: float = 0.01
|
||||||
|
|
||||||
|
desired_precisions: Sequence[float] = dataclasses.field(default_factory=list)
|
||||||
|
desired_recalls: Sequence[float] = dataclasses.field(default_factory=list)
|
||||||
|
|
||||||
|
gamma: float = 2.0
|
||||||
|
|
||||||
|
|
||||||
HParams = Union[BertHParams, AverageWordEmbeddingHParams]
|
HParams = Union[BertHParams, AverageWordEmbeddingHParams]
|
||||||
|
|||||||
@@ -79,11 +79,6 @@ mobilebert_classifier_spec = functools.partial(
|
|||||||
epochs=3, batch_size=48, learning_rate=3e-5, distribution_strategy='off'
|
epochs=3, batch_size=48, learning_rate=3e-5, distribution_strategy='off'
|
||||||
),
|
),
|
||||||
name='MobileBert',
|
name='MobileBert',
|
||||||
tflite_input_name={
|
|
||||||
'ids': 'serving_default_input_1:0',
|
|
||||||
'segment_ids': 'serving_default_input_2:0',
|
|
||||||
'mask': 'serving_default_input_3:0',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
exbert_classifier_spec = functools.partial(
|
exbert_classifier_spec = functools.partial(
|
||||||
@@ -93,11 +88,6 @@ exbert_classifier_spec = functools.partial(
|
|||||||
epochs=3, batch_size=48, learning_rate=3e-5, distribution_strategy='off'
|
epochs=3, batch_size=48, learning_rate=3e-5, distribution_strategy='off'
|
||||||
),
|
),
|
||||||
name='ExBert',
|
name='ExBert',
|
||||||
tflite_input_name={
|
|
||||||
'ids': 'serving_default_input_1:0',
|
|
||||||
'segment_ids': 'serving_default_input_2:0',
|
|
||||||
'mask': 'serving_default_input_3:0',
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -46,11 +46,13 @@ class ModelSpecTest(tf.test.TestCase):
|
|||||||
self.assertTrue(os.path.exists(model_spec_obj.downloaded_files.get_path()))
|
self.assertTrue(os.path.exists(model_spec_obj.downloaded_files.get_path()))
|
||||||
self.assertTrue(model_spec_obj.do_lower_case)
|
self.assertTrue(model_spec_obj.do_lower_case)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
model_spec_obj.tflite_input_name, {
|
model_spec_obj.tflite_input_name,
|
||||||
'ids': 'serving_default_input_1:0',
|
{
|
||||||
'mask': 'serving_default_input_3:0',
|
'ids': 'serving_default_input_word_ids:0',
|
||||||
'segment_ids': 'serving_default_input_2:0'
|
'mask': 'serving_default_input_mask:0',
|
||||||
})
|
'segment_ids': 'serving_default_input_type_ids:0',
|
||||||
|
},
|
||||||
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
model_spec_obj.model_options,
|
model_spec_obj.model_options,
|
||||||
classifier_model_options.BertModelOptions(
|
classifier_model_options.BertModelOptions(
|
||||||
|
|||||||
@@ -15,14 +15,15 @@
|
|||||||
"""Preprocessors for text classification."""
|
"""Preprocessors for text classification."""
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import tempfile
|
|
||||||
from typing import Mapping, Sequence, Tuple, Union
|
from typing import Mapping, Sequence, Tuple, Union
|
||||||
|
|
||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
import tensorflow_hub
|
import tensorflow_hub
|
||||||
|
|
||||||
|
from mediapipe.model_maker.python.core.data import cache_files as cache_files_lib
|
||||||
from mediapipe.model_maker.python.text.text_classifier import dataset as text_classifier_ds
|
from mediapipe.model_maker.python.text.text_classifier import dataset as text_classifier_ds
|
||||||
from official.nlp.data import classifier_data_lib
|
from official.nlp.data import classifier_data_lib
|
||||||
from official.nlp.tools import tokenization
|
from official.nlp.tools import tokenization
|
||||||
@@ -75,19 +76,20 @@ def _decode_record(
|
|||||||
return bert_features, example["label_ids"]
|
return bert_features, example["label_ids"]
|
||||||
|
|
||||||
|
|
||||||
def _single_file_dataset(
|
def _tfrecord_dataset(
|
||||||
input_file: str, name_to_features: Mapping[str, tf.io.FixedLenFeature]
|
tfrecord_files: Sequence[str],
|
||||||
|
name_to_features: Mapping[str, tf.io.FixedLenFeature],
|
||||||
) -> tf.data.TFRecordDataset:
|
) -> tf.data.TFRecordDataset:
|
||||||
"""Creates a single-file dataset to be passed for BERT custom training.
|
"""Creates a single-file dataset to be passed for BERT custom training.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
input_file: Filepath for the dataset.
|
tfrecord_files: Filepaths for the dataset.
|
||||||
name_to_features: Maps record keys to feature types.
|
name_to_features: Maps record keys to feature types.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dataset containing BERT model input features and labels.
|
Dataset containing BERT model input features and labels.
|
||||||
"""
|
"""
|
||||||
d = tf.data.TFRecordDataset(input_file)
|
d = tf.data.TFRecordDataset(tfrecord_files)
|
||||||
d = d.map(
|
d = d.map(
|
||||||
lambda record: _decode_record(record, name_to_features),
|
lambda record: _decode_record(record, name_to_features),
|
||||||
num_parallel_calls=tf.data.AUTOTUNE)
|
num_parallel_calls=tf.data.AUTOTUNE)
|
||||||
@@ -221,15 +223,23 @@ class BertClassifierPreprocessor:
|
|||||||
seq_len: Length of the input sequence to the model.
|
seq_len: Length of the input sequence to the model.
|
||||||
vocab_file: File containing the BERT vocab.
|
vocab_file: File containing the BERT vocab.
|
||||||
tokenizer: BERT tokenizer.
|
tokenizer: BERT tokenizer.
|
||||||
|
model_name: Name of the model provided by the model_spec. Used to associate
|
||||||
|
cached files with specific Bert model vocab.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, seq_len: int, do_lower_case: bool, uri: str):
|
def __init__(
|
||||||
|
self, seq_len: int, do_lower_case: bool, uri: str, model_name: str
|
||||||
|
):
|
||||||
self._seq_len = seq_len
|
self._seq_len = seq_len
|
||||||
# Vocab filepath is tied to the BERT module's URI.
|
# Vocab filepath is tied to the BERT module's URI.
|
||||||
self._vocab_file = os.path.join(
|
self._vocab_file = os.path.join(
|
||||||
tensorflow_hub.resolve(uri), "assets", "vocab.txt")
|
tensorflow_hub.resolve(uri), "assets", "vocab.txt"
|
||||||
self._tokenizer = tokenization.FullTokenizer(self._vocab_file,
|
)
|
||||||
do_lower_case)
|
self._do_lower_case = do_lower_case
|
||||||
|
self._tokenizer = tokenization.FullTokenizer(
|
||||||
|
self._vocab_file, self._do_lower_case
|
||||||
|
)
|
||||||
|
self._model_name = model_name
|
||||||
|
|
||||||
def _get_name_to_features(self):
|
def _get_name_to_features(self):
|
||||||
"""Gets the dictionary mapping record keys to feature types."""
|
"""Gets the dictionary mapping record keys to feature types."""
|
||||||
@@ -244,8 +254,45 @@ class BertClassifierPreprocessor:
|
|||||||
"""Returns the vocab file of the BertClassifierPreprocessor."""
|
"""Returns the vocab file of the BertClassifierPreprocessor."""
|
||||||
return self._vocab_file
|
return self._vocab_file
|
||||||
|
|
||||||
|
def _get_tfrecord_cache_files(
|
||||||
|
self, ds_cache_files
|
||||||
|
) -> cache_files_lib.TFRecordCacheFiles:
|
||||||
|
"""Helper to regenerate cache prefix filename using preprocessor info.
|
||||||
|
|
||||||
|
We need to update the dataset cache_prefix cache because the actual cached
|
||||||
|
dataset depends on the preprocessor parameters such as model_name, seq_len,
|
||||||
|
and do_lower_case in addition to the raw dataset parameters which is already
|
||||||
|
included in the ds_cache_files.cache_prefix_filename
|
||||||
|
|
||||||
|
Specifically, the new cache_prefix_filename used by the preprocessor will
|
||||||
|
be a hash generated from the following:
|
||||||
|
1. cache_prefix_filename of the initial raw dataset
|
||||||
|
2. model_name
|
||||||
|
3. seq_len
|
||||||
|
4. do_lower_case
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ds_cache_files: TFRecordCacheFiles from the original raw dataset object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new TFRecordCacheFiles object which incorporates the preprocessor
|
||||||
|
parameters.
|
||||||
|
"""
|
||||||
|
hasher = hashlib.md5()
|
||||||
|
hasher.update(ds_cache_files.cache_prefix_filename.encode("utf-8"))
|
||||||
|
hasher.update(self._model_name.encode("utf-8"))
|
||||||
|
hasher.update(str(self._seq_len).encode("utf-8"))
|
||||||
|
hasher.update(str(self._do_lower_case).encode("utf-8"))
|
||||||
|
cache_prefix_filename = hasher.hexdigest()
|
||||||
|
return cache_files_lib.TFRecordCacheFiles(
|
||||||
|
cache_prefix_filename,
|
||||||
|
ds_cache_files.cache_dir,
|
||||||
|
ds_cache_files.num_shards,
|
||||||
|
)
|
||||||
|
|
||||||
def preprocess(
|
def preprocess(
|
||||||
self, dataset: text_classifier_ds.Dataset) -> text_classifier_ds.Dataset:
|
self, dataset: text_classifier_ds.Dataset
|
||||||
|
) -> text_classifier_ds.Dataset:
|
||||||
"""Preprocesses data into input for a BERT-based classifier.
|
"""Preprocesses data into input for a BERT-based classifier.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -254,32 +301,65 @@ class BertClassifierPreprocessor:
|
|||||||
Returns:
|
Returns:
|
||||||
Dataset containing (bert_features, label) data.
|
Dataset containing (bert_features, label) data.
|
||||||
"""
|
"""
|
||||||
examples = []
|
ds_cache_files = dataset.tfrecord_cache_files
|
||||||
for index, (text, label) in enumerate(dataset.gen_tf_dataset()):
|
# Get new tfrecord_cache_files by including preprocessor information.
|
||||||
_validate_text_and_label(text, label)
|
tfrecord_cache_files = self._get_tfrecord_cache_files(ds_cache_files)
|
||||||
examples.append(
|
if not tfrecord_cache_files.is_cached():
|
||||||
classifier_data_lib.InputExample(
|
print(f"Writing new cache files to {tfrecord_cache_files.cache_prefix}")
|
||||||
guid=str(index),
|
writers = tfrecord_cache_files.get_writers()
|
||||||
text_a=text.numpy()[0].decode("utf-8"),
|
size = 0
|
||||||
text_b=None,
|
for index, (text, label) in enumerate(dataset.gen_tf_dataset()):
|
||||||
# InputExample expects the label name rather than the int ID
|
_validate_text_and_label(text, label)
|
||||||
label=dataset.label_names[label.numpy()[0]]))
|
example = classifier_data_lib.InputExample(
|
||||||
|
guid=str(index),
|
||||||
|
text_a=text.numpy()[0].decode("utf-8"),
|
||||||
|
text_b=None,
|
||||||
|
# InputExample expects the label name rather than the int ID
|
||||||
|
# label=dataset.label_names[label.numpy()[0]])
|
||||||
|
label=label.numpy()[0],
|
||||||
|
)
|
||||||
|
feature = classifier_data_lib.convert_single_example(
|
||||||
|
index, example, None, self._seq_len, self._tokenizer
|
||||||
|
)
|
||||||
|
|
||||||
tfrecord_file = os.path.join(tempfile.mkdtemp(), "bert_features.tfrecord")
|
def create_int_feature(values):
|
||||||
classifier_data_lib.file_based_convert_examples_to_features(
|
f = tf.train.Feature(
|
||||||
examples=examples,
|
int64_list=tf.train.Int64List(value=list(values))
|
||||||
label_list=dataset.label_names,
|
)
|
||||||
max_seq_length=self._seq_len,
|
return f
|
||||||
tokenizer=self._tokenizer,
|
|
||||||
output_file=tfrecord_file)
|
features = collections.OrderedDict()
|
||||||
preprocessed_ds = _single_file_dataset(tfrecord_file,
|
features["input_ids"] = create_int_feature(feature.input_ids)
|
||||||
self._get_name_to_features())
|
features["input_mask"] = create_int_feature(feature.input_mask)
|
||||||
|
features["segment_ids"] = create_int_feature(feature.segment_ids)
|
||||||
|
features["label_ids"] = create_int_feature([feature.label_id])
|
||||||
|
tf_example = tf.train.Example(
|
||||||
|
features=tf.train.Features(feature=features)
|
||||||
|
)
|
||||||
|
writers[index % len(writers)].write(tf_example.SerializeToString())
|
||||||
|
size = index + 1
|
||||||
|
for writer in writers:
|
||||||
|
writer.close()
|
||||||
|
metadata = {"size": size, "label_names": dataset.label_names}
|
||||||
|
tfrecord_cache_files.save_metadata(metadata)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"Using existing cache files at {tfrecord_cache_files.cache_prefix}"
|
||||||
|
)
|
||||||
|
metadata = tfrecord_cache_files.load_metadata()
|
||||||
|
size = metadata["size"]
|
||||||
|
label_names = metadata["label_names"]
|
||||||
|
preprocessed_ds = _tfrecord_dataset(
|
||||||
|
tfrecord_cache_files.tfrecord_files, self._get_name_to_features()
|
||||||
|
)
|
||||||
return text_classifier_ds.Dataset(
|
return text_classifier_ds.Dataset(
|
||||||
dataset=preprocessed_ds,
|
dataset=preprocessed_ds,
|
||||||
size=dataset.size,
|
size=size,
|
||||||
label_names=dataset.label_names)
|
label_names=label_names,
|
||||||
|
tfrecord_cache_files=tfrecord_cache_files,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
TextClassifierPreprocessor = (
|
TextClassifierPreprocessor = Union[
|
||||||
Union[BertClassifierPreprocessor,
|
BertClassifierPreprocessor, AverageWordEmbeddingClassifierPreprocessor
|
||||||
AverageWordEmbeddingClassifierPreprocessor])
|
]
|
||||||
|
|||||||
@@ -13,14 +13,17 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from unittest import mock as unittest_mock
|
from unittest import mock as unittest_mock
|
||||||
|
|
||||||
|
import mock
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import numpy.testing as npt
|
import numpy.testing as npt
|
||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
|
|
||||||
|
from mediapipe.model_maker.python.core.data import cache_files
|
||||||
from mediapipe.model_maker.python.text.text_classifier import dataset as text_classifier_ds
|
from mediapipe.model_maker.python.text.text_classifier import dataset as text_classifier_ds
|
||||||
from mediapipe.model_maker.python.text.text_classifier import model_spec
|
from mediapipe.model_maker.python.text.text_classifier import model_spec
|
||||||
from mediapipe.model_maker.python.text.text_classifier import preprocessor
|
from mediapipe.model_maker.python.text.text_classifier import preprocessor
|
||||||
@@ -84,11 +87,12 @@ class PreprocessorTest(tf.test.TestCase):
|
|||||||
csv_file = self._get_csv_file()
|
csv_file = self._get_csv_file()
|
||||||
dataset = text_classifier_ds.Dataset.from_csv(
|
dataset = text_classifier_ds.Dataset.from_csv(
|
||||||
filename=csv_file, csv_params=self.CSV_PARAMS_)
|
filename=csv_file, csv_params=self.CSV_PARAMS_)
|
||||||
bert_spec = model_spec.SupportedModels.MOBILEBERT_CLASSIFIER.value()
|
bert_spec = model_spec.SupportedModels.EXBERT_CLASSIFIER.value()
|
||||||
bert_preprocessor = preprocessor.BertClassifierPreprocessor(
|
bert_preprocessor = preprocessor.BertClassifierPreprocessor(
|
||||||
seq_len=5,
|
seq_len=5,
|
||||||
do_lower_case=bert_spec.do_lower_case,
|
do_lower_case=bert_spec.do_lower_case,
|
||||||
uri=bert_spec.downloaded_files.get_path(),
|
uri=bert_spec.downloaded_files.get_path(),
|
||||||
|
model_name=bert_spec.name,
|
||||||
)
|
)
|
||||||
preprocessed_dataset = bert_preprocessor.preprocess(dataset)
|
preprocessed_dataset = bert_preprocessor.preprocess(dataset)
|
||||||
labels = []
|
labels = []
|
||||||
@@ -97,18 +101,91 @@ class PreprocessorTest(tf.test.TestCase):
|
|||||||
self.assertEqual(label.shape, [1])
|
self.assertEqual(label.shape, [1])
|
||||||
labels.append(label.numpy()[0])
|
labels.append(label.numpy()[0])
|
||||||
self.assertSameElements(
|
self.assertSameElements(
|
||||||
features.keys(), ['input_word_ids', 'input_mask', 'input_type_ids'])
|
features.keys(), ['input_word_ids', 'input_mask', 'input_type_ids']
|
||||||
|
)
|
||||||
for feature in features.values():
|
for feature in features.values():
|
||||||
self.assertEqual(feature.shape, [1, 5])
|
self.assertEqual(feature.shape, [1, 5])
|
||||||
input_masks.append(features['input_mask'].numpy()[0])
|
input_masks.append(features['input_mask'].numpy()[0])
|
||||||
npt.assert_array_equal(features['input_type_ids'].numpy()[0],
|
npt.assert_array_equal(
|
||||||
[0, 0, 0, 0, 0])
|
features['input_type_ids'].numpy()[0], [0, 0, 0, 0, 0]
|
||||||
|
)
|
||||||
npt.assert_array_equal(
|
npt.assert_array_equal(
|
||||||
np.stack(input_masks), np.array([[1, 1, 1, 1, 1], [1, 1, 1, 1, 0]]))
|
np.stack(input_masks), np.array([[1, 1, 1, 1, 1], [1, 1, 1, 1, 0]])
|
||||||
|
)
|
||||||
self.assertEqual(labels, [1, 0])
|
self.assertEqual(labels, [1, 0])
|
||||||
|
|
||||||
|
def test_bert_preprocessor_cache(self):
|
||||||
|
csv_file = self._get_csv_file()
|
||||||
|
dataset = text_classifier_ds.Dataset.from_csv(
|
||||||
|
filename=csv_file,
|
||||||
|
csv_params=self.CSV_PARAMS_,
|
||||||
|
cache_dir=self.get_temp_dir(),
|
||||||
|
)
|
||||||
|
bert_spec = model_spec.SupportedModels.EXBERT_CLASSIFIER.value()
|
||||||
|
bert_preprocessor = preprocessor.BertClassifierPreprocessor(
|
||||||
|
seq_len=5,
|
||||||
|
do_lower_case=bert_spec.do_lower_case,
|
||||||
|
uri=bert_spec.downloaded_files.get_path(),
|
||||||
|
model_name=bert_spec.name,
|
||||||
|
)
|
||||||
|
ds_cache_files = dataset.tfrecord_cache_files
|
||||||
|
preprocessed_cache_files = bert_preprocessor._get_tfrecord_cache_files(
|
||||||
|
ds_cache_files
|
||||||
|
)
|
||||||
|
self.assertFalse(preprocessed_cache_files.is_cached())
|
||||||
|
preprocessed_dataset = bert_preprocessor.preprocess(dataset)
|
||||||
|
self.assertTrue(preprocessed_cache_files.is_cached())
|
||||||
|
self.assertEqual(
|
||||||
|
preprocessed_dataset.tfrecord_cache_files, preprocessed_cache_files
|
||||||
|
)
|
||||||
|
|
||||||
|
# The second time running preprocessor, it should load from cache directly
|
||||||
|
mock_stdout = io.StringIO()
|
||||||
|
with mock.patch('sys.stdout', mock_stdout):
|
||||||
|
_ = bert_preprocessor.preprocess(dataset)
|
||||||
|
self.assertEqual(
|
||||||
|
mock_stdout.getvalue(),
|
||||||
|
'Using existing cache files at'
|
||||||
|
f' {preprocessed_cache_files.cache_prefix}\n',
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_new_prefix(self, cf, bert_spec, seq_len, do_lower_case):
|
||||||
|
bert_preprocessor = preprocessor.BertClassifierPreprocessor(
|
||||||
|
seq_len=seq_len,
|
||||||
|
do_lower_case=do_lower_case,
|
||||||
|
uri=bert_spec.downloaded_files.get_path(),
|
||||||
|
model_name=bert_spec.name,
|
||||||
|
)
|
||||||
|
new_cf = bert_preprocessor._get_tfrecord_cache_files(cf)
|
||||||
|
return new_cf.cache_prefix_filename
|
||||||
|
|
||||||
|
def test_bert_get_tfrecord_cache_files(self):
|
||||||
|
# Test to ensure regenerated cache_files have different prefixes
|
||||||
|
all_cf_prefixes = set()
|
||||||
|
cf = cache_files.TFRecordCacheFiles(
|
||||||
|
cache_prefix_filename='cache_prefix',
|
||||||
|
cache_dir=self.get_temp_dir(),
|
||||||
|
num_shards=1,
|
||||||
|
)
|
||||||
|
exbert_spec = model_spec.SupportedModels.EXBERT_CLASSIFIER.value()
|
||||||
|
all_cf_prefixes.add(self._get_new_prefix(cf, exbert_spec, 5, True))
|
||||||
|
all_cf_prefixes.add(self._get_new_prefix(cf, exbert_spec, 10, True))
|
||||||
|
all_cf_prefixes.add(self._get_new_prefix(cf, exbert_spec, 5, False))
|
||||||
|
mobilebert_spec = model_spec.SupportedModels.MOBILEBERT_CLASSIFIER.value()
|
||||||
|
all_cf_prefixes.add(self._get_new_prefix(cf, mobilebert_spec, 5, True))
|
||||||
|
all_cf_prefixes.add(self._get_new_prefix(cf, mobilebert_spec, 10, True))
|
||||||
|
all_cf_prefixes.add(self._get_new_prefix(cf, mobilebert_spec, 5, False))
|
||||||
|
new_cf = cache_files.TFRecordCacheFiles(
|
||||||
|
cache_prefix_filename='new_cache_prefix',
|
||||||
|
cache_dir=self.get_temp_dir(),
|
||||||
|
num_shards=1,
|
||||||
|
)
|
||||||
|
all_cf_prefixes.add(self._get_new_prefix(new_cf, exbert_spec, 5, True))
|
||||||
|
|
||||||
|
# Each item of all_cf_prefixes should be unique, so 7 total.
|
||||||
|
self.assertLen(all_cf_prefixes, 7)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Load compressed models from tensorflow_hub
|
# Load compressed models from tensorflow_hub
|
||||||
os.environ['TFHUB_MODEL_LOAD_FORMAT'] = 'COMPRESSED'
|
|
||||||
tf.test.main()
|
tf.test.main()
|
||||||
|
|||||||
+4
-4
@@ -16,8 +16,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mask",
|
"name": "segment_ids",
|
||||||
"description": "Mask with 1 for real tokens and 0 for padding tokens.",
|
"description": "0 for the first sequence, 1 for the second sequence if exists.",
|
||||||
"content": {
|
"content": {
|
||||||
"content_properties_type": "FeatureProperties",
|
"content_properties_type": "FeatureProperties",
|
||||||
"content_properties": {
|
"content_properties": {
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "segment_ids",
|
"name": "mask",
|
||||||
"description": "0 for the first sequence, 1 for the second sequence if exists.",
|
"description": "Mask with 1 for real tokens and 0 for padding tokens.",
|
||||||
"content": {
|
"content": {
|
||||||
"content_properties_type": "FeatureProperties",
|
"content_properties_type": "FeatureProperties",
|
||||||
"content_properties": {
|
"content_properties": {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import tensorflow_hub as hub
|
|||||||
|
|
||||||
from mediapipe.model_maker.python.core.data import dataset as ds
|
from mediapipe.model_maker.python.core.data import dataset as ds
|
||||||
from mediapipe.model_maker.python.core.tasks import classifier
|
from mediapipe.model_maker.python.core.tasks import classifier
|
||||||
|
from mediapipe.model_maker.python.core.utils import loss_functions
|
||||||
from mediapipe.model_maker.python.core.utils import metrics
|
from mediapipe.model_maker.python.core.utils import metrics
|
||||||
from mediapipe.model_maker.python.core.utils import model_util
|
from mediapipe.model_maker.python.core.utils import model_util
|
||||||
from mediapipe.model_maker.python.core.utils import quantization
|
from mediapipe.model_maker.python.core.utils import quantization
|
||||||
@@ -116,17 +117,14 @@ class TextClassifier(classifier.Classifier):
|
|||||||
options.supported_model == ms.SupportedModels.MOBILEBERT_CLASSIFIER
|
options.supported_model == ms.SupportedModels.MOBILEBERT_CLASSIFIER
|
||||||
or options.supported_model == ms.SupportedModels.EXBERT_CLASSIFIER
|
or options.supported_model == ms.SupportedModels.EXBERT_CLASSIFIER
|
||||||
):
|
):
|
||||||
text_classifier = (
|
text_classifier = _BertClassifier.create_bert_classifier(
|
||||||
_BertClassifier.create_bert_classifier(train_data, validation_data,
|
train_data, validation_data, options
|
||||||
options,
|
)
|
||||||
train_data.label_names))
|
|
||||||
elif (options.supported_model ==
|
elif (options.supported_model ==
|
||||||
ms.SupportedModels.AVERAGE_WORD_EMBEDDING_CLASSIFIER):
|
ms.SupportedModels.AVERAGE_WORD_EMBEDDING_CLASSIFIER):
|
||||||
text_classifier = (
|
text_classifier = _AverageWordEmbeddingClassifier.create_average_word_embedding_classifier(
|
||||||
_AverageWordEmbeddingClassifier
|
train_data, validation_data, options
|
||||||
.create_average_word_embedding_classifier(train_data, validation_data,
|
)
|
||||||
options,
|
|
||||||
train_data.label_names))
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown model {options.supported_model}")
|
raise ValueError(f"Unknown model {options.supported_model}")
|
||||||
|
|
||||||
@@ -166,28 +164,8 @@ class TextClassifier(classifier.Classifier):
|
|||||||
processed_data = self._text_preprocessor.preprocess(data)
|
processed_data = self._text_preprocessor.preprocess(data)
|
||||||
dataset = processed_data.gen_tf_dataset(batch_size, is_training=False)
|
dataset = processed_data.gen_tf_dataset(batch_size, is_training=False)
|
||||||
|
|
||||||
additional_metrics = []
|
with self._hparams.get_strategy().scope():
|
||||||
if desired_precisions and len(data.label_names) == 2:
|
return self._model.evaluate(dataset)
|
||||||
for precision in desired_precisions:
|
|
||||||
additional_metrics.append(
|
|
||||||
metrics.BinarySparseRecallAtPrecision(
|
|
||||||
precision, name=f"recall_at_precision_{precision}"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if desired_recalls and len(data.label_names) == 2:
|
|
||||||
for recall in desired_recalls:
|
|
||||||
additional_metrics.append(
|
|
||||||
metrics.BinarySparsePrecisionAtRecall(
|
|
||||||
recall, name=f"precision_at_recall_{recall}"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
metric_functions = self._metric_functions + additional_metrics
|
|
||||||
self._model.compile(
|
|
||||||
optimizer=self._optimizer,
|
|
||||||
loss=self._loss_function,
|
|
||||||
metrics=metric_functions,
|
|
||||||
)
|
|
||||||
return self._model.evaluate(dataset)
|
|
||||||
|
|
||||||
def export_model(
|
def export_model(
|
||||||
self,
|
self,
|
||||||
@@ -255,16 +233,17 @@ class _AverageWordEmbeddingClassifier(TextClassifier):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_average_word_embedding_classifier(
|
def create_average_word_embedding_classifier(
|
||||||
cls, train_data: text_ds.Dataset, validation_data: text_ds.Dataset,
|
cls,
|
||||||
|
train_data: text_ds.Dataset,
|
||||||
|
validation_data: text_ds.Dataset,
|
||||||
options: text_classifier_options.TextClassifierOptions,
|
options: text_classifier_options.TextClassifierOptions,
|
||||||
label_names: Sequence[str]) -> "_AverageWordEmbeddingClassifier":
|
) -> "_AverageWordEmbeddingClassifier":
|
||||||
"""Creates, trains, and returns an Average Word Embedding classifier.
|
"""Creates, trains, and returns an Average Word Embedding classifier.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
train_data: Training data.
|
train_data: Training data.
|
||||||
validation_data: Validation data.
|
validation_data: Validation data.
|
||||||
options: Options for creating and training the text classifier.
|
options: Options for creating and training the text classifier.
|
||||||
label_names: Label names used in the data.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
An Average Word Embedding classifier.
|
An Average Word Embedding classifier.
|
||||||
@@ -370,28 +349,25 @@ class _BertClassifier(TextClassifier):
|
|||||||
self._callbacks = model_util.get_default_callbacks(self._hparams.export_dir)
|
self._callbacks = model_util.get_default_callbacks(self._hparams.export_dir)
|
||||||
self._model_options = model_options
|
self._model_options = model_options
|
||||||
with self._hparams.get_strategy().scope():
|
with self._hparams.get_strategy().scope():
|
||||||
self._loss_function = tf.keras.losses.SparseCategoricalCrossentropy()
|
self._loss_function = loss_functions.SparseFocalLoss(
|
||||||
self._metric_functions = [
|
self._hparams.gamma, self._num_classes
|
||||||
tf.keras.metrics.SparseCategoricalAccuracy(
|
)
|
||||||
"test_accuracy", dtype=tf.float32
|
self._metric_functions = self._create_metrics()
|
||||||
),
|
self._text_preprocessor: preprocessor.BertClassifierPreprocessor = None
|
||||||
metrics.SparsePrecision(name="precision", dtype=tf.float32),
|
|
||||||
metrics.SparseRecall(name="recall", dtype=tf.float32),
|
|
||||||
]
|
|
||||||
self._text_preprocessor: preprocessor.BertClassifierPreprocessor = None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_bert_classifier(
|
def create_bert_classifier(
|
||||||
cls, train_data: text_ds.Dataset, validation_data: text_ds.Dataset,
|
cls,
|
||||||
|
train_data: text_ds.Dataset,
|
||||||
|
validation_data: text_ds.Dataset,
|
||||||
options: text_classifier_options.TextClassifierOptions,
|
options: text_classifier_options.TextClassifierOptions,
|
||||||
label_names: Sequence[str]) -> "_BertClassifier":
|
) -> "_BertClassifier":
|
||||||
"""Creates, trains, and returns a BERT-based classifier.
|
"""Creates, trains, and returns a BERT-based classifier.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
train_data: Training data.
|
train_data: Training data.
|
||||||
validation_data: Validation data.
|
validation_data: Validation data.
|
||||||
options: Options for creating and training the text classifier.
|
options: Options for creating and training the text classifier.
|
||||||
label_names: Label names used in the data.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A BERT-based classifier.
|
A BERT-based classifier.
|
||||||
@@ -435,9 +411,59 @@ class _BertClassifier(TextClassifier):
|
|||||||
seq_len=self._model_options.seq_len,
|
seq_len=self._model_options.seq_len,
|
||||||
do_lower_case=self._model_spec.do_lower_case,
|
do_lower_case=self._model_spec.do_lower_case,
|
||||||
uri=self._model_spec.downloaded_files.get_path(),
|
uri=self._model_spec.downloaded_files.get_path(),
|
||||||
|
model_name=self._model_spec.name,
|
||||||
)
|
)
|
||||||
return (self._text_preprocessor.preprocess(train_data),
|
return (
|
||||||
self._text_preprocessor.preprocess(validation_data))
|
self._text_preprocessor.preprocess(train_data),
|
||||||
|
self._text_preprocessor.preprocess(validation_data),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _create_metrics(self):
|
||||||
|
"""Creates metrics for training and evaluation.
|
||||||
|
|
||||||
|
The default metrics are accuracy, precision, and recall.
|
||||||
|
|
||||||
|
For binary classification tasks only (num_classes=2):
|
||||||
|
Users can configure PrecisionAtRecall and RecallAtPrecision metrics using
|
||||||
|
the desired_presisions and desired_recalls fields in BertHParams.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of tf.keras.Metric subclasses which can be used with model.compile
|
||||||
|
"""
|
||||||
|
metric_functions = [
|
||||||
|
tf.keras.metrics.SparseCategoricalAccuracy(
|
||||||
|
"accuracy", dtype=tf.float32
|
||||||
|
),
|
||||||
|
metrics.SparsePrecision(name="precision", dtype=tf.float32),
|
||||||
|
metrics.SparseRecall(name="recall", dtype=tf.float32),
|
||||||
|
]
|
||||||
|
if self._num_classes == 2:
|
||||||
|
if self._hparams.desired_precisions:
|
||||||
|
for desired_precision in self._hparams.desired_precisions:
|
||||||
|
metric_functions.append(
|
||||||
|
metrics.BinarySparseRecallAtPrecision(
|
||||||
|
desired_precision,
|
||||||
|
name=f"recall_at_precision_{desired_precision}",
|
||||||
|
num_thresholds=1000,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if self._hparams.desired_recalls:
|
||||||
|
for desired_recall in self._hparams.desired_recalls:
|
||||||
|
metric_functions.append(
|
||||||
|
metrics.BinarySparseRecallAtPrecision(
|
||||||
|
desired_recall,
|
||||||
|
name=f"precision_at_recall_{desired_recall}",
|
||||||
|
num_thresholds=1000,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if self._hparams.desired_precisions or self._hparams.desired_recalls:
|
||||||
|
raise ValueError(
|
||||||
|
"desired_recalls and desired_precisions parameters are binary"
|
||||||
|
" metrics and not supported for num_classes > 2. Found"
|
||||||
|
f" num_classes: {self._num_classes}"
|
||||||
|
)
|
||||||
|
return metric_functions
|
||||||
|
|
||||||
def _create_model(self):
|
def _create_model(self):
|
||||||
"""Creates a BERT-based classifier model.
|
"""Creates a BERT-based classifier model.
|
||||||
@@ -447,11 +473,20 @@ class _BertClassifier(TextClassifier):
|
|||||||
"""
|
"""
|
||||||
encoder_inputs = dict(
|
encoder_inputs = dict(
|
||||||
input_word_ids=tf.keras.layers.Input(
|
input_word_ids=tf.keras.layers.Input(
|
||||||
shape=(self._model_options.seq_len,), dtype=tf.int32),
|
shape=(self._model_options.seq_len,),
|
||||||
|
dtype=tf.int32,
|
||||||
|
name="input_word_ids",
|
||||||
|
),
|
||||||
input_mask=tf.keras.layers.Input(
|
input_mask=tf.keras.layers.Input(
|
||||||
shape=(self._model_options.seq_len,), dtype=tf.int32),
|
shape=(self._model_options.seq_len,),
|
||||||
|
dtype=tf.int32,
|
||||||
|
name="input_mask",
|
||||||
|
),
|
||||||
input_type_ids=tf.keras.layers.Input(
|
input_type_ids=tf.keras.layers.Input(
|
||||||
shape=(self._model_options.seq_len,), dtype=tf.int32),
|
shape=(self._model_options.seq_len,),
|
||||||
|
dtype=tf.int32,
|
||||||
|
name="input_type_ids",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
encoder = hub.KerasLayer(
|
encoder = hub.KerasLayer(
|
||||||
self._model_spec.downloaded_files.get_path(),
|
self._model_spec.downloaded_files.get_path(),
|
||||||
@@ -493,16 +528,21 @@ class _BertClassifier(TextClassifier):
|
|||||||
lr_schedule = tf.keras.optimizers.schedules.PolynomialDecay(
|
lr_schedule = tf.keras.optimizers.schedules.PolynomialDecay(
|
||||||
initial_learning_rate=initial_lr,
|
initial_learning_rate=initial_lr,
|
||||||
decay_steps=total_steps,
|
decay_steps=total_steps,
|
||||||
end_learning_rate=0.0,
|
end_learning_rate=self._hparams.end_learning_rate,
|
||||||
power=1.0)
|
power=1.0,
|
||||||
|
)
|
||||||
if warmup_steps:
|
if warmup_steps:
|
||||||
lr_schedule = model_util.WarmUp(
|
lr_schedule = model_util.WarmUp(
|
||||||
initial_learning_rate=initial_lr,
|
initial_learning_rate=initial_lr,
|
||||||
decay_schedule_fn=lr_schedule,
|
decay_schedule_fn=lr_schedule,
|
||||||
warmup_steps=warmup_steps)
|
warmup_steps=warmup_steps,
|
||||||
|
)
|
||||||
if self._hparams.optimizer == hp.BertOptimizer.ADAMW:
|
if self._hparams.optimizer == hp.BertOptimizer.ADAMW:
|
||||||
self._optimizer = tf.keras.optimizers.experimental.AdamW(
|
self._optimizer = tf.keras.optimizers.experimental.AdamW(
|
||||||
lr_schedule, weight_decay=0.01, epsilon=1e-6, global_clipnorm=1.0
|
lr_schedule,
|
||||||
|
weight_decay=self._hparams.weight_decay,
|
||||||
|
epsilon=1e-6,
|
||||||
|
global_clipnorm=1.0,
|
||||||
)
|
)
|
||||||
self._optimizer.exclude_from_weight_decay(
|
self._optimizer.exclude_from_weight_decay(
|
||||||
var_names=["LayerNorm", "layer_norm", "bias"]
|
var_names=["LayerNorm", "layer_norm", "bias"]
|
||||||
@@ -510,7 +550,7 @@ class _BertClassifier(TextClassifier):
|
|||||||
elif self._hparams.optimizer == hp.BertOptimizer.LAMB:
|
elif self._hparams.optimizer == hp.BertOptimizer.LAMB:
|
||||||
self._optimizer = tfa_optimizers.LAMB(
|
self._optimizer = tfa_optimizers.LAMB(
|
||||||
lr_schedule,
|
lr_schedule,
|
||||||
weight_decay_rate=0.01,
|
weight_decay_rate=self._hparams.weight_decay,
|
||||||
epsilon=1e-6,
|
epsilon=1e-6,
|
||||||
exclude_from_weight_decay=["LayerNorm", "layer_norm", "bias"],
|
exclude_from_weight_decay=["LayerNorm", "layer_norm", "bias"],
|
||||||
global_clipnorm=1.0,
|
global_clipnorm=1.0,
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ def run(data_dir,
|
|||||||
options)
|
options)
|
||||||
|
|
||||||
# Gets evaluation results.
|
# Gets evaluation results.
|
||||||
_, acc = model.evaluate(validation_data)
|
metrics = model.evaluate(validation_data)
|
||||||
print('Eval accuracy: %f' % acc)
|
print('Eval accuracy: %f' % metrics[1])
|
||||||
|
|
||||||
model.export_model(quantization_config=quantization_config)
|
model.export_model(quantization_config=quantization_config)
|
||||||
model.export_labels(export_dir=options.hparams.export_dir)
|
model.export_labels(export_dir=options.hparams.export_dir)
|
||||||
|
|||||||
@@ -16,17 +16,17 @@ import csv
|
|||||||
import filecmp
|
import filecmp
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
|
||||||
from unittest import mock as unittest_mock
|
from unittest import mock as unittest_mock
|
||||||
|
|
||||||
|
from absl.testing import parameterized
|
||||||
import tensorflow as tf
|
import tensorflow as tf
|
||||||
|
|
||||||
|
from mediapipe.model_maker.python.core.utils import loss_functions
|
||||||
from mediapipe.model_maker.python.text import text_classifier
|
from mediapipe.model_maker.python.text import text_classifier
|
||||||
from mediapipe.tasks.python.test import test_utils
|
from mediapipe.tasks.python.test import test_utils
|
||||||
|
|
||||||
|
|
||||||
@unittest.skip('b/275624089')
|
class TextClassifierTest(tf.test.TestCase, parameterized.TestCase):
|
||||||
class TextClassifierTest(tf.test.TestCase):
|
|
||||||
|
|
||||||
_AVERAGE_WORD_EMBEDDING_JSON_FILE = (
|
_AVERAGE_WORD_EMBEDDING_JSON_FILE = (
|
||||||
test_utils.get_test_data_path('average_word_embedding_metadata.json'))
|
test_utils.get_test_data_path('average_word_embedding_metadata.json'))
|
||||||
@@ -78,8 +78,8 @@ class TextClassifierTest(tf.test.TestCase):
|
|||||||
text_classifier.TextClassifier.create(train_data, validation_data,
|
text_classifier.TextClassifier.create(train_data, validation_data,
|
||||||
options))
|
options))
|
||||||
|
|
||||||
_, accuracy = average_word_embedding_classifier.evaluate(validation_data)
|
metrics = average_word_embedding_classifier.evaluate(validation_data)
|
||||||
self.assertGreaterEqual(accuracy, 0.0)
|
self.assertGreaterEqual(metrics[1], 0.0) # metrics[1] is accuracy
|
||||||
|
|
||||||
# Test export_model
|
# Test export_model
|
||||||
average_word_embedding_classifier.export_model()
|
average_word_embedding_classifier.export_model()
|
||||||
@@ -98,12 +98,25 @@ class TextClassifierTest(tf.test.TestCase):
|
|||||||
filecmp.cmp(
|
filecmp.cmp(
|
||||||
output_metadata_file,
|
output_metadata_file,
|
||||||
self._AVERAGE_WORD_EMBEDDING_JSON_FILE,
|
self._AVERAGE_WORD_EMBEDDING_JSON_FILE,
|
||||||
shallow=False))
|
shallow=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_and_train_bert(self):
|
@parameterized.named_parameters(
|
||||||
|
# Skipping mobilebert b/c OSS test timeout/flakiness: b/275624089
|
||||||
|
# dict(
|
||||||
|
# testcase_name='mobilebert',
|
||||||
|
# supported_model=text_classifier.SupportedModels.MOBILEBERT_CLASSIFIER,
|
||||||
|
# ),
|
||||||
|
dict(
|
||||||
|
testcase_name='exbert',
|
||||||
|
supported_model=text_classifier.SupportedModels.EXBERT_CLASSIFIER,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_create_and_train_bert(self, supported_model):
|
||||||
train_data, validation_data = self._get_data()
|
train_data, validation_data = self._get_data()
|
||||||
options = text_classifier.TextClassifierOptions(
|
options = text_classifier.TextClassifierOptions(
|
||||||
supported_model=text_classifier.SupportedModels.MOBILEBERT_CLASSIFIER,
|
supported_model=supported_model,
|
||||||
model_options=text_classifier.BertModelOptions(
|
model_options=text_classifier.BertModelOptions(
|
||||||
do_fine_tuning=False, seq_len=2
|
do_fine_tuning=False, seq_len=2
|
||||||
),
|
),
|
||||||
@@ -117,8 +130,8 @@ class TextClassifierTest(tf.test.TestCase):
|
|||||||
bert_classifier = text_classifier.TextClassifier.create(
|
bert_classifier = text_classifier.TextClassifier.create(
|
||||||
train_data, validation_data, options)
|
train_data, validation_data, options)
|
||||||
|
|
||||||
_, accuracy = bert_classifier.evaluate(validation_data)
|
metrics = bert_classifier.evaluate(validation_data)
|
||||||
self.assertGreaterEqual(accuracy, 0.0)
|
self.assertGreaterEqual(metrics[1], 0.0) # metrics[1] is accuracy
|
||||||
|
|
||||||
# Test export_model
|
# Test export_model
|
||||||
bert_classifier.export_model()
|
bert_classifier.export_model()
|
||||||
@@ -142,45 +155,93 @@ class TextClassifierTest(tf.test.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_label_mismatch(self):
|
def test_label_mismatch(self):
|
||||||
options = (
|
options = text_classifier.TextClassifierOptions(
|
||||||
text_classifier.TextClassifierOptions(
|
supported_model=(text_classifier.SupportedModels.EXBERT_CLASSIFIER)
|
||||||
supported_model=(
|
)
|
||||||
text_classifier.SupportedModels.MOBILEBERT_CLASSIFIER)))
|
|
||||||
train_tf_dataset = tf.data.Dataset.from_tensor_slices([[0]])
|
train_tf_dataset = tf.data.Dataset.from_tensor_slices([[0]])
|
||||||
train_data = text_classifier.Dataset(train_tf_dataset, 1, ['foo'])
|
train_data = text_classifier.Dataset(train_tf_dataset, ['foo'], 1)
|
||||||
validation_tf_dataset = tf.data.Dataset.from_tensor_slices([[0]])
|
validation_tf_dataset = tf.data.Dataset.from_tensor_slices([[0]])
|
||||||
validation_data = text_classifier.Dataset(validation_tf_dataset, 1, ['bar'])
|
validation_data = text_classifier.Dataset(validation_tf_dataset, ['bar'], 1)
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaisesRegex(
|
||||||
ValueError,
|
ValueError,
|
||||||
'Training data label names .* not equal to validation data label names'
|
'Training data label names .* not equal to validation data label names',
|
||||||
):
|
):
|
||||||
text_classifier.TextClassifier.create(train_data, validation_data,
|
text_classifier.TextClassifier.create(
|
||||||
options)
|
train_data, validation_data, options
|
||||||
|
)
|
||||||
|
|
||||||
def test_options_mismatch(self):
|
def test_options_mismatch(self):
|
||||||
train_data, validation_data = self._get_data()
|
train_data, validation_data = self._get_data()
|
||||||
|
|
||||||
avg_options = (
|
avg_options = text_classifier.TextClassifierOptions(
|
||||||
text_classifier.TextClassifierOptions(
|
supported_model=(text_classifier.SupportedModels.EXBERT_CLASSIFIER),
|
||||||
supported_model=(
|
model_options=text_classifier.AverageWordEmbeddingModelOptions(),
|
||||||
text_classifier.SupportedModels.MOBILEBERT_CLASSIFIER),
|
)
|
||||||
model_options=text_classifier.AverageWordEmbeddingModelOptions()))
|
with self.assertRaisesWithLiteralMatch(
|
||||||
with self.assertRaisesRegex(
|
ValueError,
|
||||||
ValueError, 'Expected AVERAGE_WORD_EMBEDDING_CLASSIFIER, got'
|
'Expected AVERAGE_WORD_EMBEDDING_CLASSIFIER, got'
|
||||||
' SupportedModels.MOBILEBERT_CLASSIFIER'):
|
' SupportedModels.EXBERT_CLASSIFIER',
|
||||||
text_classifier.TextClassifier.create(train_data, validation_data,
|
):
|
||||||
avg_options)
|
text_classifier.TextClassifier.create(
|
||||||
|
train_data, validation_data, avg_options
|
||||||
|
)
|
||||||
|
|
||||||
bert_options = (
|
bert_options = text_classifier.TextClassifierOptions(
|
||||||
text_classifier.TextClassifierOptions(
|
supported_model=(
|
||||||
supported_model=(text_classifier.SupportedModels
|
text_classifier.SupportedModels.AVERAGE_WORD_EMBEDDING_CLASSIFIER
|
||||||
.AVERAGE_WORD_EMBEDDING_CLASSIFIER),
|
),
|
||||||
model_options=text_classifier.BertModelOptions()))
|
model_options=text_classifier.BertModelOptions(),
|
||||||
with self.assertRaisesRegex(
|
)
|
||||||
ValueError, 'Expected MOBILEBERT_CLASSIFIER, got'
|
with self.assertRaisesWithLiteralMatch(
|
||||||
' SupportedModels.AVERAGE_WORD_EMBEDDING_CLASSIFIER'):
|
ValueError,
|
||||||
text_classifier.TextClassifier.create(train_data, validation_data,
|
'Expected a Bert Classifier(MobileBERT or EXBERT), got'
|
||||||
bert_options)
|
' SupportedModels.AVERAGE_WORD_EMBEDDING_CLASSIFIER',
|
||||||
|
):
|
||||||
|
text_classifier.TextClassifier.create(
|
||||||
|
train_data, validation_data, bert_options
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_bert_loss_and_metrics_creation(self):
|
||||||
|
train_data, validation_data = self._get_data()
|
||||||
|
supported_model = text_classifier.SupportedModels.EXBERT_CLASSIFIER
|
||||||
|
hparams = text_classifier.BertHParams(
|
||||||
|
desired_recalls=[0.2],
|
||||||
|
desired_precisions=[0.9],
|
||||||
|
epochs=1,
|
||||||
|
batch_size=1,
|
||||||
|
learning_rate=3e-5,
|
||||||
|
distribution_strategy='off',
|
||||||
|
gamma=3.5,
|
||||||
|
)
|
||||||
|
options = text_classifier.TextClassifierOptions(
|
||||||
|
supported_model=supported_model, hparams=hparams
|
||||||
|
)
|
||||||
|
bert_classifier = text_classifier.TextClassifier.create(
|
||||||
|
train_data, validation_data, options
|
||||||
|
)
|
||||||
|
loss_fn = bert_classifier._loss_function
|
||||||
|
self.assertIsInstance(loss_fn, loss_functions.SparseFocalLoss)
|
||||||
|
self.assertEqual(loss_fn._gamma, 3.5)
|
||||||
|
self.assertEqual(loss_fn._num_classes, 2)
|
||||||
|
metric_names = [m.name for m in bert_classifier._metric_functions]
|
||||||
|
expected_metric_names = [
|
||||||
|
'accuracy',
|
||||||
|
'recall',
|
||||||
|
'precision',
|
||||||
|
'precision_at_recall_0.2',
|
||||||
|
'recall_at_precision_0.9',
|
||||||
|
]
|
||||||
|
self.assertCountEqual(metric_names, expected_metric_names)
|
||||||
|
|
||||||
|
# Non-binary data
|
||||||
|
tf_dataset = tf.data.Dataset.from_tensor_slices([[0]])
|
||||||
|
data = text_classifier.Dataset(tf_dataset, ['foo', 'bar', 'baz'], 1)
|
||||||
|
with self.assertRaisesWithLiteralMatch(
|
||||||
|
ValueError,
|
||||||
|
'desired_recalls and desired_precisions parameters are binary metrics'
|
||||||
|
' and not supported for num_classes > 2. Found num_classes: 3',
|
||||||
|
):
|
||||||
|
text_classifier.TextClassifier.create(data, data, options)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
# Placeholder for internal Python strict test compatibility macro.
|
# Placeholder for internal Python strict test compatibility macro.
|
||||||
# Placeholder for internal Python strict library and test compatibility macro.
|
# Placeholder for internal Python strict binary and library compatibility macro.
|
||||||
|
|
||||||
licenses(["notice"])
|
licenses(["notice"])
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
# Placeholder for internal Python strict library and test compatibility macro.
|
# Placeholder for internal Python strict binary and library compatibility macro.
|
||||||
# Placeholder for internal Python library rule.
|
# Placeholder for internal Python library rule.
|
||||||
|
|
||||||
licenses(["notice"])
|
licenses(["notice"])
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
# Placeholder for internal Python strict library and test compatibility macro.
|
# Placeholder for internal Python strict binary and library compatibility macro.
|
||||||
# Placeholder for internal Python strict test compatibility macro.
|
# Placeholder for internal Python strict test compatibility macro.
|
||||||
|
|
||||||
licenses(["notice"])
|
licenses(["notice"])
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ opencv-python
|
|||||||
tensorflow>=2.10
|
tensorflow>=2.10
|
||||||
tensorflow-datasets
|
tensorflow-datasets
|
||||||
tensorflow-hub
|
tensorflow-hub
|
||||||
tf-models-official==2.11.6
|
tf-models-official>=2.13.1
|
||||||
|
|||||||
@@ -13,17 +13,17 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
"""MediaPipe solution drawing utils."""
|
"""MediaPipe solution drawing utils."""
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
import math
|
import math
|
||||||
from typing import List, Mapping, Optional, Tuple, Union
|
from typing import List, Mapping, Optional, Tuple, Union
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import dataclasses
|
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from mediapipe.framework.formats import detection_pb2
|
from mediapipe.framework.formats import detection_pb2
|
||||||
from mediapipe.framework.formats import location_data_pb2
|
|
||||||
from mediapipe.framework.formats import landmark_pb2
|
from mediapipe.framework.formats import landmark_pb2
|
||||||
|
from mediapipe.framework.formats import location_data_pb2
|
||||||
|
|
||||||
_PRESENCE_THRESHOLD = 0.5
|
_PRESENCE_THRESHOLD = 0.5
|
||||||
_VISIBILITY_THRESHOLD = 0.5
|
_VISIBILITY_THRESHOLD = 0.5
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import cv2
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from google.protobuf import text_format
|
from google.protobuf import text_format
|
||||||
|
|
||||||
from mediapipe.framework.formats import detection_pb2
|
from mediapipe.framework.formats import detection_pb2
|
||||||
from mediapipe.framework.formats import landmark_pb2
|
from mediapipe.framework.formats import landmark_pb2
|
||||||
from mediapipe.python.solutions import drawing_utils
|
from mediapipe.python.solutions import drawing_utils
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# TODO: describe this package.
|
||||||
|
|
||||||
|
# Copyright 2022 The MediaPipe Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
package(default_visibility = ["//mediapipe/tasks:internal"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "category",
|
||||||
|
hdrs = ["category.h"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "classification_result",
|
||||||
|
hdrs = ["classification_result.h"],
|
||||||
|
)
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/* Copyright 2023 The MediaPipe Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==============================================================================*/
|
||||||
|
|
||||||
|
#ifndef MEDIAPIPE_TASKS_C_COMPONENTS_CONTAINERS_CATEGORY_H_
|
||||||
|
#define MEDIAPIPE_TASKS_C_COMPONENTS_CONTAINERS_CATEGORY_H_
|
||||||
|
|
||||||
|
// Defines a single classification result.
|
||||||
|
//
|
||||||
|
// The label maps packed into the TFLite Model Metadata [1] are used to populate
|
||||||
|
// the 'category_name' and 'display_name' fields.
|
||||||
|
//
|
||||||
|
// [1]: https://www.tensorflow.org/lite/convert/metadata
|
||||||
|
struct Category {
|
||||||
|
// The index of the category in the classification model output.
|
||||||
|
int index;
|
||||||
|
|
||||||
|
// The score for this category, e.g. (but not necessarily) a probability in
|
||||||
|
// [0,1].
|
||||||
|
float score;
|
||||||
|
|
||||||
|
// The optional ID for the category, read from the label map packed in the
|
||||||
|
// TFLite Model Metadata if present. Not necessarily human-readable.
|
||||||
|
char* category_name;
|
||||||
|
|
||||||
|
// The optional human-readable name for the category, read from the label map
|
||||||
|
// packed in the TFLite Model Metadata if present.
|
||||||
|
char* display_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MEDIAPIPE_TASKS_C_COMPONENTS_CONTAINERS_CATEGORY_H_
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/* Copyright 2023 The MediaPipe Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==============================================================================*/
|
||||||
|
|
||||||
|
#ifndef MEDIAPIPE_TASKS_C_COMPONENTS_CONTAINERS_CLASSIFICATION_RESULT_H_
|
||||||
|
#define MEDIAPIPE_TASKS_C_COMPONENTS_CONTAINERS_CLASSIFICATION_RESULT_H_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Defines classification results for a given classifier head.
|
||||||
|
struct Classifications {
|
||||||
|
// The array of predicted categories, usually sorted by descending scores,
|
||||||
|
// e.g. from high to low probability.
|
||||||
|
struct Category* categories;
|
||||||
|
// The number of elements in the categories array.
|
||||||
|
uint32_t categories_count;
|
||||||
|
|
||||||
|
// The index of the classifier head (i.e. output tensor) these categories
|
||||||
|
// refer to. This is useful for multi-head models.
|
||||||
|
int head_index;
|
||||||
|
|
||||||
|
// The optional name of the classifier head, as provided in the TFLite Model
|
||||||
|
// Metadata [1] if present. This is useful for multi-head models.
|
||||||
|
//
|
||||||
|
// [1]: https://www.tensorflow.org/lite/convert/metadata
|
||||||
|
char* head_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Defines classification results of a model.
|
||||||
|
struct ClassificationResult {
|
||||||
|
// The classification results for each head of the model.
|
||||||
|
struct Classifications* classifications;
|
||||||
|
// The number of classifications in the classifications array.
|
||||||
|
uint32_t classifications_count;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
int64_t timestamp_ms;
|
||||||
|
// Specifies whether the timestamp contains a valid value.
|
||||||
|
bool has_timestamp_ms;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MEDIAPIPE_TASKS_C_COMPONENTS_CONTAINERS_CLASSIFICATION_RESULT_H_
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Copyright 2023 The MediaPipe Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
package(default_visibility = ["//mediapipe/tasks:internal"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "classifier_options",
|
||||||
|
hdrs = ["classifier_options.h"],
|
||||||
|
)
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/* Copyright 2023 The MediaPipe Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==============================================================================*/
|
||||||
|
|
||||||
|
#ifndef MEDIAPIPE_TASKS_C_COMPONENTS_PROCESSORS_CLASSIFIER_OPTIONS_H_
|
||||||
|
#define MEDIAPIPE_TASKS_C_COMPONENTS_PROCESSORS_CLASSIFIER_OPTIONS_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Classifier options for MediaPipe C classification Tasks.
|
||||||
|
struct ClassifierOptions {
|
||||||
|
// The locale to use for display names specified through the TFLite Model
|
||||||
|
// Metadata, if any. Defaults to English.
|
||||||
|
char* display_names_locale;
|
||||||
|
|
||||||
|
// The maximum number of top-scored classification results to return. If < 0,
|
||||||
|
// all available results will be returned. If 0, an invalid argument error is
|
||||||
|
// returned.
|
||||||
|
int max_results;
|
||||||
|
|
||||||
|
// Score threshold to override the one provided in the model metadata (if
|
||||||
|
// any). Results below this value are rejected.
|
||||||
|
float score_threshold;
|
||||||
|
|
||||||
|
// The allowlist of category names. If non-empty, detection results whose
|
||||||
|
// category name is not in this set will be filtered out. Duplicate or unknown
|
||||||
|
// category names are ignored. Mutually exclusive with category_denylist.
|
||||||
|
char** category_allowlist;
|
||||||
|
// The number of elements in the category allowlist.
|
||||||
|
uint32_t category_allowlist_count;
|
||||||
|
|
||||||
|
// The denylist of category names. If non-empty, detection results whose
|
||||||
|
// category name is in this set will be filtered out. Duplicate or unknown
|
||||||
|
// category names are ignored. Mutually exclusive with category_allowlist.
|
||||||
|
char** category_denylist = {};
|
||||||
|
// The number of elements in the category denylist.
|
||||||
|
uint32_t category_denylist_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MEDIAPIPE_TASKS_C_COMPONENTS_PROCESSORS_CLASSIFIER_OPTIONS_H_
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Copyright 2023 The MediaPipe Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
package(default_visibility = ["//mediapipe/tasks:internal"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "base_options",
|
||||||
|
hdrs = ["base_options.h"],
|
||||||
|
)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/* Copyright 2023 The MediaPipe Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==============================================================================*/
|
||||||
|
|
||||||
|
#ifndef MEDIAPIPE_TASKS_C_CORE_BASE_OPTIONS_H_
|
||||||
|
#define MEDIAPIPE_TASKS_C_CORE_BASE_OPTIONS_H_
|
||||||
|
|
||||||
|
// Base options for MediaPipe C Tasks.
|
||||||
|
struct BaseOptions {
|
||||||
|
// The model asset file contents as a string.
|
||||||
|
char* model_asset_buffer;
|
||||||
|
|
||||||
|
// The path to the model asset to open and mmap in memory.
|
||||||
|
char* model_asset_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MEDIAPIPE_TASKS_C_CORE_BASE_OPTIONS_H_
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Copyright 2023 The MediaPipe Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
package(default_visibility = ["//mediapipe/tasks:internal"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "text_classifier",
|
||||||
|
hdrs = ["text_classifier.h"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
"//mediapipe/tasks/c/components/containers:classification_result",
|
||||||
|
"//mediapipe/tasks/c/components/processors:classifier_options",
|
||||||
|
"//mediapipe/tasks/c/core:base_options",
|
||||||
|
],
|
||||||
|
)
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/* Copyright 2023 The MediaPipe Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==============================================================================*/
|
||||||
|
|
||||||
|
#ifndef MEDIAPIPE_TASKS_C_TEXT_TEXT_CLASSIFIER_TEXT_CLASSIFIER_H_
|
||||||
|
#define MEDIAPIPE_TASKS_C_TEXT_TEXT_CLASSIFIER_TEXT_CLASSIFIER_H_
|
||||||
|
|
||||||
|
#include "mediapipe/tasks/c/components/containers/classification_result.h"
|
||||||
|
#include "mediapipe/tasks/c/components/processors/classifier_options.h"
|
||||||
|
#include "mediapipe/tasks/c/core/base_options.h"
|
||||||
|
|
||||||
|
typedef ClassificationResult TextClassifierResult;
|
||||||
|
|
||||||
|
// The options for configuring a MediaPipe text classifier task.
|
||||||
|
struct TextClassifierOptions {
|
||||||
|
// Base options for configuring MediaPipe Tasks, such as specifying the model
|
||||||
|
// file with metadata, accelerator options, op resolver, etc.
|
||||||
|
struct BaseOptions base_options;
|
||||||
|
|
||||||
|
// Options for configuring the classifier behavior, such as score threshold,
|
||||||
|
// number of results, etc.
|
||||||
|
struct ClassifierOptions classifier_options;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Creates a TextClassifier from the provided `options`.
|
||||||
|
void* text_classifier_create(struct TextClassifierOptions options);
|
||||||
|
|
||||||
|
// Performs classification on the input `text`.
|
||||||
|
TextClassifierResult text_classifier_classify(void* classifier,
|
||||||
|
char* utf8_text);
|
||||||
|
|
||||||
|
// Shuts down the TextClassifier when all the work is done. Frees all memory.
|
||||||
|
void text_classifier_close(void* classifier);
|
||||||
|
|
||||||
|
#endif // MEDIAPIPE_TASKS_C_TEXT_TEXT_CLASSIFIER_TEXT_CLASSIFIER_H_
|
||||||
@@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
#include "mediapipe/tasks/cc/vision/image_segmenter/image_segmenter.h"
|
#include "mediapipe/tasks/cc/vision/image_segmenter/image_segmenter.h"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "absl/strings/str_format.h"
|
#include "absl/strings/str_format.h"
|
||||||
#include "mediapipe/framework/api2/builder.h"
|
#include "mediapipe/framework/api2/builder.h"
|
||||||
@@ -41,6 +42,8 @@ constexpr char kConfidenceMasksTag[] = "CONFIDENCE_MASKS";
|
|||||||
constexpr char kConfidenceMasksStreamName[] = "confidence_masks";
|
constexpr char kConfidenceMasksStreamName[] = "confidence_masks";
|
||||||
constexpr char kCategoryMaskTag[] = "CATEGORY_MASK";
|
constexpr char kCategoryMaskTag[] = "CATEGORY_MASK";
|
||||||
constexpr char kCategoryMaskStreamName[] = "category_mask";
|
constexpr char kCategoryMaskStreamName[] = "category_mask";
|
||||||
|
constexpr char kOutputSizeTag[] = "OUTPUT_SIZE";
|
||||||
|
constexpr char kOutputSizeStreamName[] = "output_size";
|
||||||
constexpr char kImageInStreamName[] = "image_in";
|
constexpr char kImageInStreamName[] = "image_in";
|
||||||
constexpr char kImageOutStreamName[] = "image_out";
|
constexpr char kImageOutStreamName[] = "image_out";
|
||||||
constexpr char kImageTag[] = "IMAGE";
|
constexpr char kImageTag[] = "IMAGE";
|
||||||
@@ -70,6 +73,7 @@ CalculatorGraphConfig CreateGraphConfig(
|
|||||||
options.get());
|
options.get());
|
||||||
graph.In(kImageTag).SetName(kImageInStreamName);
|
graph.In(kImageTag).SetName(kImageInStreamName);
|
||||||
graph.In(kNormRectTag).SetName(kNormRectStreamName);
|
graph.In(kNormRectTag).SetName(kNormRectStreamName);
|
||||||
|
graph.In(kOutputSizeTag).SetName(kOutputSizeStreamName);
|
||||||
if (output_confidence_masks) {
|
if (output_confidence_masks) {
|
||||||
task_subgraph.Out(kConfidenceMasksTag)
|
task_subgraph.Out(kConfidenceMasksTag)
|
||||||
.SetName(kConfidenceMasksStreamName) >>
|
.SetName(kConfidenceMasksStreamName) >>
|
||||||
@@ -85,10 +89,12 @@ CalculatorGraphConfig CreateGraphConfig(
|
|||||||
graph.Out(kImageTag);
|
graph.Out(kImageTag);
|
||||||
if (enable_flow_limiting) {
|
if (enable_flow_limiting) {
|
||||||
return tasks::core::AddFlowLimiterCalculator(
|
return tasks::core::AddFlowLimiterCalculator(
|
||||||
graph, task_subgraph, {kImageTag, kNormRectTag}, kConfidenceMasksTag);
|
graph, task_subgraph, {kImageTag, kNormRectTag, kOutputSizeTag},
|
||||||
|
kConfidenceMasksTag);
|
||||||
}
|
}
|
||||||
graph.In(kImageTag) >> task_subgraph.In(kImageTag);
|
graph.In(kImageTag) >> task_subgraph.In(kImageTag);
|
||||||
graph.In(kNormRectTag) >> task_subgraph.In(kNormRectTag);
|
graph.In(kNormRectTag) >> task_subgraph.In(kNormRectTag);
|
||||||
|
graph.In(kOutputSizeTag) >> task_subgraph.In(kOutputSizeTag);
|
||||||
return graph.GetConfig();
|
return graph.GetConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +217,13 @@ absl::StatusOr<std::unique_ptr<ImageSegmenter>> ImageSegmenter::Create(
|
|||||||
absl::StatusOr<ImageSegmenterResult> ImageSegmenter::Segment(
|
absl::StatusOr<ImageSegmenterResult> ImageSegmenter::Segment(
|
||||||
mediapipe::Image image,
|
mediapipe::Image image,
|
||||||
std::optional<core::ImageProcessingOptions> image_processing_options) {
|
std::optional<core::ImageProcessingOptions> image_processing_options) {
|
||||||
|
return Segment(image, image.width(), image.height(),
|
||||||
|
std::move(image_processing_options));
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::StatusOr<ImageSegmenterResult> ImageSegmenter::Segment(
|
||||||
|
mediapipe::Image image, int output_width, int output_height,
|
||||||
|
std::optional<core::ImageProcessingOptions> image_processing_options) {
|
||||||
if (image.UsesGpu()) {
|
if (image.UsesGpu()) {
|
||||||
return CreateStatusWithPayload(
|
return CreateStatusWithPayload(
|
||||||
absl::StatusCode::kInvalidArgument,
|
absl::StatusCode::kInvalidArgument,
|
||||||
@@ -225,7 +238,10 @@ absl::StatusOr<ImageSegmenterResult> ImageSegmenter::Segment(
|
|||||||
ProcessImageData(
|
ProcessImageData(
|
||||||
{{kImageInStreamName, mediapipe::MakePacket<Image>(std::move(image))},
|
{{kImageInStreamName, mediapipe::MakePacket<Image>(std::move(image))},
|
||||||
{kNormRectStreamName,
|
{kNormRectStreamName,
|
||||||
MakePacket<NormalizedRect>(std::move(norm_rect))}}));
|
MakePacket<NormalizedRect>(std::move(norm_rect))},
|
||||||
|
{kOutputSizeStreamName,
|
||||||
|
MakePacket<std::pair<int, int>>(
|
||||||
|
std::make_pair(output_width, output_height))}}));
|
||||||
std::optional<std::vector<Image>> confidence_masks;
|
std::optional<std::vector<Image>> confidence_masks;
|
||||||
if (output_confidence_masks_) {
|
if (output_confidence_masks_) {
|
||||||
confidence_masks =
|
confidence_masks =
|
||||||
@@ -243,6 +259,14 @@ absl::StatusOr<ImageSegmenterResult> ImageSegmenter::Segment(
|
|||||||
absl::StatusOr<ImageSegmenterResult> ImageSegmenter::SegmentForVideo(
|
absl::StatusOr<ImageSegmenterResult> ImageSegmenter::SegmentForVideo(
|
||||||
mediapipe::Image image, int64_t timestamp_ms,
|
mediapipe::Image image, int64_t timestamp_ms,
|
||||||
std::optional<core::ImageProcessingOptions> image_processing_options) {
|
std::optional<core::ImageProcessingOptions> image_processing_options) {
|
||||||
|
return SegmentForVideo(image, image.width(), image.height(), timestamp_ms,
|
||||||
|
image_processing_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::StatusOr<ImageSegmenterResult> ImageSegmenter::SegmentForVideo(
|
||||||
|
mediapipe::Image image, int output_width, int output_height,
|
||||||
|
int64_t timestamp_ms,
|
||||||
|
std::optional<core::ImageProcessingOptions> image_processing_options) {
|
||||||
if (image.UsesGpu()) {
|
if (image.UsesGpu()) {
|
||||||
return CreateStatusWithPayload(
|
return CreateStatusWithPayload(
|
||||||
absl::StatusCode::kInvalidArgument,
|
absl::StatusCode::kInvalidArgument,
|
||||||
@@ -260,6 +284,10 @@ absl::StatusOr<ImageSegmenterResult> ImageSegmenter::SegmentForVideo(
|
|||||||
.At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))},
|
.At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))},
|
||||||
{kNormRectStreamName,
|
{kNormRectStreamName,
|
||||||
MakePacket<NormalizedRect>(std::move(norm_rect))
|
MakePacket<NormalizedRect>(std::move(norm_rect))
|
||||||
|
.At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))},
|
||||||
|
{kOutputSizeStreamName,
|
||||||
|
MakePacket<std::pair<int, int>>(
|
||||||
|
std::make_pair(output_width, output_height))
|
||||||
.At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}}));
|
.At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}}));
|
||||||
std::optional<std::vector<Image>> confidence_masks;
|
std::optional<std::vector<Image>> confidence_masks;
|
||||||
if (output_confidence_masks_) {
|
if (output_confidence_masks_) {
|
||||||
@@ -278,6 +306,13 @@ absl::StatusOr<ImageSegmenterResult> ImageSegmenter::SegmentForVideo(
|
|||||||
absl::Status ImageSegmenter::SegmentAsync(
|
absl::Status ImageSegmenter::SegmentAsync(
|
||||||
Image image, int64_t timestamp_ms,
|
Image image, int64_t timestamp_ms,
|
||||||
std::optional<core::ImageProcessingOptions> image_processing_options) {
|
std::optional<core::ImageProcessingOptions> image_processing_options) {
|
||||||
|
return SegmentAsync(image, image.width(), image.height(), timestamp_ms,
|
||||||
|
image_processing_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ImageSegmenter::SegmentAsync(
|
||||||
|
Image image, int output_width, int output_height, int64_t timestamp_ms,
|
||||||
|
std::optional<core::ImageProcessingOptions> image_processing_options) {
|
||||||
if (image.UsesGpu()) {
|
if (image.UsesGpu()) {
|
||||||
return CreateStatusWithPayload(
|
return CreateStatusWithPayload(
|
||||||
absl::StatusCode::kInvalidArgument,
|
absl::StatusCode::kInvalidArgument,
|
||||||
@@ -293,6 +328,10 @@ absl::Status ImageSegmenter::SegmentAsync(
|
|||||||
.At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))},
|
.At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))},
|
||||||
{kNormRectStreamName,
|
{kNormRectStreamName,
|
||||||
MakePacket<NormalizedRect>(std::move(norm_rect))
|
MakePacket<NormalizedRect>(std::move(norm_rect))
|
||||||
|
.At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))},
|
||||||
|
{kOutputSizeStreamName,
|
||||||
|
MakePacket<std::pair<int, int>>(
|
||||||
|
std::make_pair(output_width, output_height))
|
||||||
.At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}});
|
.At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,17 +102,36 @@ class ImageSegmenter : tasks::vision::core::BaseVisionTaskApi {
|
|||||||
//
|
//
|
||||||
// The image can be of any size with format RGB or RGBA.
|
// The image can be of any size with format RGB or RGBA.
|
||||||
//
|
//
|
||||||
|
// The output size is the same as the input image size.
|
||||||
|
//
|
||||||
// The optional 'image_processing_options' parameter can be used to specify
|
// The optional 'image_processing_options' parameter can be used to specify
|
||||||
// the rotation to apply to the image before performing segmentation, by
|
// the rotation to apply to the image before performing segmentation, by
|
||||||
// setting its 'rotation_degrees' field. Note that specifying a
|
// setting its 'rotation_degrees' field. Note that specifying a
|
||||||
// region-of-interest using the 'region_of_interest' field is NOT supported
|
// region-of-interest using the 'region_of_interest' field is NOT supported
|
||||||
// and will result in an invalid argument error being returned.
|
// and will result in an invalid argument error being returned.
|
||||||
|
|
||||||
absl::StatusOr<ImageSegmenterResult> Segment(
|
absl::StatusOr<ImageSegmenterResult> Segment(
|
||||||
mediapipe::Image image,
|
mediapipe::Image image,
|
||||||
std::optional<core::ImageProcessingOptions> image_processing_options =
|
std::optional<core::ImageProcessingOptions> image_processing_options =
|
||||||
std::nullopt);
|
std::nullopt);
|
||||||
|
|
||||||
|
// Performs image segmentation on the provided single image.
|
||||||
|
// Only use this method when the ImageSegmenter is created with the image
|
||||||
|
// running mode.
|
||||||
|
//
|
||||||
|
// The image can be of any size with format RGB or RGBA.
|
||||||
|
//
|
||||||
|
// The output width and height specify the size of the resulted mask.
|
||||||
|
//
|
||||||
|
// The optional 'image_processing_options' parameter can be used to specify
|
||||||
|
// the rotation to apply to the image before performing segmentation, 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.
|
||||||
|
absl::StatusOr<ImageSegmenterResult> Segment(
|
||||||
|
mediapipe::Image image, int output_width, int output_height,
|
||||||
|
std::optional<core::ImageProcessingOptions> image_processing_options =
|
||||||
|
std::nullopt);
|
||||||
|
|
||||||
// Performs image segmentation on the provided video frame.
|
// Performs image segmentation on the provided video frame.
|
||||||
// Only use this method when the ImageSegmenter is created with the video
|
// Only use this method when the ImageSegmenter is created with the video
|
||||||
// running mode.
|
// running mode.
|
||||||
@@ -121,16 +140,39 @@ class ImageSegmenter : tasks::vision::core::BaseVisionTaskApi {
|
|||||||
// provide the video frame's timestamp (in milliseconds). The input timestamps
|
// provide the video frame's timestamp (in milliseconds). The input timestamps
|
||||||
// must be monotonically increasing.
|
// must be monotonically increasing.
|
||||||
//
|
//
|
||||||
// The optional 'image_processing_options' parameter can be used to specify
|
// The output size is the same as the input image size.
|
||||||
// the rotation to apply to the image before performing segmentation, by
|
//
|
||||||
// setting its 'rotation_degrees' field. Note that specifying a
|
// The optional 'image_processing_options' parameter can be used
|
||||||
// region-of-interest using the 'region_of_interest' field is NOT supported
|
// to specify the rotation to apply to the image before performing
|
||||||
|
// segmentation, 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.
|
// and will result in an invalid argument error being returned.
|
||||||
absl::StatusOr<ImageSegmenterResult> SegmentForVideo(
|
absl::StatusOr<ImageSegmenterResult> SegmentForVideo(
|
||||||
mediapipe::Image image, int64_t timestamp_ms,
|
mediapipe::Image image, int64_t timestamp_ms,
|
||||||
std::optional<core::ImageProcessingOptions> image_processing_options =
|
std::optional<core::ImageProcessingOptions> image_processing_options =
|
||||||
std::nullopt);
|
std::nullopt);
|
||||||
|
|
||||||
|
// Performs image segmentation on the provided video frame.
|
||||||
|
// Only use this method when the ImageSegmenter is created with the video
|
||||||
|
// running mode.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// The output width and height specify the size of the resulted mask.
|
||||||
|
//
|
||||||
|
// The optional 'image_processing_options' parameter can be used
|
||||||
|
// to specify the rotation to apply to the image before performing
|
||||||
|
// segmentation, 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.
|
||||||
|
absl::StatusOr<ImageSegmenterResult> SegmentForVideo(
|
||||||
|
mediapipe::Image image, int output_width, int output_height,
|
||||||
|
int64_t timestamp_ms,
|
||||||
|
std::optional<core::ImageProcessingOptions> image_processing_options =
|
||||||
|
std::nullopt);
|
||||||
|
|
||||||
// Sends live image data to perform image segmentation, and the results will
|
// Sends live image data to perform image segmentation, and the results will
|
||||||
// be available via the "result_callback" provided in the
|
// be available via the "result_callback" provided in the
|
||||||
// ImageSegmenterOptions. Only use this method when the ImageSegmenter is
|
// ImageSegmenterOptions. Only use this method when the ImageSegmenter is
|
||||||
@@ -141,6 +183,8 @@ class ImageSegmenter : tasks::vision::core::BaseVisionTaskApi {
|
|||||||
// sent to the image segmenter. The input timestamps must be monotonically
|
// sent to the image segmenter. The input timestamps must be monotonically
|
||||||
// increasing.
|
// increasing.
|
||||||
//
|
//
|
||||||
|
// The output size is the same as the input image size.
|
||||||
|
//
|
||||||
// The optional 'image_processing_options' parameter can be used to specify
|
// The optional 'image_processing_options' parameter can be used to specify
|
||||||
// the rotation to apply to the image before performing segmentation, by
|
// the rotation to apply to the image before performing segmentation, by
|
||||||
// setting its 'rotation_degrees' field. Note that specifying a
|
// setting its 'rotation_degrees' field. Note that specifying a
|
||||||
@@ -158,6 +202,36 @@ class ImageSegmenter : tasks::vision::core::BaseVisionTaskApi {
|
|||||||
std::optional<core::ImageProcessingOptions>
|
std::optional<core::ImageProcessingOptions>
|
||||||
image_processing_options = std::nullopt);
|
image_processing_options = std::nullopt);
|
||||||
|
|
||||||
|
// Sends live image data to perform image segmentation, and the results will
|
||||||
|
// be available via the "result_callback" provided in the
|
||||||
|
// ImageSegmenterOptions. Only use this method when the ImageSegmenter 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 image segmenter. The input timestamps must be monotonically
|
||||||
|
// increasing.
|
||||||
|
//
|
||||||
|
// The output width and height specify the size of the resulted mask.
|
||||||
|
//
|
||||||
|
// The optional 'image_processing_options' parameter can be used to specify
|
||||||
|
// the rotation to apply to the image before performing segmentation, 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" prvoides
|
||||||
|
// - An ImageSegmenterResult.
|
||||||
|
// - The const reference to the corresponding input image that the image
|
||||||
|
// segmentation 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 SegmentAsync(mediapipe::Image image, int output_width,
|
||||||
|
int output_height, int64_t timestamp_ms,
|
||||||
|
std::optional<core::ImageProcessingOptions>
|
||||||
|
image_processing_options = std::nullopt);
|
||||||
|
|
||||||
// Shuts down the ImageSegmenter when all works are done.
|
// Shuts down the ImageSegmenter when all works are done.
|
||||||
absl::Status Close() { return runner_->Close(); }
|
absl::Status Close() { return runner_->Close(); }
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ constexpr char kImageGpuTag[] = "IMAGE_GPU";
|
|||||||
constexpr char kNormRectTag[] = "NORM_RECT";
|
constexpr char kNormRectTag[] = "NORM_RECT";
|
||||||
constexpr char kTensorsTag[] = "TENSORS";
|
constexpr char kTensorsTag[] = "TENSORS";
|
||||||
constexpr char kOutputSizeTag[] = "OUTPUT_SIZE";
|
constexpr char kOutputSizeTag[] = "OUTPUT_SIZE";
|
||||||
|
constexpr char kSizeTag[] = "SIZE";
|
||||||
constexpr char kQualityScoresTag[] = "QUALITY_SCORES";
|
constexpr char kQualityScoresTag[] = "QUALITY_SCORES";
|
||||||
constexpr char kSegmentationMetadataName[] = "SEGMENTER_METADATA";
|
constexpr char kSegmentationMetadataName[] = "SEGMENTER_METADATA";
|
||||||
|
|
||||||
@@ -356,6 +357,9 @@ absl::StatusOr<ImageAndTensorsOnDevice> ConvertImageToTensors(
|
|||||||
// Describes image rotation and region of image to perform detection
|
// Describes image rotation and region of image to perform detection
|
||||||
// on.
|
// on.
|
||||||
// @Optional: rect covering the whole image is used if not specified.
|
// @Optional: rect covering the whole image is used if not specified.
|
||||||
|
// OUTPUT_SIZE - std::pair<int, int> @Optional
|
||||||
|
// The output size of the mask, in width and height. If not specified, the
|
||||||
|
// output size of the input image is used.
|
||||||
//
|
//
|
||||||
// Outputs:
|
// Outputs:
|
||||||
// CONFIDENCE_MASK - mediapipe::Image @Multiple
|
// CONFIDENCE_MASK - mediapipe::Image @Multiple
|
||||||
@@ -400,11 +404,16 @@ class ImageSegmenterGraph : public core::ModelTaskGraph {
|
|||||||
if (!options.segmenter_options().has_output_type()) {
|
if (!options.segmenter_options().has_output_type()) {
|
||||||
MP_RETURN_IF_ERROR(SanityCheck(sc));
|
MP_RETURN_IF_ERROR(SanityCheck(sc));
|
||||||
}
|
}
|
||||||
|
std::optional<Source<std::pair<int, int>>> output_size;
|
||||||
|
if (HasInput(sc->OriginalNode(), kOutputSizeTag)) {
|
||||||
|
output_size = graph.In(kOutputSizeTag).Cast<std::pair<int, int>>();
|
||||||
|
}
|
||||||
ASSIGN_OR_RETURN(
|
ASSIGN_OR_RETURN(
|
||||||
auto output_streams,
|
auto output_streams,
|
||||||
BuildSegmentationTask(
|
BuildSegmentationTask(
|
||||||
options, *model_resources, graph[Input<Image>(kImageTag)],
|
options, *model_resources, graph[Input<Image>(kImageTag)],
|
||||||
graph[Input<NormalizedRect>::Optional(kNormRectTag)], graph));
|
graph[Input<NormalizedRect>::Optional(kNormRectTag)], output_size,
|
||||||
|
graph));
|
||||||
|
|
||||||
// TODO: remove deprecated output type support.
|
// TODO: remove deprecated output type support.
|
||||||
if (options.segmenter_options().has_output_type()) {
|
if (options.segmenter_options().has_output_type()) {
|
||||||
@@ -469,7 +478,8 @@ class ImageSegmenterGraph : public core::ModelTaskGraph {
|
|||||||
absl::StatusOr<ImageSegmenterOutputs> BuildSegmentationTask(
|
absl::StatusOr<ImageSegmenterOutputs> BuildSegmentationTask(
|
||||||
const ImageSegmenterGraphOptions& task_options,
|
const ImageSegmenterGraphOptions& task_options,
|
||||||
const core::ModelResources& model_resources, Source<Image> image_in,
|
const core::ModelResources& model_resources, Source<Image> image_in,
|
||||||
Source<NormalizedRect> norm_rect_in, Graph& graph) {
|
Source<NormalizedRect> norm_rect_in,
|
||||||
|
std::optional<Source<std::pair<int, int>>> output_size, Graph& graph) {
|
||||||
MP_RETURN_IF_ERROR(SanityCheckOptions(task_options));
|
MP_RETURN_IF_ERROR(SanityCheckOptions(task_options));
|
||||||
|
|
||||||
// Adds preprocessing calculators and connects them to the graph input image
|
// Adds preprocessing calculators and connects them to the graph input image
|
||||||
@@ -514,10 +524,14 @@ class ImageSegmenterGraph : public core::ModelTaskGraph {
|
|||||||
image_and_tensors.tensors >> inference.In(kTensorsTag);
|
image_and_tensors.tensors >> inference.In(kTensorsTag);
|
||||||
inference.Out(kTensorsTag) >> tensor_to_images.In(kTensorsTag);
|
inference.Out(kTensorsTag) >> tensor_to_images.In(kTensorsTag);
|
||||||
|
|
||||||
// Adds image property calculator for output size.
|
if (output_size.has_value()) {
|
||||||
auto& image_properties = graph.AddNode("ImagePropertiesCalculator");
|
*output_size >> tensor_to_images.In(kOutputSizeTag);
|
||||||
image_in >> image_properties.In("IMAGE");
|
} else {
|
||||||
image_properties.Out("SIZE") >> tensor_to_images.In(kOutputSizeTag);
|
// Adds image property calculator for output size.
|
||||||
|
auto& image_properties = graph.AddNode("ImagePropertiesCalculator");
|
||||||
|
image_in >> image_properties.In(kImageTag);
|
||||||
|
image_properties.Out(kSizeTag) >> tensor_to_images.In(kOutputSizeTag);
|
||||||
|
}
|
||||||
|
|
||||||
// Exports multiple segmented masks.
|
// Exports multiple segmented masks.
|
||||||
// TODO: remove deprecated output type support.
|
// TODO: remove deprecated output type support.
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ strip_api_include_path_prefix(
|
|||||||
"//mediapipe/tasks/ios/components/containers:sources/MPPClassificationResult.h",
|
"//mediapipe/tasks/ios/components/containers:sources/MPPClassificationResult.h",
|
||||||
"//mediapipe/tasks/ios/components/containers:sources/MPPEmbedding.h",
|
"//mediapipe/tasks/ios/components/containers:sources/MPPEmbedding.h",
|
||||||
"//mediapipe/tasks/ios/components/containers:sources/MPPEmbeddingResult.h",
|
"//mediapipe/tasks/ios/components/containers:sources/MPPEmbeddingResult.h",
|
||||||
|
"//mediapipe/tasks/ios/components/containers:sources/MPPConnection.h",
|
||||||
"//mediapipe/tasks/ios/components/containers:sources/MPPDetection.h",
|
"//mediapipe/tasks/ios/components/containers:sources/MPPDetection.h",
|
||||||
|
"//mediapipe/tasks/ios/components/containers:sources/MPPLandmark.h",
|
||||||
"//mediapipe/tasks/ios/core:sources/MPPBaseOptions.h",
|
"//mediapipe/tasks/ios/core:sources/MPPBaseOptions.h",
|
||||||
"//mediapipe/tasks/ios/core:sources/MPPTaskOptions.h",
|
"//mediapipe/tasks/ios/core:sources/MPPTaskOptions.h",
|
||||||
"//mediapipe/tasks/ios/core:sources/MPPTaskResult.h",
|
"//mediapipe/tasks/ios/core:sources/MPPTaskResult.h",
|
||||||
@@ -160,6 +162,8 @@ apple_static_xcframework(
|
|||||||
":MPPCategory.h",
|
":MPPCategory.h",
|
||||||
":MPPClassificationResult.h",
|
":MPPClassificationResult.h",
|
||||||
":MPPDetection.h",
|
":MPPDetection.h",
|
||||||
|
":MPPLandmark.h",
|
||||||
|
":MPPConnection.h",
|
||||||
":MPPCommon.h",
|
":MPPCommon.h",
|
||||||
":MPPTaskOptions.h",
|
":MPPTaskOptions.h",
|
||||||
":MPPTaskResult.h",
|
":MPPTaskResult.h",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
* The delegate of `MPPFaceLandmarker` must adopt `MPPFaceLandmarkerLiveStreamDelegate` protocol.
|
* The delegate of `MPPFaceLandmarker` must adopt `MPPFaceLandmarkerLiveStreamDelegate` protocol.
|
||||||
* The methods in this protocol are optional.
|
* The methods in this protocol are optional.
|
||||||
*/
|
*/
|
||||||
NS_SWIFT_NAME(FaceDetectorLiveStreamDelegate)
|
NS_SWIFT_NAME(FaceLandmarkerLiveStreamDelegate)
|
||||||
@protocol MPPFaceLandmarkerLiveStreamDelegate <NSObject>
|
@protocol MPPFaceLandmarkerLiveStreamDelegate <NSObject>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -35,3 +35,13 @@ objc_library(
|
|||||||
"//mediapipe/tasks/ios/vision/core:MPPRunningMode",
|
"//mediapipe/tasks/ios/vision/core:MPPRunningMode",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
objc_library(
|
||||||
|
name = "MPPImageSegmenter",
|
||||||
|
hdrs = ["sources/MPPImageSegmenterOptions.h"],
|
||||||
|
deps = [
|
||||||
|
":MPPImageSegmenterOptions",
|
||||||
|
":MPPImageSegmenterResult",
|
||||||
|
"//mediapipe/tasks/ios/vision/core:MPPImage",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,217 @@
|
|||||||
|
// Copyright 2023 The MediaPipe Authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#import "mediapipe/tasks/ios/vision/core/sources/MPPImage.h"
|
||||||
|
#import "mediapipe/tasks/ios/vision/image_segmenter/sources/MPPImageSegmenterOptions.h"
|
||||||
|
#import "mediapipe/tasks/ios/vision/image_segmenter/sources/MPPImageSegmenterResult.h"
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Class that performs segmentation on images.
|
||||||
|
*
|
||||||
|
* The API expects a TFLite model with mandatory TFLite Model Metadata.
|
||||||
|
*/
|
||||||
|
NS_SWIFT_NAME(ImageSegmenter)
|
||||||
|
@interface MPPImageSegmenter : NSObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of `MPPImageSegmenter` from an absolute path to a TensorFlow Lite model
|
||||||
|
* file stored locally on the device and the default `MPPImageSegmenterOptions`.
|
||||||
|
*
|
||||||
|
* @param modelPath An absolute path to a TensorFlow Lite model file stored locally on the device.
|
||||||
|
* @param error An optional error parameter populated when there is an error in initializing the
|
||||||
|
* image segmenter.
|
||||||
|
*
|
||||||
|
* @return A new instance of `MPPImageSegmenter` with the given model path. `nil` if there is an
|
||||||
|
* error in initializing the image segmenter.
|
||||||
|
*/
|
||||||
|
- (nullable instancetype)initWithModelPath:(NSString *)modelPath error:(NSError **)error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of `MPPImageSegmenter` from the given `MPPImageSegmenterOptions`.
|
||||||
|
*
|
||||||
|
* @param options The options of type `MPPImageSegmenterOptions` to use for configuring the
|
||||||
|
* `MPPImageSegmenter`.
|
||||||
|
* @param error An optional error parameter populated when there is an error in initializing the
|
||||||
|
* image segmenter.
|
||||||
|
*
|
||||||
|
* @return A new instance of `MPPImageSegmenter` with the given options. `nil` if there is an error
|
||||||
|
* in initializing the image segmenter.
|
||||||
|
*/
|
||||||
|
- (nullable instancetype)initWithOptions:(MPPImageSegmenterOptions *)options
|
||||||
|
error:(NSError **)error NS_DESIGNATED_INITIALIZER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs segmentation on the provided MPPImage using the whole image as region of interest.
|
||||||
|
* Rotation will be applied according to the `orientation` property of the provided `MPPImage`. Only
|
||||||
|
* use this method when the `MPPImageSegmenter` is created with `MPPRunningModeImage`.
|
||||||
|
*
|
||||||
|
* This method supports RGBA images. If your `MPPImage` has a source type of
|
||||||
|
* `MPPImageSourceTypePixelBuffer` or `MPPImageSourceTypeSampleBuffer`, the underlying pixel buffer
|
||||||
|
* must have one of the following pixel format types:
|
||||||
|
* 1. kCVPixelFormatType_32BGRA
|
||||||
|
* 2. kCVPixelFormatType_32RGBA
|
||||||
|
*
|
||||||
|
* If your `MPPImage` has a source type of `MPPImageSourceTypeImage` ensure that the color space is
|
||||||
|
* RGB with an Alpha channel.
|
||||||
|
*
|
||||||
|
* @param image The `MPPImage` on which segmentation is to be performed.
|
||||||
|
* @param error An optional error parameter populated when there is an error in performing
|
||||||
|
* segmentation on the input image.
|
||||||
|
*
|
||||||
|
* @return An `MPPImageSegmenterResult` that contains the segmented masks.
|
||||||
|
*/
|
||||||
|
- (nullable MPPImageSegmenterResult *)segmentImage:(MPPImage *)image
|
||||||
|
error:(NSError *)error NS_SWIFT_NAME(segment(image:));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs segmentation on the provided MPPImage using the whole image as region of interest and
|
||||||
|
* invokes the given completion handler block with the response. The method returns synchronously
|
||||||
|
* once the completion handler returns.
|
||||||
|
*
|
||||||
|
* Rotation will be applied according to the `orientation` property of the provided
|
||||||
|
* `MPPImage`. Only use this method when the `MPPImageSegmenter` is created with
|
||||||
|
* `MPPRunningModeImage`.
|
||||||
|
*
|
||||||
|
* This method supports RGBA images. If your `MPPImage` has a source type of
|
||||||
|
* `MPPImageSourceTypePixelBuffer` or `MPPImageSourceTypeSampleBuffer`, the underlying pixel buffer
|
||||||
|
* must have one of the following pixel format types:
|
||||||
|
* 1. kCVPixelFormatType_32BGRA
|
||||||
|
* 2. kCVPixelFormatType_32RGBA
|
||||||
|
*
|
||||||
|
* If your `MPPImage` has a source type of `MPPImageSourceTypeImage` ensure that the color space is
|
||||||
|
* RGB with an Alpha channel.
|
||||||
|
*
|
||||||
|
* @param image The `MPPImage` on which segmentation is to be performed.
|
||||||
|
* @param completionHandler A block to be invoked with the results of performing segmentation on the
|
||||||
|
* image. The block takes two arguments, the optional `MPPImageSegmenterResult` that contains the
|
||||||
|
* segmented masks if the segmentation was successful and an optional error populated upon failure.
|
||||||
|
* The lifetime of the returned masks is only guaranteed for the duration of the block.
|
||||||
|
*/
|
||||||
|
- (void)segmentImage:(MPPImage *)image
|
||||||
|
withCompletionHandler:((void ^)(MPPImageSegmenterResult *_Nullable result,
|
||||||
|
NSError *_Nullable error))completionHandler
|
||||||
|
NS_SWIFT_NAME(segment(image:completion:));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs segmentation on the provided video frame of type `MPPImage` using the whole image as
|
||||||
|
* region of interest.
|
||||||
|
*
|
||||||
|
* Rotation will be applied according to the `orientation` property of the provided `MPPImage`. Only
|
||||||
|
* use this method when the `MPPImageSegmenter` is created with `MPPRunningModeVideo`.
|
||||||
|
*
|
||||||
|
* This method supports RGBA images. If your `MPPImage` has a source type of
|
||||||
|
* `MPPImageSourceTypePixelBuffer` or `MPPImageSourceTypeSampleBuffer`, the underlying pixel buffer
|
||||||
|
* must have one of the following pixel format types:
|
||||||
|
* 1. kCVPixelFormatType_32BGRA
|
||||||
|
* 2. kCVPixelFormatType_32RGBA
|
||||||
|
*
|
||||||
|
* If your `MPPImage` has a source type of `MPPImageSourceTypeImage` ensure that the color space is
|
||||||
|
* RGB with an Alpha channel.
|
||||||
|
*
|
||||||
|
* @param image The `MPPImage` on which segmentation is to be performed.
|
||||||
|
* @param timestampInMilliseconds The video frame's timestamp (in milliseconds). The input
|
||||||
|
* timestamps must be monotonically increasing.
|
||||||
|
* @param error An optional error parameter populated when there is an error in performing
|
||||||
|
* segmentation on the input image.
|
||||||
|
*
|
||||||
|
* @return An `MPPImageSegmenterResult` that contains a the segmented masks.
|
||||||
|
*/
|
||||||
|
- (nullable MPPImageSegmenterResult *)segmentVideoFrame:(MPPImage *)image
|
||||||
|
timestampInMilliseconds:(NSInteger)timestampInMilliseconds
|
||||||
|
error:(NSError **)error
|
||||||
|
NS_SWIFT_NAME(segment(videoFrame:timestampInMilliseconds:));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs segmentation on the provided video frame of type `MPPImage` using the whole image as
|
||||||
|
* region of interest invokes the given completion handler block with the response. The method
|
||||||
|
* returns synchronously once the completion handler returns.
|
||||||
|
*
|
||||||
|
* Rotation will be applied according to the `orientation` property of the provided `MPPImage`. Only
|
||||||
|
* use this method when the `MPPImageSegmenter` is created with `MPPRunningModeVideo`.
|
||||||
|
*
|
||||||
|
* This method supports RGBA images. If your `MPPImage` has a source type of
|
||||||
|
* `MPPImageSourceTypePixelBuffer` or `MPPImageSourceTypeSampleBuffer`, the underlying pixel buffer
|
||||||
|
* must have one of the following pixel format types:
|
||||||
|
* 1. kCVPixelFormatType_32BGRA
|
||||||
|
* 2. kCVPixelFormatType_32RGBA
|
||||||
|
*
|
||||||
|
* If your `MPPImage` has a source type of `MPPImageSourceTypeImage` ensure that the color space is
|
||||||
|
* RGB with an Alpha channel.
|
||||||
|
*
|
||||||
|
* @param image The `MPPImage` on which segmentation is to be performed.
|
||||||
|
* @param timestampInMilliseconds The video frame's timestamp (in milliseconds). The input
|
||||||
|
* timestamps must be monotonically increasing.
|
||||||
|
* @param completionHandler A block to be invoked with the results of performing segmentation on the
|
||||||
|
* image. The block takes two arguments, the optional `MPPImageSegmenterResult` that contains the
|
||||||
|
* segmented masks if the segmentation was successful and an optional error only populated upon
|
||||||
|
* failure. The lifetime of the returned masks is only guaranteed for the duration of the block.
|
||||||
|
*/
|
||||||
|
- (void)segmentVideoFrame:(MPPImage *)image
|
||||||
|
timestampInMilliseconds:(NSInteger)timestampInMilliseconds
|
||||||
|
withCompletionHandler:((void ^)(MPPImageSegmenterResult *_Nullable result,
|
||||||
|
NSError *_Nullable error))completionHandler
|
||||||
|
NS_SWIFT_NAME(segment(videoFrame:timestampInMilliseconds:completion:));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends live stream image data of type `MPPImage` to perform segmentation using the whole image as
|
||||||
|
* region of interest.
|
||||||
|
*
|
||||||
|
* Rotation will be applied according to the `orientation` property of the provided `MPPImage`. Only
|
||||||
|
* use this method when the `MPPImageSegmenter` is created with`MPPRunningModeLiveStream`.
|
||||||
|
*
|
||||||
|
* The object which needs to be continuously notified of the available results of image segmentation
|
||||||
|
* must confirm to `MPPImageSegmenterLiveStreamDelegate` protocol and implement the
|
||||||
|
*`imageSegmenter:didFinishSegmentationWithResult:timestampInMilliseconds:error:` delegate method.
|
||||||
|
*
|
||||||
|
* It's required to provide a timestamp (in milliseconds) to indicate when the input image is sent
|
||||||
|
* to the segmenter. The input timestamps must be monotonically increasing.
|
||||||
|
*
|
||||||
|
* This method supports RGBA images. If your `MPPImage` has a source type of
|
||||||
|
*`MPPImageSourceTypePixelBuffer` or `MPPImageSourceTypeSampleBuffer`, the underlying pixel buffer
|
||||||
|
* must have one of the following pixel format types:
|
||||||
|
* 1. kCVPixelFormatType_32BGRA
|
||||||
|
* 2. kCVPixelFormatType_32RGBA
|
||||||
|
*
|
||||||
|
* If the input `MPPImage` has a source type of `MPPImageSourceTypeImage` ensure that the color
|
||||||
|
* space is RGB with an Alpha channel.
|
||||||
|
*
|
||||||
|
* If this method is used for classifying live camera frames using `AVFoundation`, ensure that you
|
||||||
|
* request `AVCaptureVideoDataOutput` to output frames in `kCMPixelFormat_32RGBA` using its
|
||||||
|
* `videoSettings` property.
|
||||||
|
*
|
||||||
|
* @param image A live stream image data of type `MPPImage` on which segmentation is to be
|
||||||
|
* performed.
|
||||||
|
* @param timestampInMilliseconds The timestamp (in milliseconds) which indicates when the input
|
||||||
|
* image is sent to the segmenter. The input timestamps must be monotonically increasing.
|
||||||
|
* @param error An optional error parameter populated when there is an error when sending the input
|
||||||
|
* image to the graph.
|
||||||
|
*
|
||||||
|
* @return `YES` if the image was sent to the task successfully, otherwise `NO`.
|
||||||
|
*/
|
||||||
|
- (BOOL)segmentAsyncInImage:(MPPImage *)image
|
||||||
|
timestampInMilliseconds:(NSInteger)timestampInMilliseconds
|
||||||
|
error:(NSError **)error
|
||||||
|
NS_SWIFT_NAME(segmentAsync(image:timestampInMilliseconds:));
|
||||||
|
|
||||||
|
- (instancetype)init NS_UNAVAILABLE;
|
||||||
|
|
||||||
|
+ (instancetype)new NS_UNAVAILABLE;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
@@ -92,6 +92,9 @@ android_library(
|
|||||||
android_library(
|
android_library(
|
||||||
name = "landmark",
|
name = "landmark",
|
||||||
srcs = ["Landmark.java"],
|
srcs = ["Landmark.java"],
|
||||||
|
javacopts = [
|
||||||
|
"-Xep:AndroidJdkLibsChecker:OFF",
|
||||||
|
],
|
||||||
deps = [
|
deps = [
|
||||||
"//third_party:autovalue",
|
"//third_party:autovalue",
|
||||||
"@maven//:com_google_guava_guava",
|
"@maven//:com_google_guava_guava",
|
||||||
@@ -101,6 +104,9 @@ android_library(
|
|||||||
android_library(
|
android_library(
|
||||||
name = "normalized_landmark",
|
name = "normalized_landmark",
|
||||||
srcs = ["NormalizedLandmark.java"],
|
srcs = ["NormalizedLandmark.java"],
|
||||||
|
javacopts = [
|
||||||
|
"-Xep:AndroidJdkLibsChecker:OFF",
|
||||||
|
],
|
||||||
deps = [
|
deps = [
|
||||||
"//third_party:autovalue",
|
"//third_party:autovalue",
|
||||||
"@maven//:com_google_guava_guava",
|
"@maven//:com_google_guava_guava",
|
||||||
|
|||||||
+24
-2
@@ -16,6 +16,7 @@ package com.google.mediapipe.tasks.components.containers;
|
|||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
import com.google.auto.value.AutoValue;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Landmark represents a point in 3D space with x, y, z coordinates. The landmark coordinates are in
|
* Landmark represents a point in 3D space with x, y, z coordinates. The landmark coordinates are in
|
||||||
@@ -27,7 +28,12 @@ public abstract class Landmark {
|
|||||||
private static final float TOLERANCE = 1e-6f;
|
private static final float TOLERANCE = 1e-6f;
|
||||||
|
|
||||||
public static Landmark create(float x, float y, float z) {
|
public static Landmark create(float x, float y, float z) {
|
||||||
return new AutoValue_Landmark(x, y, z);
|
return new AutoValue_Landmark(x, y, z, Optional.empty(), Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Landmark create(
|
||||||
|
float x, float y, float z, Optional<Float> visibility, Optional<Float> presence) {
|
||||||
|
return new AutoValue_Landmark(x, y, z, visibility, presence);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The x coordinates of the landmark.
|
// The x coordinates of the landmark.
|
||||||
@@ -39,6 +45,12 @@ public abstract class Landmark {
|
|||||||
// The z coordinates of the landmark.
|
// The z coordinates of the landmark.
|
||||||
public abstract float z();
|
public abstract float z();
|
||||||
|
|
||||||
|
// Visibility of the normalized landmark.
|
||||||
|
public abstract Optional<Float> visibility();
|
||||||
|
|
||||||
|
// Presence of the normalized landmark.
|
||||||
|
public abstract Optional<Float> presence();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean equals(Object o) {
|
public final boolean equals(Object o) {
|
||||||
if (!(o instanceof Landmark)) {
|
if (!(o instanceof Landmark)) {
|
||||||
@@ -57,6 +69,16 @@ public abstract class Landmark {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final String toString() {
|
public final String toString() {
|
||||||
return "<Landmark (x=" + x() + " y=" + y() + " z=" + z() + ")>";
|
return "<Landmark (x="
|
||||||
|
+ x()
|
||||||
|
+ " y="
|
||||||
|
+ y()
|
||||||
|
+ " z="
|
||||||
|
+ z()
|
||||||
|
+ " visibility= "
|
||||||
|
+ visibility()
|
||||||
|
+ " presence="
|
||||||
|
+ presence()
|
||||||
|
+ ")>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-2
@@ -16,6 +16,7 @@ package com.google.mediapipe.tasks.components.containers;
|
|||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
import com.google.auto.value.AutoValue;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalized Landmark represents a point in 3D space with x, y, z coordinates. x and y are
|
* Normalized Landmark represents a point in 3D space with x, y, z coordinates. x and y are
|
||||||
@@ -28,7 +29,12 @@ public abstract class NormalizedLandmark {
|
|||||||
private static final float TOLERANCE = 1e-6f;
|
private static final float TOLERANCE = 1e-6f;
|
||||||
|
|
||||||
public static NormalizedLandmark create(float x, float y, float z) {
|
public static NormalizedLandmark create(float x, float y, float z) {
|
||||||
return new AutoValue_NormalizedLandmark(x, y, z);
|
return new AutoValue_NormalizedLandmark(x, y, z, Optional.empty(), Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NormalizedLandmark create(
|
||||||
|
float x, float y, float z, Optional<Float> visibility, Optional<Float> presence) {
|
||||||
|
return new AutoValue_NormalizedLandmark(x, y, z, visibility, presence);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The x coordinates of the normalized landmark.
|
// The x coordinates of the normalized landmark.
|
||||||
@@ -40,6 +46,12 @@ public abstract class NormalizedLandmark {
|
|||||||
// The z coordinates of the normalized landmark.
|
// The z coordinates of the normalized landmark.
|
||||||
public abstract float z();
|
public abstract float z();
|
||||||
|
|
||||||
|
// Visibility of the normalized landmark.
|
||||||
|
public abstract Optional<Float> visibility();
|
||||||
|
|
||||||
|
// Presence of the normalized landmark.
|
||||||
|
public abstract Optional<Float> presence();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean equals(Object o) {
|
public final boolean equals(Object o) {
|
||||||
if (!(o instanceof NormalizedLandmark)) {
|
if (!(o instanceof NormalizedLandmark)) {
|
||||||
@@ -58,6 +70,16 @@ public abstract class NormalizedLandmark {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final String toString() {
|
public final String toString() {
|
||||||
return "<Normalized Landmark (x=" + x() + " y=" + y() + " z=" + z() + ")>";
|
return "<Normalized Landmark (x="
|
||||||
|
+ x()
|
||||||
|
+ " y="
|
||||||
|
+ y()
|
||||||
|
+ " z="
|
||||||
|
+ z()
|
||||||
|
+ " visibility= "
|
||||||
|
+ visibility()
|
||||||
|
+ " presence="
|
||||||
|
+ presence()
|
||||||
|
+ ")>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -53,7 +53,15 @@ public abstract class FaceLandmarkerResult implements TaskResult {
|
|||||||
faceLandmarksProto.getLandmarkList()) {
|
faceLandmarksProto.getLandmarkList()) {
|
||||||
faceLandmarks.add(
|
faceLandmarks.add(
|
||||||
NormalizedLandmark.create(
|
NormalizedLandmark.create(
|
||||||
faceLandmarkProto.getX(), faceLandmarkProto.getY(), faceLandmarkProto.getZ()));
|
faceLandmarkProto.getX(),
|
||||||
|
faceLandmarkProto.getY(),
|
||||||
|
faceLandmarkProto.getZ(),
|
||||||
|
faceLandmarkProto.hasVisibility()
|
||||||
|
? Optional.of(faceLandmarkProto.getVisibility())
|
||||||
|
: Optional.empty(),
|
||||||
|
faceLandmarkProto.hasPresence()
|
||||||
|
? Optional.of(faceLandmarkProto.getPresence())
|
||||||
|
: Optional.empty()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Optional<List<List<Category>>> multiFaceBlendshapes = Optional.empty();
|
Optional<List<List<Category>>> multiFaceBlendshapes = Optional.empty();
|
||||||
|
|||||||
+17
-2
@@ -25,6 +25,7 @@ import com.google.mediapipe.tasks.core.TaskResult;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/** Represents the hand landmarks deection results generated by {@link HandLandmarker}. */
|
/** Represents the hand landmarks deection results generated by {@link HandLandmarker}. */
|
||||||
@AutoValue
|
@AutoValue
|
||||||
@@ -53,7 +54,15 @@ public abstract class HandLandmarkerResult implements TaskResult {
|
|||||||
handLandmarksProto.getLandmarkList()) {
|
handLandmarksProto.getLandmarkList()) {
|
||||||
handLandmarks.add(
|
handLandmarks.add(
|
||||||
NormalizedLandmark.create(
|
NormalizedLandmark.create(
|
||||||
handLandmarkProto.getX(), handLandmarkProto.getY(), handLandmarkProto.getZ()));
|
handLandmarkProto.getX(),
|
||||||
|
handLandmarkProto.getY(),
|
||||||
|
handLandmarkProto.getZ(),
|
||||||
|
handLandmarkProto.hasVisibility()
|
||||||
|
? Optional.of(handLandmarkProto.getVisibility())
|
||||||
|
: Optional.empty(),
|
||||||
|
handLandmarkProto.hasPresence()
|
||||||
|
? Optional.of(handLandmarkProto.getPresence())
|
||||||
|
: Optional.empty()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (LandmarkProto.LandmarkList handWorldLandmarksProto : worldLandmarksProto) {
|
for (LandmarkProto.LandmarkList handWorldLandmarksProto : worldLandmarksProto) {
|
||||||
@@ -65,7 +74,13 @@ public abstract class HandLandmarkerResult implements TaskResult {
|
|||||||
com.google.mediapipe.tasks.components.containers.Landmark.create(
|
com.google.mediapipe.tasks.components.containers.Landmark.create(
|
||||||
handWorldLandmarkProto.getX(),
|
handWorldLandmarkProto.getX(),
|
||||||
handWorldLandmarkProto.getY(),
|
handWorldLandmarkProto.getY(),
|
||||||
handWorldLandmarkProto.getZ()));
|
handWorldLandmarkProto.getZ(),
|
||||||
|
handWorldLandmarkProto.hasVisibility()
|
||||||
|
? Optional.of(handWorldLandmarkProto.getVisibility())
|
||||||
|
: Optional.empty(),
|
||||||
|
handWorldLandmarkProto.hasPresence()
|
||||||
|
? Optional.of(handWorldLandmarkProto.getPresence())
|
||||||
|
: Optional.empty()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (ClassificationList handednessProto : handednessesProto) {
|
for (ClassificationList handednessProto : handednessesProto) {
|
||||||
|
|||||||
+16
-2
@@ -58,7 +58,15 @@ public abstract class PoseLandmarkerResult implements TaskResult {
|
|||||||
poseLandmarksProto.getLandmarkList()) {
|
poseLandmarksProto.getLandmarkList()) {
|
||||||
poseLandmarks.add(
|
poseLandmarks.add(
|
||||||
NormalizedLandmark.create(
|
NormalizedLandmark.create(
|
||||||
poseLandmarkProto.getX(), poseLandmarkProto.getY(), poseLandmarkProto.getZ()));
|
poseLandmarkProto.getX(),
|
||||||
|
poseLandmarkProto.getY(),
|
||||||
|
poseLandmarkProto.getZ(),
|
||||||
|
poseLandmarkProto.hasVisibility()
|
||||||
|
? Optional.of(poseLandmarkProto.getVisibility())
|
||||||
|
: Optional.empty(),
|
||||||
|
poseLandmarkProto.hasPresence()
|
||||||
|
? Optional.of(poseLandmarkProto.getPresence())
|
||||||
|
: Optional.empty()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (LandmarkProto.LandmarkList poseWorldLandmarksProto : worldLandmarksProto) {
|
for (LandmarkProto.LandmarkList poseWorldLandmarksProto : worldLandmarksProto) {
|
||||||
@@ -70,7 +78,13 @@ public abstract class PoseLandmarkerResult implements TaskResult {
|
|||||||
Landmark.create(
|
Landmark.create(
|
||||||
poseWorldLandmarkProto.getX(),
|
poseWorldLandmarkProto.getX(),
|
||||||
poseWorldLandmarkProto.getY(),
|
poseWorldLandmarkProto.getY(),
|
||||||
poseWorldLandmarkProto.getZ()));
|
poseWorldLandmarkProto.getZ(),
|
||||||
|
poseWorldLandmarkProto.hasVisibility()
|
||||||
|
? Optional.of(poseWorldLandmarkProto.getVisibility())
|
||||||
|
: Optional.empty(),
|
||||||
|
poseWorldLandmarkProto.hasPresence()
|
||||||
|
? Optional.of(poseWorldLandmarkProto.getPresence())
|
||||||
|
: Optional.empty()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new AutoValue_PoseLandmarkerResult(
|
return new AutoValue_PoseLandmarkerResult(
|
||||||
|
|||||||
+1
-1
@@ -140,7 +140,7 @@ public class TextEmbedderTest {
|
|||||||
TextEmbedder.cosineSimilarity(
|
TextEmbedder.cosineSimilarity(
|
||||||
result0.embeddingResult().embeddings().get(0),
|
result0.embeddingResult().embeddings().get(0),
|
||||||
result1.embeddingResult().embeddings().get(0));
|
result1.embeddingResult().embeddings().get(0));
|
||||||
assertThat(similarity).isWithin(DOUBLE_DIFF_TOLERANCE).of(0.3477488707202946);
|
assertThat(similarity).isWithin(DOUBLE_DIFF_TOLERANCE).of(0.3565317439544432);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+43
@@ -15,6 +15,7 @@
|
|||||||
package com.google.mediapipe.tasks.vision.poselandmarker;
|
package com.google.mediapipe.tasks.vision.poselandmarker;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
@@ -26,6 +27,7 @@ import com.google.common.truth.Correspondence;
|
|||||||
import com.google.mediapipe.framework.MediaPipeException;
|
import com.google.mediapipe.framework.MediaPipeException;
|
||||||
import com.google.mediapipe.framework.image.BitmapImageBuilder;
|
import com.google.mediapipe.framework.image.BitmapImageBuilder;
|
||||||
import com.google.mediapipe.framework.image.MPImage;
|
import com.google.mediapipe.framework.image.MPImage;
|
||||||
|
import com.google.mediapipe.tasks.components.containers.Landmark;
|
||||||
import com.google.mediapipe.tasks.components.containers.NormalizedLandmark;
|
import com.google.mediapipe.tasks.components.containers.NormalizedLandmark;
|
||||||
import com.google.mediapipe.tasks.components.containers.proto.LandmarksDetectionResultProto.LandmarksDetectionResult;
|
import com.google.mediapipe.tasks.components.containers.proto.LandmarksDetectionResultProto.LandmarksDetectionResult;
|
||||||
import com.google.mediapipe.tasks.core.BaseOptions;
|
import com.google.mediapipe.tasks.core.BaseOptions;
|
||||||
@@ -34,6 +36,7 @@ import com.google.mediapipe.tasks.vision.core.RunningMode;
|
|||||||
import com.google.mediapipe.tasks.vision.poselandmarker.PoseLandmarker.PoseLandmarkerOptions;
|
import com.google.mediapipe.tasks.vision.poselandmarker.PoseLandmarker.PoseLandmarkerOptions;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -50,6 +53,8 @@ public class PoseLandmarkerTest {
|
|||||||
private static final String NO_POSES_IMAGE = "burger.jpg";
|
private static final String NO_POSES_IMAGE = "burger.jpg";
|
||||||
private static final String TAG = "Pose Landmarker Test";
|
private static final String TAG = "Pose Landmarker Test";
|
||||||
private static final float LANDMARKS_ERROR_TOLERANCE = 0.03f;
|
private static final float LANDMARKS_ERROR_TOLERANCE = 0.03f;
|
||||||
|
private static final float VISIBILITY_TOLERANCE = 0.9f;
|
||||||
|
private static final float PRESENCE_TOLERANCE = 0.9f;
|
||||||
private static final int IMAGE_WIDTH = 1000;
|
private static final int IMAGE_WIDTH = 1000;
|
||||||
private static final int IMAGE_HEIGHT = 667;
|
private static final int IMAGE_HEIGHT = 667;
|
||||||
|
|
||||||
@@ -70,6 +75,8 @@ public class PoseLandmarkerTest {
|
|||||||
PoseLandmarkerResult actualResult = poseLandmarker.detect(getImageFromAsset(POSE_IMAGE));
|
PoseLandmarkerResult actualResult = poseLandmarker.detect(getImageFromAsset(POSE_IMAGE));
|
||||||
PoseLandmarkerResult expectedResult = getExpectedPoseLandmarkerResult(POSE_LANDMARKS);
|
PoseLandmarkerResult expectedResult = getExpectedPoseLandmarkerResult(POSE_LANDMARKS);
|
||||||
assertActualResultApproximatelyEqualsToExpectedResult(actualResult, expectedResult);
|
assertActualResultApproximatelyEqualsToExpectedResult(actualResult, expectedResult);
|
||||||
|
assertAllLandmarksAreVisibleAndPresent(
|
||||||
|
actualResult, VISIBILITY_TOLERANCE, PRESENCE_TOLERANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -361,4 +368,40 @@ public class PoseLandmarkerTest {
|
|||||||
assertThat(inputImage.getWidth()).isEqualTo(IMAGE_WIDTH);
|
assertThat(inputImage.getWidth()).isEqualTo(IMAGE_WIDTH);
|
||||||
assertThat(inputImage.getHeight()).isEqualTo(IMAGE_HEIGHT);
|
assertThat(inputImage.getHeight()).isEqualTo(IMAGE_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void assertAllLandmarksAreVisibleAndPresent(
|
||||||
|
PoseLandmarkerResult result, float visbilityThreshold, float presenceThreshold) {
|
||||||
|
for (int i = 0; i < result.landmarks().size(); i++) {
|
||||||
|
List<NormalizedLandmark> landmarks = result.landmarks().get(i);
|
||||||
|
for (int j = 0; j < landmarks.size(); j++) {
|
||||||
|
NormalizedLandmark landmark = landmarks.get(j);
|
||||||
|
String landmarkMessage = "Landmark List " + i + " landmark " + j + ": " + landmark;
|
||||||
|
landmark
|
||||||
|
.visibility()
|
||||||
|
.ifPresent(
|
||||||
|
val ->
|
||||||
|
assertWithMessage(landmarkMessage).that(val).isAtLeast((visbilityThreshold)));
|
||||||
|
landmark
|
||||||
|
.presence()
|
||||||
|
.ifPresent(
|
||||||
|
val -> assertWithMessage(landmarkMessage).that(val).isAtLeast((presenceThreshold)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < result.worldLandmarks().size(); i++) {
|
||||||
|
List<Landmark> landmarks = result.worldLandmarks().get(i);
|
||||||
|
for (int j = 0; j < landmarks.size(); j++) {
|
||||||
|
Landmark landmark = landmarks.get(j);
|
||||||
|
String landmarkMessage = "World Landmark List " + i + " landmark " + j + ": " + landmark;
|
||||||
|
landmark
|
||||||
|
.visibility()
|
||||||
|
.ifPresent(
|
||||||
|
val ->
|
||||||
|
assertWithMessage(landmarkMessage).that(val).isAtLeast((visbilityThreshold)));
|
||||||
|
landmark
|
||||||
|
.presence()
|
||||||
|
.ifPresent(
|
||||||
|
val -> assertWithMessage(landmarkMessage).that(val).isAtLeast((presenceThreshold)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ _TEST_DATA_DIR = 'mediapipe/tasks/testdata/text'
|
|||||||
# Tolerance for embedding vector coordinate values.
|
# Tolerance for embedding vector coordinate values.
|
||||||
_EPSILON = 1e-4
|
_EPSILON = 1e-4
|
||||||
# Tolerance for cosine similarity evaluation.
|
# Tolerance for cosine similarity evaluation.
|
||||||
_SIMILARITY_TOLERANCE = 1e-6
|
_SIMILARITY_TOLERANCE = 1e-3
|
||||||
|
|
||||||
|
|
||||||
class ModelFileType(enum.Enum):
|
class ModelFileType(enum.Enum):
|
||||||
@@ -287,7 +287,7 @@ class TextEmbedderTest(parameterized.TestCase):
|
|||||||
|
|
||||||
@parameterized.parameters(
|
@parameterized.parameters(
|
||||||
# TODO: The similarity should likely be lower
|
# TODO: The similarity should likely be lower
|
||||||
(_BERT_MODEL_FILE, 0.980880),
|
(_BERT_MODEL_FILE, 0.98077),
|
||||||
(_USE_MODEL_FILE, 0.780334),
|
(_USE_MODEL_FILE, 0.780334),
|
||||||
)
|
)
|
||||||
def test_embed_with_different_themes(self, model_file, expected_similarity):
|
def test_embed_with_different_themes(self, model_file, expected_similarity):
|
||||||
|
|||||||
+3
@@ -57,6 +57,7 @@ mediapipe_files(srcs = [
|
|||||||
"hand_landmarker.task",
|
"hand_landmarker.task",
|
||||||
"left_hands.jpg",
|
"left_hands.jpg",
|
||||||
"left_hands_rotated.jpg",
|
"left_hands_rotated.jpg",
|
||||||
|
"leopard_bg_removal_result_512x512.png",
|
||||||
"mobilenet_v1_0.25_192_quantized_1_default_1.tflite",
|
"mobilenet_v1_0.25_192_quantized_1_default_1.tflite",
|
||||||
"mobilenet_v1_0.25_224_1_default_1.tflite",
|
"mobilenet_v1_0.25_224_1_default_1.tflite",
|
||||||
"mobilenet_v1_0.25_224_1_metadata_1.tflite",
|
"mobilenet_v1_0.25_224_1_metadata_1.tflite",
|
||||||
@@ -65,6 +66,7 @@ mediapipe_files(srcs = [
|
|||||||
"mobilenet_v1_0.25_224_quant_without_subgraph_metadata.tflite",
|
"mobilenet_v1_0.25_224_quant_without_subgraph_metadata.tflite",
|
||||||
"mobilenet_v2_1.0_224.tflite",
|
"mobilenet_v2_1.0_224.tflite",
|
||||||
"mobilenet_v3_small_100_224_embedder.tflite",
|
"mobilenet_v3_small_100_224_embedder.tflite",
|
||||||
|
"mobilenetsweep_dptrigmqn384_unit_384_384_fp16quant_fp32input_opt.tflite",
|
||||||
"mozart_square.jpg",
|
"mozart_square.jpg",
|
||||||
"multi_objects.jpg",
|
"multi_objects.jpg",
|
||||||
"multi_objects_rotated.jpg",
|
"multi_objects_rotated.jpg",
|
||||||
@@ -136,6 +138,7 @@ filegroup(
|
|||||||
"hand_landmark_lite.tflite",
|
"hand_landmark_lite.tflite",
|
||||||
"left_hands.jpg",
|
"left_hands.jpg",
|
||||||
"left_hands_rotated.jpg",
|
"left_hands_rotated.jpg",
|
||||||
|
"leopard_bg_removal_result_512x512.png",
|
||||||
"mozart_square.jpg",
|
"mozart_square.jpg",
|
||||||
"multi_objects.jpg",
|
"multi_objects.jpg",
|
||||||
"multi_objects_rotated.jpg",
|
"multi_objects_rotated.jpg",
|
||||||
|
|||||||
@@ -555,9 +555,9 @@ without timestamps, use the `context`.
|
|||||||
|`PREFIX/feature/dimensions`|context int list|`set_feature_dimensions` / `SetFeatureDimensions`|A list of integer dimensions for each feature.|
|
|`PREFIX/feature/dimensions`|context int list|`set_feature_dimensions` / `SetFeatureDimensions`|A list of integer dimensions for each feature.|
|
||||||
|`PREFIX/feature/rate`|context float|`set_feature_rate` / `SetFeatureRate`|The rate that features are calculated as features per second.|
|
|`PREFIX/feature/rate`|context float|`set_feature_rate` / `SetFeatureRate`|The rate that features are calculated as features per second.|
|
||||||
|`PREFIX/feature/bytes/format`|context bytes|`set_feature_bytes_format` / `SetFeatureBytesFormat`|The encoding format if any for features stored as bytes.|
|
|`PREFIX/feature/bytes/format`|context bytes|`set_feature_bytes_format` / `SetFeatureBytesFormat`|The encoding format if any for features stored as bytes.|
|
||||||
|`PREFIX/context_feature/floats`|context float list|`add_context_feature_floats` / `AddContextFeatureFloats`|A list of floats for the entire example.|
|
|`PREFIX/context_feature/floats`|context float list|`set_context_feature_floats` / `AddContextFeatureFloats`|A list of floats for the entire example.|
|
||||||
|`PREFIX/context_feature/bytes`|context bytes list|`add_context_feature_bytes` / `AddContextFeatureBytes`|A list of bytes for the entire example. Maybe be encoded.|
|
|`PREFIX/context_feature/bytes`|context bytes list|`set_context_feature_bytes` / `AddContextFeatureBytes`|A list of bytes for the entire example. Maybe be encoded.|
|
||||||
|`PREFIX/context_feature/ints`|context int list|`add_context_feature_ints` / `AddContextFeatureInts`|A list of ints for the entire example.|
|
|`PREFIX/context_feature/ints`|context int list|`set_context_feature_ints` / `AddContextFeatureInts`|A list of ints for the entire example.|
|
||||||
|
|
||||||
### Keys related to audio
|
### Keys related to audio
|
||||||
Audio is a special subtype of generic features with additional data about the
|
Audio is a special subtype of generic features with additional data about the
|
||||||
|
|||||||
Vendored
+3
-3
@@ -379,9 +379,9 @@ java_library(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
java_proto_library(
|
java_import(
|
||||||
name = "any_java_proto",
|
name = "any_java_proto",
|
||||||
deps = [
|
jars = [
|
||||||
"@com_google_protobuf//:any_proto",
|
"@com_google_protobuf//java/core:libcore.jar",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
+27
@@ -39,3 +39,30 @@ index 4028ccc..483e639 100644
|
|||||||
if (append_newline) {
|
if (append_newline) {
|
||||||
// Fix the ostrstream back how it was before we screwed with it.
|
// Fix the ostrstream back how it was before we screwed with it.
|
||||||
// It's 99.44% certain that we don't need to worry about doing this.
|
// It's 99.44% certain that we don't need to worry about doing this.
|
||||||
|
|
||||||
|
diff --git a/bazel/glog.bzl b/bazel/glog.bzl
|
||||||
|
index dacd934..d7b3d78 100644
|
||||||
|
--- a/bazel/glog.bzl
|
||||||
|
+++ b/bazel/glog.bzl
|
||||||
|
@@ -53,7 +53,6 @@ def glog_library(namespace = "google", with_gflags = 1, **kwargs):
|
||||||
|
)
|
||||||
|
|
||||||
|
common_copts = [
|
||||||
|
- "-std=c++14",
|
||||||
|
"-DGLOG_BAZEL_BUILD",
|
||||||
|
# Inject a C++ namespace.
|
||||||
|
"-DGOOGLE_NAMESPACE='%s'" % namespace,
|
||||||
|
@@ -145,7 +144,13 @@ def glog_library(namespace = "google", with_gflags = 1, **kwargs):
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
+ c14_opts = ["-std=c++14"]
|
||||||
|
+ c17_opts = ["-std=c++17"]
|
||||||
|
+
|
||||||
|
final_lib_copts = select({
|
||||||
|
+ "@bazel_tools//src/conditions:windows": c17_opts,
|
||||||
|
+ "//conditions:default": c14_opts,
|
||||||
|
+ }) + select({
|
||||||
|
"@bazel_tools//src/conditions:windows": common_copts + windows_only_copts,
|
||||||
|
"@bazel_tools//src/conditions:darwin": common_copts + linux_or_darwin_copts + darwin_only_copts,
|
||||||
|
"@bazel_tools//src/conditions:freebsd": common_copts + linux_or_darwin_copts + freebsd_only_copts,
|
||||||
|
|||||||
Vendored
+12
@@ -646,6 +646,12 @@ def external_files():
|
|||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/left_hands_rotated.jpg?generation=1666037068103465"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/left_hands_rotated.jpg?generation=1666037068103465"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
http_file(
|
||||||
|
name = "com_google_mediapipe_leopard_bg_removal_result_512x512_png",
|
||||||
|
sha256 = "30be22e89fdd1d7b985294498ec67509b0caa1ca941fe291fa25f43a3873e4dd",
|
||||||
|
urls = ["https://storage.googleapis.com/mediapipe-assets/leopard_bg_removal_result_512x512.png?generation=1690239134617707"],
|
||||||
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_leopard_bg_removal_result_png",
|
name = "com_google_mediapipe_leopard_bg_removal_result_png",
|
||||||
sha256 = "afd33f2058fd58d189cda86ec931647741a6139970c9bcbc637cdd151ec657c5",
|
sha256 = "afd33f2058fd58d189cda86ec931647741a6139970c9bcbc637cdd151ec657c5",
|
||||||
@@ -712,6 +718,12 @@ def external_files():
|
|||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/mobile_ica_8bit-with-unsupported-metadata-version.tflite?generation=1661875819091013"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/mobile_ica_8bit-with-unsupported-metadata-version.tflite?generation=1661875819091013"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
http_file(
|
||||||
|
name = "com_google_mediapipe_mobilenetsweep_dptrigmqn384_unit_384_384_fp16quant_fp32input_opt_tflite",
|
||||||
|
sha256 = "3c4c7e36b35fc903ecfb51b351b4849b23c57cc18d1416cf6cabaa1522d84760",
|
||||||
|
urls = ["https://storage.googleapis.com/mediapipe-assets/mobilenetsweep_dptrigmqn384_unit_384_384_fp16quant_fp32input_opt.tflite?generation=1690302146106240"],
|
||||||
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_mobilenet_v1_0_25_192_quantized_1_default_1_tflite",
|
name = "com_google_mediapipe_mobilenet_v1_0_25_192_quantized_1_default_1_tflite",
|
||||||
sha256 = "f80999b6324c6f101300c3ee38fbe7e11e74a743b5e0be7350602087fe7430a3",
|
sha256 = "f80999b6324c6f101300c3ee38fbe7e11e74a743b5e0be7350602087fe7430a3",
|
||||||
|
|||||||
Vendored
+24
-24
@@ -12,72 +12,72 @@ def wasm_files():
|
|||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_wasm_audio_wasm_internal_js",
|
name = "com_google_mediapipe_wasm_audio_wasm_internal_js",
|
||||||
sha256 = "0a6d057ead24a09f116dd388146b1614f5e12559a88eb3d141e93d3f8193a29d",
|
sha256 = "9e5f88363212ac1ad505a0b9e59e3dd34413064f3b70219ff8b0216d6a53128f",
|
||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_internal.js?generation=1688751355212943"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_internal.js?generation=1690577772170421"],
|
||||||
)
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_wasm_audio_wasm_internal_wasm",
|
name = "com_google_mediapipe_wasm_audio_wasm_internal_wasm",
|
||||||
sha256 = "3c475f7420f4fe5382d7123c6f5fb21fe08e2bc47e2acbc5aefd82ab589f2850",
|
sha256 = "8e4c7e9efcfe0d1107b40626f14070f17a817d2b830205ae642ea645fa882d28",
|
||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_internal.wasm?generation=1688751357824803"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_internal.wasm?generation=1690577774642876"],
|
||||||
)
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_wasm_audio_wasm_nosimd_internal_js",
|
name = "com_google_mediapipe_wasm_audio_wasm_nosimd_internal_js",
|
||||||
sha256 = "e92c7630cd873b2a3984c41287b65a338d56806baaddd2b6261bddbb4b5f2ea2",
|
sha256 = "9b9d1fbbead06a26461bb664189d46f0c327a1077e67f0aeeb0628d04de13a81",
|
||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_nosimd_internal.js?generation=1688751360158457"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_nosimd_internal.js?generation=1690577777075565"],
|
||||||
)
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_wasm_audio_wasm_nosimd_internal_wasm",
|
name = "com_google_mediapipe_wasm_audio_wasm_nosimd_internal_wasm",
|
||||||
sha256 = "b1445e29bc187f53f6b36da1b9ce505351b4931f16fbc8aa8b34f082dde3becf",
|
sha256 = "44734a8fdb979eb9359de0c0282565d74cdced5d3a6687be849875e0eb11503c",
|
||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_nosimd_internal.wasm?generation=1688751362506882"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/audio_wasm_nosimd_internal.wasm?generation=1690577779811164"],
|
||||||
)
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_wasm_text_wasm_internal_js",
|
name = "com_google_mediapipe_wasm_text_wasm_internal_js",
|
||||||
sha256 = "095161b74dca1991d15483b9525433853c4b141e5682ca0b32f42fba7ec92ed2",
|
sha256 = "93275ebbae8dd2e9be0394391b722a0de5ac9ed51066093b1ac6ec24bebf5813",
|
||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_internal.js?generation=1688751364517949"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_internal.js?generation=1690577782193422"],
|
||||||
)
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_wasm_text_wasm_internal_wasm",
|
name = "com_google_mediapipe_wasm_text_wasm_internal_wasm",
|
||||||
sha256 = "157b3e32546e5ff6a223d2f137a4f52e89ff28c95236a5ffd9baf185559bc3f9",
|
sha256 = "35e734890cae0c51c1ad91e3589d5777b013bcbac64a5bcbb3a67ce4a5815dd6",
|
||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_internal.wasm?generation=1688751366879784"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_internal.wasm?generation=1690577784996034"],
|
||||||
)
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_wasm_text_wasm_nosimd_internal_js",
|
name = "com_google_mediapipe_wasm_text_wasm_nosimd_internal_js",
|
||||||
sha256 = "beae70d5a1a2975cada2d8acbf291ee17a298a75018b1918405e8d6029458231",
|
sha256 = "4e6cea3ae95ffac595bfc08f0dab4ff452c91434eb71f92c0dd34250a46825a1",
|
||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_nosimd_internal.js?generation=1688751369120108"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_nosimd_internal.js?generation=1690577787398460"],
|
||||||
)
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_wasm_text_wasm_nosimd_internal_wasm",
|
name = "com_google_mediapipe_wasm_text_wasm_nosimd_internal_wasm",
|
||||||
sha256 = "1223d5069ba1fa70a585a193d3d5f9bf990d043c0a1de03544ad2869daa8f03c",
|
sha256 = "43cfab25c1d47822015e434d726a80d84e0bfdb5e685a511ab45d8b5cbe944d3",
|
||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_nosimd_internal.wasm?generation=1688751371734691"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/text_wasm_nosimd_internal.wasm?generation=1690577790301890"],
|
||||||
)
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_wasm_vision_wasm_internal_js",
|
name = "com_google_mediapipe_wasm_vision_wasm_internal_js",
|
||||||
sha256 = "8f97c81a2e15065828ca3877aaff90f870e15b628e902e453f28c8c59c373c8b",
|
sha256 = "6a73602a14484297690e69d716e683341b62a5fde8f5debde78de2651cb69bbe",
|
||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_internal.js?generation=1688751373720358"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_internal.js?generation=1690577792657082"],
|
||||||
)
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_wasm_vision_wasm_internal_wasm",
|
name = "com_google_mediapipe_wasm_vision_wasm_internal_wasm",
|
||||||
sha256 = "a007d064939cf4f447416e1e5a777fcabe1413346e1c65982329d05b7472bbc8",
|
sha256 = "3431f70071f3980bf13e638551e9bb333335223e35542ee768db06501f7a26f2",
|
||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_internal.wasm?generation=1688751376340177"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_internal.wasm?generation=1690577795814175"],
|
||||||
)
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_wasm_vision_wasm_nosimd_internal_js",
|
name = "com_google_mediapipe_wasm_vision_wasm_nosimd_internal_js",
|
||||||
sha256 = "42e2ed5d23a36a607f81bc8f6a6801806887b4d284b520b04777230000682592",
|
sha256 = "ece9ac1f41b93340b08682514ca291431ff7084c858caf6455e65b0c6c3eb717",
|
||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_nosimd_internal.js?generation=1688751378413876"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_nosimd_internal.js?generation=1690577798226032"],
|
||||||
)
|
)
|
||||||
|
|
||||||
http_file(
|
http_file(
|
||||||
name = "com_google_mediapipe_wasm_vision_wasm_nosimd_internal_wasm",
|
name = "com_google_mediapipe_wasm_vision_wasm_nosimd_internal_wasm",
|
||||||
sha256 = "2c246638f29add7cc06bc65be3c5f9eddf66296a83a90a9b697c3f6281184b9c",
|
sha256 = "4d54739714db6b3d0fbdd0608c2824c4ccceaaf279aa4ba160f2eab2663b30f2",
|
||||||
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_nosimd_internal.wasm?generation=1688751380722112"],
|
urls = ["https://storage.googleapis.com/mediapipe-assets/wasm/vision_wasm_nosimd_internal.wasm?generation=1690577801077668"],
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user