|
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ==============================================================================
|
|
|
|
"""Contains functions which are convenient for unit testing."""
|
|
import numpy as np
|
|
import tensorflow as tf
|
|
|
|
from object_detection.core import anchor_generator
|
|
from object_detection.core import box_coder
|
|
from object_detection.core import box_list
|
|
from object_detection.core import box_predictor
|
|
from object_detection.core import matcher
|
|
from object_detection.utils import shape_utils
|
|
|
|
# Default size (both width and height) used for testing mask predictions.
|
|
DEFAULT_MASK_SIZE = 5
|
|
|
|
|
|
class MockBoxCoder(box_coder.BoxCoder):
|
|
"""Simple `difference` BoxCoder."""
|
|
|
|
@property
|
|
def code_size(self):
|
|
return 4
|
|
|
|
def _encode(self, boxes, anchors):
|
|
return boxes.get() - anchors.get()
|
|
|
|
def _decode(self, rel_codes, anchors):
|
|
return box_list.BoxList(rel_codes + anchors.get())
|
|
|
|
|
|
class MockMaskHead(object):
|
|
"""Simple maskhead that returns all zeros as mask predictions."""
|
|
|
|
def __init__(self, num_classes):
|
|
self._num_classes = num_classes
|
|
|
|
def predict(self, features):
|
|
batch_size = tf.shape(features)[0]
|
|
return tf.zeros((batch_size, 1, self._num_classes, DEFAULT_MASK_SIZE,
|
|
DEFAULT_MASK_SIZE),
|
|
dtype=tf.float32)
|
|
|
|
|
|
class MockBoxPredictor(box_predictor.BoxPredictor):
|
|
"""Simple box predictor that ignores inputs and outputs all zeros."""
|
|
|
|
def __init__(self, is_training, num_classes, add_background_class=True):
|
|
super(MockBoxPredictor, self).__init__(is_training, num_classes)
|
|
self._add_background_class = add_background_class
|
|
|
|
def _predict(self, image_features, num_predictions_per_location):
|
|
image_feature = image_features[0]
|
|
combined_feature_shape = shape_utils.combined_static_and_dynamic_shape(
|
|
image_feature)
|
|
batch_size = combined_feature_shape[0]
|
|
num_anchors = (combined_feature_shape[1] * combined_feature_shape[2])
|
|
code_size = 4
|
|
zero = tf.reduce_sum(0 * image_feature)
|
|
num_class_slots = self.num_classes
|
|
if self._add_background_class:
|
|
num_class_slots = num_class_slots + 1
|
|
box_encodings = zero + tf.zeros(
|
|
(batch_size, num_anchors, 1, code_size), dtype=tf.float32)
|
|
class_predictions_with_background = zero + tf.zeros(
|
|
(batch_size, num_anchors, num_class_slots), dtype=tf.float32)
|
|
predictions_dict = {
|
|
box_predictor.BOX_ENCODINGS:
|
|
box_encodings,
|
|
box_predictor.CLASS_PREDICTIONS_WITH_BACKGROUND:
|
|
class_predictions_with_background
|
|
}
|
|
return predictions_dict
|
|
|
|
|
|
class MockKerasBoxPredictor(box_predictor.KerasBoxPredictor):
|
|
"""Simple box predictor that ignores inputs and outputs all zeros."""
|
|
|
|
def __init__(self, is_training, num_classes, add_background_class=True):
|
|
super(MockKerasBoxPredictor, self).__init__(
|
|
is_training, num_classes, False, False)
|
|
self._add_background_class = add_background_class
|
|
|
|
def _predict(self, image_features, **kwargs):
|
|
image_feature = image_features[0]
|
|
combined_feature_shape = shape_utils.combined_static_and_dynamic_shape(
|
|
image_feature)
|
|
batch_size = combined_feature_shape[0]
|
|
num_anchors = (combined_feature_shape[1] * combined_feature_shape[2])
|
|
code_size = 4
|
|
zero = tf.reduce_sum(0 * image_feature)
|
|
num_class_slots = self.num_classes
|
|
if self._add_background_class:
|
|
num_class_slots = num_class_slots + 1
|
|
box_encodings = zero + tf.zeros(
|
|
(batch_size, num_anchors, 1, code_size), dtype=tf.float32)
|
|
class_predictions_with_background = zero + tf.zeros(
|
|
(batch_size, num_anchors, num_class_slots), dtype=tf.float32)
|
|
predictions_dict = {
|
|
box_predictor.BOX_ENCODINGS:
|
|
box_encodings,
|
|
box_predictor.CLASS_PREDICTIONS_WITH_BACKGROUND:
|
|
class_predictions_with_background
|
|
}
|
|
return predictions_dict
|
|
|
|
|
|
class MockAnchorGenerator(anchor_generator.AnchorGenerator):
|
|
"""Mock anchor generator."""
|
|
|
|
def name_scope(self):
|
|
return 'MockAnchorGenerator'
|
|
|
|
def num_anchors_per_location(self):
|
|
return [1]
|
|
|
|
def _generate(self, feature_map_shape_list):
|
|
num_anchors = sum([shape[0] * shape[1] for shape in feature_map_shape_list])
|
|
return box_list.BoxList(tf.zeros((num_anchors, 4), dtype=tf.float32))
|
|
|
|
|
|
class MockMatcher(matcher.Matcher):
|
|
"""Simple matcher that matches first anchor to first groundtruth box."""
|
|
|
|
def _match(self, similarity_matrix, valid_rows):
|
|
return tf.constant([0, -1, -1, -1], dtype=tf.int32)
|
|
|
|
|
|
def create_diagonal_gradient_image(height, width, depth):
|
|
"""Creates pyramid image. Useful for testing.
|
|
|
|
For example, pyramid_image(5, 6, 1) looks like:
|
|
# [[[ 5. 4. 3. 2. 1. 0.]
|
|
# [ 6. 5. 4. 3. 2. 1.]
|
|
# [ 7. 6. 5. 4. 3. 2.]
|
|
# [ 8. 7. 6. 5. 4. 3.]
|
|
# [ 9. 8. 7. 6. 5. 4.]]]
|
|
|
|
Args:
|
|
height: height of image
|
|
width: width of image
|
|
depth: depth of image
|
|
|
|
Returns:
|
|
pyramid image
|
|
"""
|
|
row = np.arange(height)
|
|
col = np.arange(width)[::-1]
|
|
image_layer = np.expand_dims(row, 1) + col
|
|
image_layer = np.expand_dims(image_layer, 2)
|
|
|
|
image = image_layer
|
|
for i in range(1, depth):
|
|
image = np.concatenate((image, image_layer * pow(10, i)), 2)
|
|
|
|
return image.astype(np.float32)
|
|
|
|
|
|
def create_random_boxes(num_boxes, max_height, max_width):
|
|
"""Creates random bounding boxes of specific maximum height and width.
|
|
|
|
Args:
|
|
num_boxes: number of boxes.
|
|
max_height: maximum height of boxes.
|
|
max_width: maximum width of boxes.
|
|
|
|
Returns:
|
|
boxes: numpy array of shape [num_boxes, 4]. Each row is in form
|
|
[y_min, x_min, y_max, x_max].
|
|
"""
|
|
|
|
y_1 = np.random.uniform(size=(1, num_boxes)) * max_height
|
|
y_2 = np.random.uniform(size=(1, num_boxes)) * max_height
|
|
x_1 = np.random.uniform(size=(1, num_boxes)) * max_width
|
|
x_2 = np.random.uniform(size=(1, num_boxes)) * max_width
|
|
|
|
boxes = np.zeros(shape=(num_boxes, 4))
|
|
boxes[:, 0] = np.minimum(y_1, y_2)
|
|
boxes[:, 1] = np.minimum(x_1, x_2)
|
|
boxes[:, 2] = np.maximum(y_1, y_2)
|
|
boxes[:, 3] = np.maximum(x_1, x_2)
|
|
|
|
return boxes.astype(np.float32)
|
|
|
|
|
|
def first_rows_close_as_set(a, b, k=None, rtol=1e-6, atol=1e-6):
|
|
"""Checks if first K entries of two lists are close, up to permutation.
|
|
|
|
Inputs to this assert are lists of items which can be compared via
|
|
numpy.allclose(...) and can be sorted.
|
|
|
|
Args:
|
|
a: list of items which can be compared via numpy.allclose(...) and are
|
|
sortable.
|
|
b: list of items which can be compared via numpy.allclose(...) and are
|
|
sortable.
|
|
k: a non-negative integer. If not provided, k is set to be len(a).
|
|
rtol: relative tolerance.
|
|
atol: absolute tolerance.
|
|
|
|
Returns:
|
|
boolean, True if input lists a and b have the same length and
|
|
the first k entries of the inputs satisfy numpy.allclose() after
|
|
sorting entries.
|
|
"""
|
|
if not isinstance(a, list) or not isinstance(b, list) or len(a) != len(b):
|
|
return False
|
|
if not k:
|
|
k = len(a)
|
|
k = min(k, len(a))
|
|
a_sorted = sorted(a[:k])
|
|
b_sorted = sorted(b[:k])
|
|
return all([
|
|
np.allclose(entry_a, entry_b, rtol, atol)
|
|
for (entry_a, entry_b) in zip(a_sorted, b_sorted)
|
|
])
|