# 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) ])