You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

237 lines
8.4 KiB

6 years ago
  1. # Copyright 2017 The TensorFlow Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. # ==============================================================================
  15. """Label map utility functions."""
  16. import logging
  17. import tensorflow as tf
  18. from google.protobuf import text_format
  19. from object_detection.protos import string_int_label_map_pb2
  20. def _validate_label_map(label_map):
  21. """Checks if a label map is valid.
  22. Args:
  23. label_map: StringIntLabelMap to validate.
  24. Raises:
  25. ValueError: if label map is invalid.
  26. """
  27. for item in label_map.item:
  28. if item.id < 0:
  29. raise ValueError('Label map ids should be >= 0.')
  30. if (item.id == 0 and item.name != 'background' and
  31. item.display_name != 'background'):
  32. raise ValueError('Label map id 0 is reserved for the background label')
  33. def create_category_index(categories):
  34. """Creates dictionary of COCO compatible categories keyed by category id.
  35. Args:
  36. categories: a list of dicts, each of which has the following keys:
  37. 'id': (required) an integer id uniquely identifying this category.
  38. 'name': (required) string representing category name
  39. e.g., 'cat', 'dog', 'pizza'.
  40. Returns:
  41. category_index: a dict containing the same entries as categories, but keyed
  42. by the 'id' field of each category.
  43. """
  44. category_index = {}
  45. for cat in categories:
  46. category_index[cat['id']] = cat
  47. return category_index
  48. def get_max_label_map_index(label_map):
  49. """Get maximum index in label map.
  50. Args:
  51. label_map: a StringIntLabelMapProto
  52. Returns:
  53. an integer
  54. """
  55. return max([item.id for item in label_map.item])
  56. def convert_label_map_to_categories(label_map,
  57. max_num_classes,
  58. use_display_name=True):
  59. """Given label map proto returns categories list compatible with eval.
  60. This function converts label map proto and returns a list of dicts, each of
  61. which has the following keys:
  62. 'id': (required) an integer id uniquely identifying this category.
  63. 'name': (required) string representing category name
  64. e.g., 'cat', 'dog', 'pizza'.
  65. We only allow class into the list if its id-label_id_offset is
  66. between 0 (inclusive) and max_num_classes (exclusive).
  67. If there are several items mapping to the same id in the label map,
  68. we will only keep the first one in the categories list.
  69. Args:
  70. label_map: a StringIntLabelMapProto or None. If None, a default categories
  71. list is created with max_num_classes categories.
  72. max_num_classes: maximum number of (consecutive) label indices to include.
  73. use_display_name: (boolean) choose whether to load 'display_name' field as
  74. category name. If False or if the display_name field does not exist, uses
  75. 'name' field as category names instead.
  76. Returns:
  77. categories: a list of dictionaries representing all possible categories.
  78. """
  79. categories = []
  80. list_of_ids_already_added = []
  81. if not label_map:
  82. label_id_offset = 1
  83. for class_id in range(max_num_classes):
  84. categories.append({
  85. 'id': class_id + label_id_offset,
  86. 'name': 'category_{}'.format(class_id + label_id_offset)
  87. })
  88. return categories
  89. for item in label_map.item:
  90. if not 0 < item.id <= max_num_classes:
  91. logging.info(
  92. 'Ignore item %d since it falls outside of requested '
  93. 'label range.', item.id)
  94. continue
  95. if use_display_name and item.HasField('display_name'):
  96. name = item.display_name
  97. else:
  98. name = item.name
  99. if item.id not in list_of_ids_already_added:
  100. list_of_ids_already_added.append(item.id)
  101. categories.append({'id': item.id, 'name': name})
  102. return categories
  103. def load_labelmap(path):
  104. """Loads label map proto.
  105. Args:
  106. path: path to StringIntLabelMap proto text file.
  107. Returns:
  108. a StringIntLabelMapProto
  109. """
  110. with tf.gfile.GFile(path, 'r') as fid:
  111. label_map_string = fid.read()
  112. label_map = string_int_label_map_pb2.StringIntLabelMap()
  113. try:
  114. text_format.Merge(label_map_string, label_map)
  115. except text_format.ParseError:
  116. label_map.ParseFromString(label_map_string)
  117. _validate_label_map(label_map)
  118. return label_map
  119. def get_label_map_dict(label_map_path,
  120. use_display_name=False,
  121. fill_in_gaps_and_background=False):
  122. """Reads a label map and returns a dictionary of label names to id.
  123. Args:
  124. label_map_path: path to StringIntLabelMap proto text file.
  125. use_display_name: whether to use the label map items' display names as keys.
  126. fill_in_gaps_and_background: whether to fill in gaps and background with
  127. respect to the id field in the proto. The id: 0 is reserved for the
  128. 'background' class and will be added if it is missing. All other missing
  129. ids in range(1, max(id)) will be added with a dummy class name
  130. ("class_<id>") if they are missing.
  131. Returns:
  132. A dictionary mapping label names to id.
  133. Raises:
  134. ValueError: if fill_in_gaps_and_background and label_map has non-integer or
  135. negative values.
  136. """
  137. label_map = load_labelmap(label_map_path)
  138. label_map_dict = {}
  139. for item in label_map.item:
  140. if use_display_name:
  141. label_map_dict[item.display_name] = item.id
  142. else:
  143. label_map_dict[item.name] = item.id
  144. if fill_in_gaps_and_background:
  145. values = set(label_map_dict.values())
  146. if 0 not in values:
  147. label_map_dict['background'] = 0
  148. if not all(isinstance(value, int) for value in values):
  149. raise ValueError('The values in label map must be integers in order to'
  150. 'fill_in_gaps_and_background.')
  151. if not all(value >= 0 for value in values):
  152. raise ValueError('The values in the label map must be positive.')
  153. if len(values) != max(values) + 1:
  154. # there are gaps in the labels, fill in gaps.
  155. for value in range(1, max(values)):
  156. if value not in values:
  157. # TODO(rathodv): Add a prefix 'class_' here once the tool to generate
  158. # teacher annotation adds this prefix in the data.
  159. label_map_dict[str(value)] = value
  160. return label_map_dict
  161. def create_categories_from_labelmap(label_map_path, use_display_name=True):
  162. """Reads a label map and returns categories list compatible with eval.
  163. This function converts label map proto and returns a list of dicts, each of
  164. which has the following keys:
  165. 'id': an integer id uniquely identifying this category.
  166. 'name': string representing category name e.g., 'cat', 'dog'.
  167. Args:
  168. label_map_path: Path to `StringIntLabelMap` proto text file.
  169. use_display_name: (boolean) choose whether to load 'display_name' field
  170. as category name. If False or if the display_name field does not exist,
  171. uses 'name' field as category names instead.
  172. Returns:
  173. categories: a list of dictionaries representing all possible categories.
  174. """
  175. label_map = load_labelmap(label_map_path)
  176. max_num_classes = max(item.id for item in label_map.item)
  177. return convert_label_map_to_categories(label_map, max_num_classes,
  178. use_display_name)
  179. def create_category_index_from_labelmap(label_map_path, use_display_name=True):
  180. """Reads a label map and returns a category index.
  181. Args:
  182. label_map_path: Path to `StringIntLabelMap` proto text file.
  183. use_display_name: (boolean) choose whether to load 'display_name' field
  184. as category name. If False or if the display_name field does not exist,
  185. uses 'name' field as category names instead.
  186. Returns:
  187. A category index, which is a dictionary that maps integer ids to dicts
  188. containing categories, e.g.
  189. {1: {'id': 1, 'name': 'dog'}, 2: {'id': 2, 'name': 'cat'}, ...}
  190. """
  191. categories = create_categories_from_labelmap(label_map_path, use_display_name)
  192. return create_category_index(categories)
  193. def create_class_agnostic_category_index():
  194. """Creates a category index with a single `object` class."""
  195. return {1: {'id': 1, 'name': 'object'}}