| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| """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_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) |
| ]) |
|
|