|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""Converts COCO data to sharded TFRecord file format with Example protos. |
|
|
|
Please check |
|
../g3doc/setup/coco.md |
|
for instructions. |
|
""" |
|
|
|
import collections |
|
import json |
|
import math |
|
import os |
|
|
|
from typing import Sequence, Tuple, Any |
|
|
|
from absl import app |
|
from absl import flags |
|
from absl import logging |
|
import numpy as np |
|
import tensorflow as tf |
|
|
|
from deeplab2.data import coco_constants |
|
from deeplab2.data import data_utils |
|
from deeplab2.data import dataset |
|
|
|
FLAGS = flags.FLAGS |
|
|
|
flags.DEFINE_string('coco_root', None, 'coco dataset root folder.') |
|
|
|
flags.DEFINE_string('output_dir', None, |
|
'Path to save converted TFRecord of TensorFlow examples.') |
|
|
|
flags.DEFINE_boolean('treat_crowd_as_ignore', True, |
|
'Whether to apply ignore labels to crowd pixels in ' |
|
'panoptic label.') |
|
|
|
_NUM_SHARDS = 1000 |
|
|
|
|
|
_SPLITS_TO_SIZES = dataset.COCO_PANOPTIC_INFORMATION.splits_to_sizes |
|
_IGNORE_LABEL = dataset.COCO_PANOPTIC_INFORMATION.ignore_label |
|
_CLASS_HAS_INSTANCE_LIST = dataset.COCO_PANOPTIC_INFORMATION.class_has_instances_list |
|
_PANOPTIC_LABEL_DIVISOR = dataset.COCO_PANOPTIC_INFORMATION.panoptic_label_divisor |
|
_CLASS_MAPPING = coco_constants.get_id_mapping() |
|
|
|
|
|
_FOLDERS_MAP = { |
|
'train': { |
|
'image': 'train2017', |
|
'label': 'annotations', |
|
}, |
|
'val': { |
|
'image': 'val2017', |
|
'label': 'annotations', |
|
}, |
|
'test': { |
|
'image': 'test2017', |
|
'label': '', |
|
} |
|
} |
|
|
|
|
|
_DATA_FORMAT_MAP = { |
|
'image': 'jpg', |
|
'label': 'png', |
|
} |
|
_PANOPTIC_LABEL_FORMAT = 'raw' |
|
|
|
|
|
def _get_images(coco_root: str, dataset_split: str) -> Sequence[str]: |
|
"""Gets files for the specified data type and dataset split. |
|
|
|
Args: |
|
coco_root: String, path to coco dataset root folder. |
|
dataset_split: String, dataset split ('train', 'val', 'test'). |
|
|
|
Returns: |
|
A list of sorted file names. |
|
""" |
|
pattern = '*.%s' % _DATA_FORMAT_MAP['image'] |
|
search_files = os.path.join( |
|
coco_root, _FOLDERS_MAP[dataset_split]['image'], pattern) |
|
filenames = tf.io.gfile.glob(search_files) |
|
return sorted(filenames) |
|
|
|
|
|
def _get_panoptic_annotation(coco_root: str, dataset_split: str, |
|
annotation_file_name: str) -> str: |
|
panoptic_folder = 'panoptic_%s2017' % dataset_split |
|
return os.path.join(coco_root, _FOLDERS_MAP[dataset_split]['label'], |
|
panoptic_folder, annotation_file_name) |
|
|
|
|
|
def _read_segments(coco_root: str, dataset_split: str): |
|
"""Reads segments information from json file. |
|
|
|
Args: |
|
coco_root: String, path to coco dataset root folder. |
|
dataset_split: String, dataset split. |
|
|
|
Returns: |
|
segments_dict: A dictionary that maps file prefix of annotation_file_name to |
|
a tuple of (panoptic annotation file name, segments). Please refer to |
|
_generate_panoptic_label() method on the detail structure of `segments`. |
|
|
|
Raises: |
|
ValueError: If found duplicated image id in annotations. |
|
""" |
|
json_filename = os.path.join( |
|
coco_root, _FOLDERS_MAP[dataset_split]['label'], |
|
'panoptic_%s2017.json' % dataset_split) |
|
with tf.io.gfile.GFile(json_filename) as f: |
|
panoptic_dataset = json.load(f) |
|
|
|
segments_dict = {} |
|
for annotation in panoptic_dataset['annotations']: |
|
image_id = annotation['image_id'] |
|
if image_id in segments_dict: |
|
raise ValueError('Image ID %s already exists' % image_id) |
|
annotation_file_name = annotation['file_name'] |
|
segments = annotation['segments_info'] |
|
|
|
segments_dict[os.path.splitext(annotation_file_name)[-2]] = ( |
|
annotation_file_name, segments) |
|
|
|
return segments_dict |
|
|
|
|
|
def _generate_panoptic_label(panoptic_annotation_file: str, segments: |
|
Any) -> np.ndarray: |
|
"""Creates panoptic label map from annotations. |
|
|
|
Args: |
|
panoptic_annotation_file: String, path to panoptic annotation. |
|
segments: A list of dictionaries containing information of every segment. |
|
Read from panoptic_${DATASET_SPLIT}2017.json. This method consumes |
|
the following fields in each dictionary: |
|
- id: panoptic id |
|
- category_id: semantic class id |
|
- area: pixel area of this segment |
|
- iscrowd: if this segment is crowd region |
|
|
|
Returns: |
|
A 2D numpy int32 array with the same height / width with panoptic |
|
annotation. Each pixel value represents its panoptic ID. Please refer to |
|
g3doc/setup/coco.md for more details about how panoptic ID is assigned. |
|
""" |
|
with tf.io.gfile.GFile(panoptic_annotation_file, 'rb') as f: |
|
panoptic_label = data_utils.read_image(f.read()) |
|
|
|
if panoptic_label.mode != 'RGB': |
|
raise ValueError('Expect RGB image for panoptic label, gets %s' % |
|
panoptic_label.mode) |
|
|
|
panoptic_label = np.array(panoptic_label, dtype=np.int32) |
|
|
|
|
|
panoptic_label = np.dot(panoptic_label, [1, 256, 256 * 256]) |
|
|
|
semantic_label = np.ones_like(panoptic_label) * _IGNORE_LABEL |
|
instance_label = np.zeros_like(panoptic_label) |
|
|
|
instance_count = collections.defaultdict(int) |
|
|
|
for segment in segments: |
|
selected_pixels = panoptic_label == segment['id'] |
|
pixel_area = np.sum(selected_pixels) |
|
if pixel_area != segment['area']: |
|
raise ValueError('Expect %d pixels for segment %s, gets %d.' % |
|
(segment['area'], segment, pixel_area)) |
|
|
|
category_id = segment['category_id'] |
|
|
|
|
|
category_id = _CLASS_MAPPING[category_id] |
|
|
|
semantic_label[selected_pixels] = category_id |
|
|
|
if category_id in _CLASS_HAS_INSTANCE_LIST: |
|
if segment['iscrowd']: |
|
|
|
if FLAGS.treat_crowd_as_ignore: |
|
semantic_label[selected_pixels] = _IGNORE_LABEL |
|
continue |
|
|
|
instance_count[category_id] += 1 |
|
if instance_count[category_id] >= _PANOPTIC_LABEL_DIVISOR: |
|
raise ValueError('Too many instances for category %d in this image.' % |
|
category_id) |
|
instance_label[selected_pixels] = instance_count[category_id] |
|
elif segment['iscrowd']: |
|
raise ValueError('Stuff class should not have `iscrowd` label.') |
|
|
|
panoptic_label = semantic_label * _PANOPTIC_LABEL_DIVISOR + instance_label |
|
return panoptic_label.astype(np.int32) |
|
|
|
|
|
def _create_panoptic_label(coco_root: str, dataset_split: str, image_path: str, |
|
segments_dict: Any |
|
) -> Tuple[str, str]: |
|
"""Creates labels for panoptic segmentation. |
|
|
|
Args: |
|
coco_root: String, path to coco dataset root folder. |
|
dataset_split: String, dataset split ('train', 'val', 'test'). |
|
image_path: String, path to the image file. |
|
segments_dict: |
|
Read from panoptic_${DATASET_SPLIT}2017.json. This method consumes |
|
the following fields in each dictionary: |
|
- id: panoptic id |
|
- category_id: semantic class id |
|
- area: pixel area of this segment |
|
- iscrowd: if this segment is crowd region |
|
|
|
Returns: |
|
A panoptic label where each pixel value represents its panoptic ID. |
|
Please refer to g3doc/setup/coco.md for more details about howpanoptic ID |
|
is assigned. |
|
A string indicating label format in TFRecord. |
|
""" |
|
|
|
image_path = os.path.normpath(image_path) |
|
path_list = image_path.split(os.sep) |
|
file_name = path_list[-1] |
|
|
|
annotation_file_name, segments = segments_dict[ |
|
os.path.splitext(file_name)[-2]] |
|
panoptic_annotation_file = _get_panoptic_annotation(coco_root, |
|
dataset_split, |
|
annotation_file_name) |
|
|
|
panoptic_label = _generate_panoptic_label(panoptic_annotation_file, segments) |
|
return panoptic_label.tostring(), _PANOPTIC_LABEL_FORMAT |
|
|
|
|
|
def _convert_dataset(coco_root: str, dataset_split: str, |
|
output_dir: str) -> None: |
|
"""Converts the specified dataset split to TFRecord format. |
|
|
|
Args: |
|
coco_root: String, path to coco dataset root folder. |
|
dataset_split: String, the dataset split (one of `train`, `val` and `test`). |
|
output_dir: String, directory to write output TFRecords to. |
|
""" |
|
image_files = _get_images(coco_root, dataset_split) |
|
|
|
num_images = len(image_files) |
|
|
|
if dataset_split != 'test': |
|
segments_dict = _read_segments(coco_root, dataset_split) |
|
|
|
num_per_shard = int(math.ceil(len(image_files) / _NUM_SHARDS)) |
|
|
|
for shard_id in range(_NUM_SHARDS): |
|
shard_filename = '%s-%05d-of-%05d.tfrecord' % ( |
|
dataset_split, shard_id, _NUM_SHARDS) |
|
output_filename = os.path.join(output_dir, shard_filename) |
|
with tf.io.TFRecordWriter(output_filename) as tfrecord_writer: |
|
start_idx = shard_id * num_per_shard |
|
end_idx = min((shard_id + 1) * num_per_shard, num_images) |
|
for i in range(start_idx, end_idx): |
|
|
|
with tf.io.gfile.GFile(image_files[i], 'rb') as f: |
|
image_data = f.read() |
|
|
|
if dataset_split == 'test': |
|
label_data, label_format = None, None |
|
else: |
|
label_data, label_format = _create_panoptic_label( |
|
coco_root, dataset_split, image_files[i], segments_dict) |
|
|
|
|
|
image_path = os.path.normpath(image_files[i]) |
|
path_list = image_path.split(os.sep) |
|
file_name = path_list[-1] |
|
file_prefix = file_name.replace(_DATA_FORMAT_MAP['image'], '') |
|
example = data_utils.create_tfexample(image_data, |
|
'jpeg', |
|
file_prefix, label_data, |
|
label_format) |
|
|
|
tfrecord_writer.write(example.SerializeToString()) |
|
|
|
|
|
def main(unused_argv: Sequence[str]) -> None: |
|
tf.io.gfile.makedirs(FLAGS.output_dir) |
|
|
|
for dataset_split in ('train', 'val', 'test'): |
|
logging.info('Starts processing dataset split %s.', dataset_split) |
|
_convert_dataset(FLAGS.coco_root, dataset_split, FLAGS.output_dir) |
|
|
|
|
|
if __name__ == '__main__': |
|
flags.mark_flags_as_required(['coco_root', 'output_dir']) |
|
app.run(main) |
|
|