From 3339f66769c2bb0e6bd757bdaf6375e8b7eac48b Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Fri, 29 Jul 2022 21:51:03 +0800 Subject: [PATCH 01/21] bug fixed --- pyproject.toml | 6 +++--- src/labelme2yolo/__about__.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 41a373e..bf2ca72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,9 +30,9 @@ dependencies = [ dynamic = ["version"] [project.urls] -Documentation = "https://github.com/unknown/labelme2yolo#readme" -Issues = "https://github.com/unknown/labelme2yolo/issues" -Source = "https://github.com/unknown/labelme2yolo" +Documentation = "https://github.com/greatv/labelme2yolo#readme" +Issues = "https://github.com/greatv/labelme2yolo/issues" +Source = "https://github.com/greatv/labelme2yolo" [tool.hatch.version] path = "src/labelme2yolo/__about__.py" diff --git a/src/labelme2yolo/__about__.py b/src/labelme2yolo/__about__.py index 4f7bd11..d76c17a 100644 --- a/src/labelme2yolo/__about__.py +++ b/src/labelme2yolo/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2022-present Wang Xin # # SPDX-License-Identifier: MIT -__version__ = '0.0.1' +__version__ = '0.0.2' From 9301f7232ca2562c5f66db76d9180ca200e89f67 Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Sat, 30 Jul 2022 16:21:44 +0800 Subject: [PATCH 02/21] Update README.md --- README.md | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 0792faf..0748932 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,12 @@ Help converting LabelMe Annotation Tool JSON format to YOLO text file format. If you've already marked your segmentation dataset by LabelMe, it's easy to use this tool to help converting to YOLO format dataset. +## Installation + +```console +pip install labelme2yolo +``` + ## Parameters Explain **--json_dir** LabelMe JSON files folder path. @@ -22,58 +28,53 @@ If you've already marked your segmentation dataset by LabelMe, it's easy to use ### 1. Convert JSON files, split training, validation and test dataset by --val_size and --test_size Put all LabelMe JSON files under **labelme_json_dir**, and run this python command. ```bash -python labelme2yolo.py --json_dir /home/username/labelme_json_dir/ --val_size 0.15 --test_size 0.15 +labelme2yolo --json_dir /path/to/labelme_json_dir/ --val_size 0.15 --test_size 0.15 ``` Script would generate YOLO format dataset labels and images under different folders, for example, ```bash -/home/username/labelme_json_dir/YOLODataset/labels/train/ -/home/username/labelme_json_dir/YOLODataset/labels/test/ -/home/username/labelme_json_dir/YOLODataset/labels/val/ -/home/username/labelme_json_dir/YOLODataset/images/train/ -/home/username/labelme_json_dir/YOLODataset/images/test/ -/home/username/labelme_json_dir/YOLODataset/images/val/ +/path/to/labelme_json_dir/YOLODataset/labels/train/ +/path/to/labelme_json_dir/YOLODataset/labels/test/ +/path/to/labelme_json_dir/YOLODataset/labels/val/ +/path/to/labelme_json_dir/YOLODataset/images/train/ +/path/to/labelme_json_dir/YOLODataset/images/test/ +/path/to/labelme_json_dir/YOLODataset/images/val/ -/home/username/labelme_json_dir/YOLODataset/dataset.yaml +/path/to/labelme_json_dir/YOLODataset/dataset.yaml ``` ### 2. Convert JSON files, split training and validation dataset by folder If you already split train dataset and validation dataset for LabelMe by yourself, please put these folder under labelme_json_dir, for example, ```bash -/home/username/labelme_json_dir/train/ -/home/username/labelme_json_dir/val/ +/path/to/labelme_json_dir/train/ +/path/to/labelme_json_dir/val/ ``` Put all LabelMe JSON files under **labelme_json_dir**. Script would read train and validation dataset by folder. Run this python command. ```bash -python labelme2yolo.py --json_dir /home/username/labelme_json_dir/ +labelme2yolo --json_dir /path/to/labelme_json_dir/ ``` Script would generate YOLO format dataset labels and images under different folders, for example, ```bash -/home/username/labelme_json_dir/YOLODataset/labels/train/ -/home/username/labelme_json_dir/YOLODataset/labels/val/ -/home/username/labelme_json_dir/YOLODataset/images/train/ -/home/username/labelme_json_dir/YOLODataset/images/val/ +/path/to/labelme_json_dir/YOLODataset/labels/train/ +/path/to/labelme_json_dir/YOLODataset/labels/val/ +/path/to/labelme_json_dir/YOLODataset/images/train/ +/path/to/labelme_json_dir/YOLODataset/images/val/ -/home/username/labelme_json_dir/YOLODataset/dataset.yaml +/path/to/labelme_json_dir/YOLODataset/dataset.yaml ``` ### 3. Convert single JSON file Put LabelMe JSON file under **labelme_json_dir**. , and run this python command. ```bash -python labelme2yolo.py --json_dir /home/username/labelme_json_dir/ --json_name 2.json +labelme2yolo --json_dir /path/to/labelme_json_dir/ --json_name 2.json ``` Script would generate YOLO format text label and image under **labelme_json_dir**, for example, ```bash -/home/username/labelme_json_dir/2.text -/home/username/labelme_json_dir/2.png +/path/to/labelme_json_dir/2.text +/path/to/labelme_json_dir/2.png ``` -## Installation - -```console -pip install labelme2yolo -``` ## License From cb0a47943e66f861cfdecda8a580a28402fa32e5 Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Mon, 1 Aug 2022 10:23:19 +0800 Subject: [PATCH 03/21] Update README.md Add Codacy Badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0748932..498f46b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![PyPI - Version](https://img.shields.io/pypi/v/labelme2yolo.svg)](https://pypi.org/project/labelme2yolo) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/labelme2yolo.svg)](https://pypi.org/project/labelme2yolo) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/12122fe86f8643c4aa5667c20d528f61)](https://www.codacy.com/gh/GreatV/labelme2yolo/dashboard?utm_source=github.com&utm_medium=referral&utm_content=GreatV/labelme2yolo&utm_campaign=Badge_Grade) Help converting LabelMe Annotation Tool JSON format to YOLO text file format. If you've already marked your segmentation dataset by LabelMe, it's easy to use this tool to help converting to YOLO format dataset. From 2e5b3fec078a68b7eabb8c19c524e28b5bbb717c Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Mon, 1 Aug 2022 15:29:19 +0800 Subject: [PATCH 04/21] update .gitignore for pycharm --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 68bc17f..2dc53ca 100644 --- a/.gitignore +++ b/.gitignore @@ -157,4 +157,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ From 845afa03283650381c707fbe515874bd869ac5b0 Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Mon, 1 Aug 2022 15:43:19 +0800 Subject: [PATCH 05/21] reformat code --- src/labelme2yolo/l2y.py | 260 +++++++++++++++++++++------------------- 1 file changed, 134 insertions(+), 126 deletions(-) diff --git a/src/labelme2yolo/l2y.py b/src/labelme2yolo/l2y.py index 5d823a1..f3970e9 100644 --- a/src/labelme2yolo/l2y.py +++ b/src/labelme2yolo/l2y.py @@ -1,12 +1,10 @@ -''' +""" Created on Aug 18, 2021 @author: xiaosonh @author: GreatV(Wang Xin) -''' +""" import os -import sys -import argparse import shutil import math import base64 @@ -123,214 +121,224 @@ def apply_exif_orientation(image): else: return image + +def _get_label_id_map(json_dir): + label_set = set() + + for file_name in os.listdir(json_dir): + if file_name.endswith('json'): + json_path = os.path.join(json_dir, file_name) + data = json.load(open(json_path)) + for shape in data['shapes']: + label_set.add(shape['label']) + + return OrderedDict([(label, label_id) + for label_id, label in enumerate(label_set)]) + + +def _save_yolo_label(json_name, label_dir_path, target_dir, yolo_obj_list): + txt_path = os.path.join(label_dir_path, + target_dir, + json_name.replace('.json', '.txt')) + + with open(txt_path, 'w+') as f: + for yolo_obj_idx, yolo_obj in enumerate(yolo_obj_list): + yolo_obj_line = '%s %s %s %s %s\n' % yolo_obj \ + if yolo_obj_idx + 1 != len(yolo_obj_list) else \ + '%s %s %s %s %s' % yolo_obj + f.write(yolo_obj_line) + + class Labelme2YOLO(object): - + def __init__(self, json_dir): self._json_dir = json_dir - - self._label_id_map = self._get_label_id_map(self._json_dir) - + + self._label_id_map = _get_label_id_map(self._json_dir) + def _make_train_val_dir(self): - self._label_dir_path = os.path.join(self._json_dir, + self._label_dir_path = os.path.join(self._json_dir, 'YOLODataset/labels/') - self._image_dir_path = os.path.join(self._json_dir, + self._image_dir_path = os.path.join(self._json_dir, 'YOLODataset/images/') - - for yolo_path in (os.path.join(self._label_dir_path + 'train/'), + + for yolo_path in (os.path.join(self._label_dir_path + 'train/'), os.path.join(self._label_dir_path + 'val/'), os.path.join(self._label_dir_path + 'test/'), - os.path.join(self._image_dir_path + 'train/'), + os.path.join(self._image_dir_path + 'train/'), os.path.join(self._image_dir_path + 'val/'), os.path.join(self._image_dir_path + 'test/')): if os.path.exists(yolo_path): shutil.rmtree(yolo_path) - - os.makedirs(yolo_path) - - def _get_label_id_map(self, json_dir): - label_set = set() - - for file_name in os.listdir(json_dir): - if file_name.endswith('json'): - json_path = os.path.join(json_dir, file_name) - data = json.load(open(json_path)) - for shape in data['shapes']: - label_set.add(shape['label']) - - return OrderedDict([(label, label_id) \ - for label_id, label in enumerate(label_set)]) - + + os.makedirs(yolo_path) + def _train_test_split(self, folders, json_names, val_size, test_size): if len(folders) > 0 and 'train' in folders and 'val' in folders and 'test' in folders: train_folder = os.path.join(self._json_dir, 'train/') - train_json_names = [train_sample_name + '.json' \ - for train_sample_name in os.listdir(train_folder) \ + train_json_names = [train_sample_name + '.json' + for train_sample_name in os.listdir(train_folder) if os.path.isdir(os.path.join(train_folder, train_sample_name))] - + val_folder = os.path.join(self._json_dir, 'val/') - val_json_names = [val_sample_name + '.json' \ - for val_sample_name in os.listdir(val_folder) \ + val_json_names = [val_sample_name + '.json' + for val_sample_name in os.listdir(val_folder) if os.path.isdir(os.path.join(val_folder, val_sample_name))] - + test_folder = os.path.join(self._json_dir, 'test/') - test_json_names = [test_sample_name + '.json' \ - for test_sample_name in os.listdir(test_folder) \ - if os.path.isdir(os.path.join(test_folder, test_sample_name))] - + test_json_names = [test_sample_name + '.json' + for test_sample_name in os.listdir(test_folder) + if os.path.isdir(os.path.join(test_folder, test_sample_name))] + return train_json_names, val_json_names, test_json_names - - train_idxs, val_idxs = train_test_split(range(len(json_names)), - test_size=val_size) - tmp_train_len = len(train_idxs) - test_idxs = [] + + train_indexes, val_indexes = train_test_split(range(len(json_names)), + test_size=val_size) + tmp_train_len = len(train_indexes) + test_indexes = [] if test_size > 1e-8: - train_idxs, test_idxs = train_test_split(range(tmp_train_len), test_size=test_size / (1 - val_size)) - train_json_names = [json_names[train_idx] for train_idx in train_idxs] - val_json_names = [json_names[val_idx] for val_idx in val_idxs] - test_json_names = [json_names[test_idx] for test_idx in test_idxs] - + train_indexes, test_indexes = train_test_split( + range(tmp_train_len), test_size=test_size / (1 - val_size)) + train_json_names = [json_names[train_idx] for train_idx in train_indexes] + val_json_names = [json_names[val_idx] for val_idx in val_indexes] + test_json_names = [json_names[test_idx] for test_idx in test_indexes] + return train_json_names, val_json_names, test_json_names - + def convert(self, val_size, test_size): - json_names = [file_name for file_name in os.listdir(self._json_dir) \ - if os.path.isfile(os.path.join(self._json_dir, file_name)) and \ + json_names = [file_name for file_name in os.listdir(self._json_dir) + if os.path.isfile(os.path.join(self._json_dir, file_name)) and file_name.endswith('.json')] - folders = [file_name for file_name in os.listdir(self._json_dir) \ - if os.path.isdir(os.path.join(self._json_dir, file_name))] - train_json_names, val_json_names, test_json_names = self._train_test_split(folders, json_names, val_size, test_size) - + folders = [file_name for file_name in os.listdir(self._json_dir) + if os.path.isdir(os.path.join(self._json_dir, file_name))] + train_json_names, val_json_names, test_json_names = self._train_test_split( + folders, json_names, val_size, test_size) + self._make_train_val_dir() - + # convert labelme object to yolo format object, and save them to files # also get image from labelme json file and save them under images folder - for target_dir, json_names in zip(('train/', 'val/', 'test/'), + for target_dir, json_names in zip(('train/', 'val/', 'test/'), (train_json_names, val_json_names, test_json_names)): pool = Pool(os.cpu_count() - 1) for json_name in json_names: - pool.apply_async(self.covert_json_to_text, args=(target_dir, json_name)) + pool.apply_async(self.covert_json_to_text, + args=(target_dir, json_name)) pool.close() pool.join() - + print('Generating dataset.yaml file ...') self._save_dataset_yaml() def covert_json_to_text(self, target_dir, json_name): json_path = os.path.join(self._json_dir, json_name) json_data = json.load(open(json_path)) - - print('Converting %s for %s ...' % (json_name, target_dir.replace('/', ''))) - - img_path = self._save_yolo_image(json_data, - json_name, - self._image_dir_path, - target_dir) - + + print('Converting %s for %s ...' % + (json_name, target_dir.replace('/', ''))) + + img_path = self._save_yolo_image(json_data, + json_name, + self._image_dir_path, + target_dir) + yolo_obj_list = self._get_yolo_object_list(json_data, img_path) - self._save_yolo_label(json_name, - self._label_dir_path, - target_dir, - yolo_obj_list) - + _save_yolo_label(json_name, + self._label_dir_path, + target_dir, + yolo_obj_list) + def convert_one(self, json_name): json_path = os.path.join(self._json_dir, json_name) json_data = json.load(open(json_path)) - + print('Converting %s ...' % json_name) - - img_path = self._save_yolo_image(json_data, json_name, + + img_path = self._save_yolo_image(json_data, json_name, self._json_dir, '') - + yolo_obj_list = self._get_yolo_object_list(json_data, img_path) - self._save_yolo_label(json_name, self._json_dir, - '', yolo_obj_list) - + _save_yolo_label(json_name, self._json_dir, + '', yolo_obj_list) + def _get_yolo_object_list(self, json_data, img_path): yolo_obj_list = [] - + img_h, img_w, _ = cv2.imread(img_path).shape for shape in json_data['shapes']: # labelme circle shape is different from others # it only has 2 points, 1st is circle center, 2nd is drag end point if shape['shape_type'] == 'circle': - yolo_obj = self._get_circle_shape_yolo_object(shape, img_h, img_w) + yolo_obj = self._get_circle_shape_yolo_object( + shape, img_h, img_w) else: - yolo_obj = self._get_other_shape_yolo_object(shape, img_h, img_w) - + yolo_obj = self._get_other_shape_yolo_object( + shape, img_h, img_w) + yolo_obj_list.append(yolo_obj) - + return yolo_obj_list - + def _get_circle_shape_yolo_object(self, shape, img_h, img_w): obj_center_x, obj_center_y = shape['points'][0] - - radius = math.sqrt((obj_center_x - shape['points'][1][0]) ** 2 + + + radius = math.sqrt((obj_center_x - shape['points'][1][0]) ** 2 + (obj_center_y - shape['points'][1][1]) ** 2) obj_w = 2 * radius obj_h = 2 * radius - - yolo_center_x= round(float(obj_center_x / img_w), 6) + + yolo_center_x = round(float(obj_center_x / img_w), 6) yolo_center_y = round(float(obj_center_y / img_h), 6) yolo_w = round(float(obj_w / img_w), 6) yolo_h = round(float(obj_h / img_h), 6) - + label_id = self._label_id_map[shape['label']] - + return label_id, yolo_center_x, yolo_center_y, yolo_w, yolo_h - + def _get_other_shape_yolo_object(self, shape, img_h, img_w): def __get_object_desc(obj_port_list): - __get_dist = lambda int_list: max(int_list) - min(int_list) - - x_lists = [port[0] for port in obj_port_list] + def __get_dist(int_list): return max(int_list) - min(int_list) + + x_lists = [port[0] for port in obj_port_list] y_lists = [port[1] for port in obj_port_list] - + return min(x_lists), __get_dist(x_lists), min(y_lists), __get_dist(y_lists) - + obj_x_min, obj_w, obj_y_min, obj_h = __get_object_desc(shape['points']) - - yolo_center_x= round(float((obj_x_min + obj_w / 2.0) / img_w), 6) + + yolo_center_x = round(float((obj_x_min + obj_w / 2.0) / img_w), 6) yolo_center_y = round(float((obj_y_min + obj_h / 2.0) / img_h), 6) yolo_w = round(float(obj_w / img_w), 6) yolo_h = round(float(obj_h / img_h), 6) - - label_id = self._label_id_map[shape['label']] - - return label_id, yolo_center_x, yolo_center_y, yolo_w, yolo_h - - def _save_yolo_label(self, json_name, label_dir_path, target_dir, yolo_obj_list): - txt_path = os.path.join(label_dir_path, - target_dir, - json_name.replace('.json', '.txt')) - with open(txt_path, 'w+') as f: - for yolo_obj_idx, yolo_obj in enumerate(yolo_obj_list): - yolo_obj_line = '%s %s %s %s %s\n' % yolo_obj \ - if yolo_obj_idx + 1 != len(yolo_obj_list) else \ - '%s %s %s %s %s' % yolo_obj - f.write(yolo_obj_line) - + label_id = self._label_id_map[shape['label']] + + return label_id, yolo_center_x, yolo_center_y, yolo_w, yolo_h + def _save_yolo_image(self, json_data, json_name, image_dir_path, target_dir): img_name = json_name.replace('.json', '.png') - img_path = os.path.join(image_dir_path, target_dir,img_name) - + img_path = os.path.join(image_dir_path, target_dir, img_name) + if not os.path.exists(img_path): img = img_b64_to_arr(json_data['imageData']) PIL.Image.fromarray(img).save(img_path) - + return img_path - + def _save_dataset_yaml(self): - yaml_path = os.path.join(self._json_dir, 'YOLODataset/', 'dataset.yaml') - + yaml_path = os.path.join( + self._json_dir, 'YOLODataset/', 'dataset.yaml') + with open(yaml_path, 'w+') as yaml_file: - yaml_file.write('train: %s\n' % \ + yaml_file.write('train: %s\n' % os.path.join(self._image_dir_path, 'train/')) - yaml_file.write('val: %s\n\n' % \ + yaml_file.write('val: %s\n\n' % os.path.join(self._image_dir_path, 'val/')) - yaml_file.write('test: %s\n\n' % \ + yaml_file.write('test: %s\n\n' % os.path.join(self._image_dir_path, 'test/')) yaml_file.write('nc: %i\n\n' % len(self._label_id_map)) - + names_str = '' for label, _ in self._label_id_map.items(): names_str += "'%s', " % label From 99d963316d4f4cde6fa405a78f791bcbea2b6de0 Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Mon, 1 Aug 2022 15:51:49 +0800 Subject: [PATCH 06/21] reformat code --- src/labelme2yolo/__main__.py | 2 +- src/labelme2yolo/cli/__init__.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/labelme2yolo/__main__.py b/src/labelme2yolo/__main__.py index beda96e..856c948 100644 --- a/src/labelme2yolo/__main__.py +++ b/src/labelme2yolo/__main__.py @@ -6,4 +6,4 @@ import sys if __name__ == '__main__': from .cli import run - sys.exit(run()) \ No newline at end of file + sys.exit(run()) diff --git a/src/labelme2yolo/cli/__init__.py b/src/labelme2yolo/cli/__init__.py index 18e2d70..3c821bb 100644 --- a/src/labelme2yolo/cli/__init__.py +++ b/src/labelme2yolo/cli/__init__.py @@ -9,23 +9,23 @@ from labelme2yolo.l2y import Labelme2YOLO def run(): parser = argparse.ArgumentParser("labelme2yolo") - parser.add_argument('--json_dir',type=str, + parser.add_argument('--json_dir', type=str, help='Please input the path of the labelme json files.') - parser.add_argument('--val_size',type=float, nargs='?', default=None, + parser.add_argument('--val_size', type=float, nargs='?', default=None, help='Please input the validation dataset size, for example 0.1 ') - parser.add_argument('--test_size',type=float, nargs='?', default=0.0, + parser.add_argument('--test_size', type=float, nargs='?', default=0.0, help='Please input the validation dataset size, for example 0.1 ') - parser.add_argument('--json_name',type=str, nargs='?', default=None, + parser.add_argument('--json_name', type=str, nargs='?', default=None, help='If you put json name, it would convert only one json file to YOLO.') args = parser.parse_args() - + if not args.json_dir: parser.print_help(sys.stderr) sys.exit(1) - + convertor = Labelme2YOLO(args.json_dir) - + if args.json_name is None: convertor.convert(val_size=args.val_size, test_size=args.test_size) else: - convertor.convert_one(args.json_name) \ No newline at end of file + convertor.convert_one(args.json_name) From a0fe8ac2318e579a89d833d3460adca07a88e134 Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Mon, 1 Aug 2022 16:17:00 +0800 Subject: [PATCH 07/21] update code style --- README.md | 1 - src/labelme2yolo/l2y.py | 57 +++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 498f46b..84f0c35 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,6 @@ Script would generate YOLO format text label and image under **labelme_json_dir* /path/to/labelme_json_dir/2.png ``` - ## License `labelme2yolo` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. diff --git a/src/labelme2yolo/l2y.py b/src/labelme2yolo/l2y.py index f3970e9..ad1cefe 100644 --- a/src/labelme2yolo/l2y.py +++ b/src/labelme2yolo/l2y.py @@ -149,6 +149,17 @@ def _save_yolo_label(json_name, label_dir_path, target_dir, yolo_obj_list): f.write(yolo_obj_line) +def _save_yolo_image(json_data, json_name, image_dir_path, target_dir): + img_name = json_name.replace('.json', '.png') + img_path = os.path.join(image_dir_path, target_dir, img_name) + + if not os.path.exists(img_path): + img = img_b64_to_arr(json_data['imageData']) + PIL.Image.fromarray(img).save(img_path) + + return img_path + + class Labelme2YOLO(object): def __init__(self, json_dir): @@ -175,20 +186,9 @@ class Labelme2YOLO(object): def _train_test_split(self, folders, json_names, val_size, test_size): if len(folders) > 0 and 'train' in folders and 'val' in folders and 'test' in folders: - train_folder = os.path.join(self._json_dir, 'train/') - train_json_names = [train_sample_name + '.json' - for train_sample_name in os.listdir(train_folder) - if os.path.isdir(os.path.join(train_folder, train_sample_name))] - - val_folder = os.path.join(self._json_dir, 'val/') - val_json_names = [val_sample_name + '.json' - for val_sample_name in os.listdir(val_folder) - if os.path.isdir(os.path.join(val_folder, val_sample_name))] - - test_folder = os.path.join(self._json_dir, 'test/') - test_json_names = [test_sample_name + '.json' - for test_sample_name in os.listdir(test_folder) - if os.path.isdir(os.path.join(test_folder, test_sample_name))] + train_json_names = self.get_json_names('train/') + val_json_names = self.get_json_names('val/') + test_json_names = self.get_json_names('test/') return train_json_names, val_json_names, test_json_names @@ -205,6 +205,13 @@ class Labelme2YOLO(object): return train_json_names, val_json_names, test_json_names + def get_json_names(self, data_type: str): + data_folder = os.path.join(self._json_dir, data_type) + data_json_names = [data_sample_name + '.json' + for data_sample_name in os.listdir(data_folder) + if os.path.isdir(os.path.join(data_folder, data_sample_name))] + return data_json_names + def convert(self, val_size, test_size): json_names = [file_name for file_name in os.listdir(self._json_dir) if os.path.isfile(os.path.join(self._json_dir, file_name)) and @@ -237,10 +244,10 @@ class Labelme2YOLO(object): print('Converting %s for %s ...' % (json_name, target_dir.replace('/', ''))) - img_path = self._save_yolo_image(json_data, - json_name, - self._image_dir_path, - target_dir) + img_path = _save_yolo_image(json_data, + json_name, + self._image_dir_path, + target_dir) yolo_obj_list = self._get_yolo_object_list(json_data, img_path) _save_yolo_label(json_name, @@ -254,8 +261,8 @@ class Labelme2YOLO(object): print('Converting %s ...' % json_name) - img_path = self._save_yolo_image(json_data, json_name, - self._json_dir, '') + img_path = _save_yolo_image(json_data, json_name, + self._json_dir, '') yolo_obj_list = self._get_yolo_object_list(json_data, img_path) _save_yolo_label(json_name, self._json_dir, @@ -316,16 +323,6 @@ class Labelme2YOLO(object): return label_id, yolo_center_x, yolo_center_y, yolo_w, yolo_h - def _save_yolo_image(self, json_data, json_name, image_dir_path, target_dir): - img_name = json_name.replace('.json', '.png') - img_path = os.path.join(image_dir_path, target_dir, img_name) - - if not os.path.exists(img_path): - img = img_b64_to_arr(json_data['imageData']) - PIL.Image.fromarray(img).save(img_path) - - return img_path - def _save_dataset_yaml(self): yaml_path = os.path.join( self._json_dir, 'YOLODataset/', 'dataset.yaml') From a383a7797543fabc2dd495434731fe4147fd14d2 Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Tue, 2 Aug 2022 18:00:25 +0800 Subject: [PATCH 08/21] update requirements --- requirements.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 022b726..71427c5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ opencv-python>=4.1.2 -Pillow -scikit-learn +Pillow~=9.2.0 +scikit-learn~=1.1.1 + +numpy~=1.23.1 \ No newline at end of file From 53706ca394c5cd782010bc8850cff080f3194dbb Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Tue, 2 Aug 2022 18:01:02 +0800 Subject: [PATCH 09/21] reformat code --- src/labelme2yolo/l2y.py | 68 +++++++---------------------------------- 1 file changed, 11 insertions(+), 57 deletions(-) diff --git a/src/labelme2yolo/l2y.py b/src/labelme2yolo/l2y.py index ad1cefe..0051b4a 100644 --- a/src/labelme2yolo/l2y.py +++ b/src/labelme2yolo/l2y.py @@ -66,60 +66,14 @@ def img_arr_to_b64(img_arr): # copy form https://github.com/wkentaro/labelme/blob/main/labelme/utils/image.py def img_data_to_png_data(img_data): - with io.BytesIO() as f: - f.write(img_data) - img = PIL.Image.open(f) + with io.BytesIO() as f_out: + f_out.write(img_data) + img = PIL.Image.open(f_out) - with io.BytesIO() as f: - img.save(f, "PNG") - f.seek(0) - return f.read() - - -# copy form https://github.com/wkentaro/labelme/blob/main/labelme/utils/image.py -def apply_exif_orientation(image): - try: - exif = image._getexif() - except AttributeError: - exif = None - - if exif is None: - return image - - exif = { - PIL.ExifTags.TAGS[k]: v - for k, v in exif.items() - if k in PIL.ExifTags.TAGS - } - - orientation = exif.get("Orientation", None) - - if orientation == 1: - # do nothing - return image - elif orientation == 2: - # left-to-right mirror - return PIL.ImageOps.mirror(image) - elif orientation == 3: - # rotate 180 - return image.transpose(PIL.Image.ROTATE_180) - elif orientation == 4: - # top-to-bottom mirror - return PIL.ImageOps.flip(image) - elif orientation == 5: - # top-to-left mirror - return PIL.ImageOps.mirror(image.transpose(PIL.Image.ROTATE_270)) - elif orientation == 6: - # rotate 270 - return image.transpose(PIL.Image.ROTATE_270) - elif orientation == 7: - # top-to-right mirror - return PIL.ImageOps.mirror(image.transpose(PIL.Image.ROTATE_90)) - elif orientation == 8: - # rotate 90 - return image.transpose(PIL.Image.ROTATE_90) - else: - return image + with io.BytesIO() as f_in: + img.save(f_in, "PNG") + f_in.seek(0) + return f_in.read() def _get_label_id_map(json_dir): @@ -325,15 +279,15 @@ class Labelme2YOLO(object): def _save_dataset_yaml(self): yaml_path = os.path.join( - self._json_dir, 'YOLODataset/', 'dataset.yaml') + self._json_dir, 'YOLODataset', 'dataset.yaml') with open(yaml_path, 'w+') as yaml_file: yaml_file.write('train: %s\n' % - os.path.join(self._image_dir_path, 'train/')) + os.path.join(self._image_dir_path, 'train')) yaml_file.write('val: %s\n\n' % - os.path.join(self._image_dir_path, 'val/')) + os.path.join(self._image_dir_path, 'val')) yaml_file.write('test: %s\n\n' % - os.path.join(self._image_dir_path, 'test/')) + os.path.join(self._image_dir_path, 'test')) yaml_file.write('nc: %i\n\n' % len(self._label_id_map)) names_str = '' From a3a824b5ce89a029ffd3e5b39b9ae7787295ee44 Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Fri, 5 Aug 2022 16:26:56 +0800 Subject: [PATCH 10/21] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 84f0c35..5e283bb 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ # Labelme2YOLO [![PyPI - Version](https://img.shields.io/pypi/v/labelme2yolo.svg)](https://pypi.org/project/labelme2yolo) +![PyPI - Downloads](https://img.shields.io/pypi/dm/labelme2yolo?style=flat) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/labelme2yolo.svg)](https://pypi.org/project/labelme2yolo) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/12122fe86f8643c4aa5667c20d528f61)](https://www.codacy.com/gh/GreatV/labelme2yolo/dashboard?utm_source=github.com&utm_medium=referral&utm_content=GreatV/labelme2yolo&utm_campaign=Badge_Grade) From 8abb8e4bd425c576e8449b202450231001ba8601 Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Wed, 10 Aug 2022 16:51:21 +0800 Subject: [PATCH 11/21] bug fixed --- src/labelme2yolo/cli/__init__.py | 10 ++++----- src/labelme2yolo/l2y.py | 37 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/labelme2yolo/cli/__init__.py b/src/labelme2yolo/cli/__init__.py index 3c821bb..557c83c 100644 --- a/src/labelme2yolo/cli/__init__.py +++ b/src/labelme2yolo/cli/__init__.py @@ -2,8 +2,6 @@ # # SPDX-License-Identifier: MIT import argparse -import sys -# from labelme2yolo.__about__ import version from labelme2yolo.l2y import Labelme2YOLO @@ -13,15 +11,15 @@ def run(): help='Please input the path of the labelme json files.') parser.add_argument('--val_size', type=float, nargs='?', default=None, help='Please input the validation dataset size, for example 0.1 ') - parser.add_argument('--test_size', type=float, nargs='?', default=0.0, + parser.add_argument('--test_size', type=float, nargs='?', default=None, help='Please input the validation dataset size, for example 0.1 ') parser.add_argument('--json_name', type=str, nargs='?', default=None, help='If you put json name, it would convert only one json file to YOLO.') args = parser.parse_args() if not args.json_dir: - parser.print_help(sys.stderr) - sys.exit(1) + parser.print_help() + return 0 convertor = Labelme2YOLO(args.json_dir) @@ -29,3 +27,5 @@ def run(): convertor.convert(val_size=args.val_size, test_size=args.test_size) else: convertor.convert_one(args.json_name) + + return 0 diff --git a/src/labelme2yolo/l2y.py b/src/labelme2yolo/l2y.py index 0051b4a..cc53468 100644 --- a/src/labelme2yolo/l2y.py +++ b/src/labelme2yolo/l2y.py @@ -76,7 +76,7 @@ def img_data_to_png_data(img_data): return f_in.read() -def _get_label_id_map(json_dir): +def get_label_id_map(json_dir): label_set = set() for file_name in os.listdir(json_dir): @@ -90,7 +90,7 @@ def _get_label_id_map(json_dir): for label_id, label in enumerate(label_set)]) -def _save_yolo_label(json_name, label_dir_path, target_dir, yolo_obj_list): +def save_yolo_label(json_name, label_dir_path, target_dir, yolo_obj_list): txt_path = os.path.join(label_dir_path, target_dir, json_name.replace('.json', '.txt')) @@ -103,7 +103,7 @@ def _save_yolo_label(json_name, label_dir_path, target_dir, yolo_obj_list): f.write(yolo_obj_line) -def _save_yolo_image(json_data, json_name, image_dir_path, target_dir): +def save_yolo_image(json_data, json_name, image_dir_path, target_dir): img_name = json_name.replace('.json', '.png') img_path = os.path.join(image_dir_path, target_dir, img_name) @@ -119,7 +119,7 @@ class Labelme2YOLO(object): def __init__(self, json_dir): self._json_dir = json_dir - self._label_id_map = _get_label_id_map(self._json_dir) + self._label_id_map = get_label_id_map(self._json_dir) def _make_train_val_dir(self): self._label_dir_path = os.path.join(self._json_dir, @@ -150,10 +150,11 @@ class Labelme2YOLO(object): test_size=val_size) tmp_train_len = len(train_indexes) test_indexes = [] - if test_size > 1e-8: + if test_size: train_indexes, test_indexes = train_test_split( range(tmp_train_len), test_size=test_size / (1 - val_size)) - train_json_names = [json_names[train_idx] for train_idx in train_indexes] + train_json_names = [json_names[train_idx] + for train_idx in train_indexes] val_json_names = [json_names[val_idx] for val_idx in val_indexes] test_json_names = [json_names[test_idx] for test_idx in test_indexes] @@ -198,16 +199,16 @@ class Labelme2YOLO(object): print('Converting %s for %s ...' % (json_name, target_dir.replace('/', ''))) - img_path = _save_yolo_image(json_data, - json_name, - self._image_dir_path, - target_dir) + img_path = save_yolo_image(json_data, + json_name, + self._image_dir_path, + target_dir) yolo_obj_list = self._get_yolo_object_list(json_data, img_path) - _save_yolo_label(json_name, - self._label_dir_path, - target_dir, - yolo_obj_list) + save_yolo_label(json_name, + self._label_dir_path, + target_dir, + yolo_obj_list) def convert_one(self, json_name): json_path = os.path.join(self._json_dir, json_name) @@ -215,12 +216,12 @@ class Labelme2YOLO(object): print('Converting %s ...' % json_name) - img_path = _save_yolo_image(json_data, json_name, - self._json_dir, '') + img_path = save_yolo_image(json_data, json_name, + self._json_dir, '') yolo_obj_list = self._get_yolo_object_list(json_data, img_path) - _save_yolo_label(json_name, self._json_dir, - '', yolo_obj_list) + save_yolo_label(json_name, self._json_dir, + '', yolo_obj_list) def _get_yolo_object_list(self, json_data, img_path): yolo_obj_list = [] From 8a337f91a97608db0d4df1871162864343b39c2b Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Wed, 10 Aug 2022 17:24:06 +0800 Subject: [PATCH 12/21] trailing whitespace --- src/labelme2yolo/cli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/labelme2yolo/cli/__init__.py b/src/labelme2yolo/cli/__init__.py index 557c83c..24b40d4 100644 --- a/src/labelme2yolo/cli/__init__.py +++ b/src/labelme2yolo/cli/__init__.py @@ -27,5 +27,5 @@ def run(): convertor.convert(val_size=args.val_size, test_size=args.test_size) else: convertor.convert_one(args.json_name) - + return 0 From cf23412bfd4e1b2c52512a5a1ef5e14669986ad2 Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Wed, 10 Aug 2022 22:30:20 +0800 Subject: [PATCH 13/21] format code using back --- src/labelme2yolo/__about__.py | 2 +- src/labelme2yolo/__main__.py | 2 +- src/labelme2yolo/cli/__init__.py | 33 ++++-- src/labelme2yolo/l2y.py | 194 ++++++++++++++++--------------- 4 files changed, 127 insertions(+), 104 deletions(-) diff --git a/src/labelme2yolo/__about__.py b/src/labelme2yolo/__about__.py index d76c17a..118c1e3 100644 --- a/src/labelme2yolo/__about__.py +++ b/src/labelme2yolo/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2022-present Wang Xin # # SPDX-License-Identifier: MIT -__version__ = '0.0.2' +__version__ = "0.0.2" diff --git a/src/labelme2yolo/__main__.py b/src/labelme2yolo/__main__.py index 856c948..7880844 100644 --- a/src/labelme2yolo/__main__.py +++ b/src/labelme2yolo/__main__.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: MIT import sys -if __name__ == '__main__': +if __name__ == "__main__": from .cli import run sys.exit(run()) diff --git a/src/labelme2yolo/cli/__init__.py b/src/labelme2yolo/cli/__init__.py index 24b40d4..64f96e8 100644 --- a/src/labelme2yolo/cli/__init__.py +++ b/src/labelme2yolo/cli/__init__.py @@ -2,19 +2,36 @@ # # SPDX-License-Identifier: MIT import argparse + from labelme2yolo.l2y import Labelme2YOLO def run(): parser = argparse.ArgumentParser("labelme2yolo") - parser.add_argument('--json_dir', type=str, - help='Please input the path of the labelme json files.') - parser.add_argument('--val_size', type=float, nargs='?', default=None, - help='Please input the validation dataset size, for example 0.1 ') - parser.add_argument('--test_size', type=float, nargs='?', default=None, - help='Please input the validation dataset size, for example 0.1 ') - parser.add_argument('--json_name', type=str, nargs='?', default=None, - help='If you put json name, it would convert only one json file to YOLO.') + parser.add_argument( + "--json_dir", type=str, help="Please input the path of the labelme json files." + ) + parser.add_argument( + "--val_size", + type=float, + nargs="?", + default=None, + help="Please input the validation dataset size, for example 0.1 ", + ) + parser.add_argument( + "--test_size", + type=float, + nargs="?", + default=None, + help="Please input the validation dataset size, for example 0.1 ", + ) + parser.add_argument( + "--json_name", + type=str, + nargs="?", + default=None, + help="If you put json name, it would convert only one json file to YOLO.", + ) args = parser.parse_args() if not args.json_dir: diff --git a/src/labelme2yolo/l2y.py b/src/labelme2yolo/l2y.py index cc53468..d5ee4bb 100644 --- a/src/labelme2yolo/l2y.py +++ b/src/labelme2yolo/l2y.py @@ -4,21 +4,21 @@ Created on Aug 18, 2021 @author: xiaosonh @author: GreatV(Wang Xin) """ -import os -import shutil -import math import base64 import io +import json +import math +import os +import shutil from collections import OrderedDict from multiprocessing import Pool -import json import cv2 -from sklearn.model_selection import train_test_split import numpy as np import PIL.ExifTags import PIL.Image import PIL.ImageOps +from sklearn.model_selection import train_test_split # copy form https://github.com/wkentaro/labelme/blob/main/labelme/utils/image.py @@ -80,81 +80,87 @@ def get_label_id_map(json_dir): label_set = set() for file_name in os.listdir(json_dir): - if file_name.endswith('json'): + if file_name.endswith("json"): json_path = os.path.join(json_dir, file_name) data = json.load(open(json_path)) - for shape in data['shapes']: - label_set.add(shape['label']) + for shape in data["shapes"]: + label_set.add(shape["label"]) - return OrderedDict([(label, label_id) - for label_id, label in enumerate(label_set)]) + return OrderedDict([(label, label_id) for label_id, label in enumerate(label_set)]) def save_yolo_label(json_name, label_dir_path, target_dir, yolo_obj_list): - txt_path = os.path.join(label_dir_path, - target_dir, - json_name.replace('.json', '.txt')) + txt_path = os.path.join( + label_dir_path, target_dir, json_name.replace(".json", ".txt") + ) - with open(txt_path, 'w+') as f: + with open(txt_path, "w+") as f: for yolo_obj_idx, yolo_obj in enumerate(yolo_obj_list): - yolo_obj_line = '%s %s %s %s %s\n' % yolo_obj \ - if yolo_obj_idx + 1 != len(yolo_obj_list) else \ - '%s %s %s %s %s' % yolo_obj + yolo_obj_line = ( + "%s %s %s %s %s\n" % yolo_obj + if yolo_obj_idx + 1 != len(yolo_obj_list) + else "%s %s %s %s %s" % yolo_obj + ) f.write(yolo_obj_line) def save_yolo_image(json_data, json_name, image_dir_path, target_dir): - img_name = json_name.replace('.json', '.png') + img_name = json_name.replace(".json", ".png") img_path = os.path.join(image_dir_path, target_dir, img_name) if not os.path.exists(img_path): - img = img_b64_to_arr(json_data['imageData']) + img = img_b64_to_arr(json_data["imageData"]) PIL.Image.fromarray(img).save(img_path) return img_path class Labelme2YOLO(object): - def __init__(self, json_dir): self._json_dir = json_dir self._label_id_map = get_label_id_map(self._json_dir) def _make_train_val_dir(self): - self._label_dir_path = os.path.join(self._json_dir, - 'YOLODataset/labels/') - self._image_dir_path = os.path.join(self._json_dir, - 'YOLODataset/images/') + self._label_dir_path = os.path.join(self._json_dir, "YOLODataset/labels/") + self._image_dir_path = os.path.join(self._json_dir, "YOLODataset/images/") - for yolo_path in (os.path.join(self._label_dir_path + 'train/'), - os.path.join(self._label_dir_path + 'val/'), - os.path.join(self._label_dir_path + 'test/'), - os.path.join(self._image_dir_path + 'train/'), - os.path.join(self._image_dir_path + 'val/'), - os.path.join(self._image_dir_path + 'test/')): + for yolo_path in ( + os.path.join(self._label_dir_path + "train/"), + os.path.join(self._label_dir_path + "val/"), + os.path.join(self._label_dir_path + "test/"), + os.path.join(self._image_dir_path + "train/"), + os.path.join(self._image_dir_path + "val/"), + os.path.join(self._image_dir_path + "test/"), + ): if os.path.exists(yolo_path): shutil.rmtree(yolo_path) os.makedirs(yolo_path) def _train_test_split(self, folders, json_names, val_size, test_size): - if len(folders) > 0 and 'train' in folders and 'val' in folders and 'test' in folders: - train_json_names = self.get_json_names('train/') - val_json_names = self.get_json_names('val/') - test_json_names = self.get_json_names('test/') + if ( + len(folders) > 0 + and "train" in folders + and "val" in folders + and "test" in folders + ): + train_json_names = self.get_json_names("train/") + val_json_names = self.get_json_names("val/") + test_json_names = self.get_json_names("test/") return train_json_names, val_json_names, test_json_names - train_indexes, val_indexes = train_test_split(range(len(json_names)), - test_size=val_size) + train_indexes, val_indexes = train_test_split( + range(len(json_names)), test_size=val_size + ) tmp_train_len = len(train_indexes) test_indexes = [] if test_size: train_indexes, test_indexes = train_test_split( - range(tmp_train_len), test_size=test_size / (1 - val_size)) - train_json_names = [json_names[train_idx] - for train_idx in train_indexes] + range(tmp_train_len), test_size=test_size / (1 - val_size) + ) + train_json_names = [json_names[train_idx] for train_idx in train_indexes] val_json_names = [json_names[val_idx] for val_idx in val_indexes] test_json_names = [json_names[test_idx] for test_idx in test_indexes] @@ -162,90 +168,93 @@ class Labelme2YOLO(object): def get_json_names(self, data_type: str): data_folder = os.path.join(self._json_dir, data_type) - data_json_names = [data_sample_name + '.json' - for data_sample_name in os.listdir(data_folder) - if os.path.isdir(os.path.join(data_folder, data_sample_name))] + data_json_names = [ + data_sample_name + ".json" + for data_sample_name in os.listdir(data_folder) + if os.path.isdir(os.path.join(data_folder, data_sample_name)) + ] return data_json_names def convert(self, val_size, test_size): - json_names = [file_name for file_name in os.listdir(self._json_dir) - if os.path.isfile(os.path.join(self._json_dir, file_name)) and - file_name.endswith('.json')] - folders = [file_name for file_name in os.listdir(self._json_dir) - if os.path.isdir(os.path.join(self._json_dir, file_name))] + json_names = [ + file_name + for file_name in os.listdir(self._json_dir) + if os.path.isfile(os.path.join(self._json_dir, file_name)) + and file_name.endswith(".json") + ] + folders = [ + file_name + for file_name in os.listdir(self._json_dir) + if os.path.isdir(os.path.join(self._json_dir, file_name)) + ] train_json_names, val_json_names, test_json_names = self._train_test_split( - folders, json_names, val_size, test_size) + folders, json_names, val_size, test_size + ) self._make_train_val_dir() # convert labelme object to yolo format object, and save them to files # also get image from labelme json file and save them under images folder - for target_dir, json_names in zip(('train/', 'val/', 'test/'), - (train_json_names, val_json_names, test_json_names)): + for target_dir, json_names in zip( + ("train/", "val/", "test/"), + (train_json_names, val_json_names, test_json_names), + ): pool = Pool(os.cpu_count() - 1) for json_name in json_names: - pool.apply_async(self.covert_json_to_text, - args=(target_dir, json_name)) + pool.apply_async(self.covert_json_to_text, args=(target_dir, json_name)) pool.close() pool.join() - print('Generating dataset.yaml file ...') + print("Generating dataset.yaml file ...") self._save_dataset_yaml() def covert_json_to_text(self, target_dir, json_name): json_path = os.path.join(self._json_dir, json_name) json_data = json.load(open(json_path)) - print('Converting %s for %s ...' % - (json_name, target_dir.replace('/', ''))) + print("Converting %s for %s ..." % (json_name, target_dir.replace("/", ""))) - img_path = save_yolo_image(json_data, - json_name, - self._image_dir_path, - target_dir) + img_path = save_yolo_image( + json_data, json_name, self._image_dir_path, target_dir + ) yolo_obj_list = self._get_yolo_object_list(json_data, img_path) - save_yolo_label(json_name, - self._label_dir_path, - target_dir, - yolo_obj_list) + save_yolo_label(json_name, self._label_dir_path, target_dir, yolo_obj_list) def convert_one(self, json_name): json_path = os.path.join(self._json_dir, json_name) json_data = json.load(open(json_path)) - print('Converting %s ...' % json_name) + print("Converting %s ..." % json_name) - img_path = save_yolo_image(json_data, json_name, - self._json_dir, '') + img_path = save_yolo_image(json_data, json_name, self._json_dir, "") yolo_obj_list = self._get_yolo_object_list(json_data, img_path) - save_yolo_label(json_name, self._json_dir, - '', yolo_obj_list) + save_yolo_label(json_name, self._json_dir, "", yolo_obj_list) def _get_yolo_object_list(self, json_data, img_path): yolo_obj_list = [] img_h, img_w, _ = cv2.imread(img_path).shape - for shape in json_data['shapes']: + for shape in json_data["shapes"]: # labelme circle shape is different from others # it only has 2 points, 1st is circle center, 2nd is drag end point - if shape['shape_type'] == 'circle': - yolo_obj = self._get_circle_shape_yolo_object( - shape, img_h, img_w) + if shape["shape_type"] == "circle": + yolo_obj = self._get_circle_shape_yolo_object(shape, img_h, img_w) else: - yolo_obj = self._get_other_shape_yolo_object( - shape, img_h, img_w) + yolo_obj = self._get_other_shape_yolo_object(shape, img_h, img_w) yolo_obj_list.append(yolo_obj) return yolo_obj_list def _get_circle_shape_yolo_object(self, shape, img_h, img_w): - obj_center_x, obj_center_y = shape['points'][0] + obj_center_x, obj_center_y = shape["points"][0] - radius = math.sqrt((obj_center_x - shape['points'][1][0]) ** 2 + - (obj_center_y - shape['points'][1][1]) ** 2) + radius = math.sqrt( + (obj_center_x - shape["points"][1][0]) ** 2 + + (obj_center_y - shape["points"][1][1]) ** 2 + ) obj_w = 2 * radius obj_h = 2 * radius @@ -254,45 +263,42 @@ class Labelme2YOLO(object): yolo_w = round(float(obj_w / img_w), 6) yolo_h = round(float(obj_h / img_h), 6) - label_id = self._label_id_map[shape['label']] + label_id = self._label_id_map[shape["label"]] return label_id, yolo_center_x, yolo_center_y, yolo_w, yolo_h def _get_other_shape_yolo_object(self, shape, img_h, img_w): def __get_object_desc(obj_port_list): - def __get_dist(int_list): return max(int_list) - min(int_list) + def __get_dist(int_list): + return max(int_list) - min(int_list) x_lists = [port[0] for port in obj_port_list] y_lists = [port[1] for port in obj_port_list] return min(x_lists), __get_dist(x_lists), min(y_lists), __get_dist(y_lists) - obj_x_min, obj_w, obj_y_min, obj_h = __get_object_desc(shape['points']) + obj_x_min, obj_w, obj_y_min, obj_h = __get_object_desc(shape["points"]) yolo_center_x = round(float((obj_x_min + obj_w / 2.0) / img_w), 6) yolo_center_y = round(float((obj_y_min + obj_h / 2.0) / img_h), 6) yolo_w = round(float(obj_w / img_w), 6) yolo_h = round(float(obj_h / img_h), 6) - label_id = self._label_id_map[shape['label']] + label_id = self._label_id_map[shape["label"]] return label_id, yolo_center_x, yolo_center_y, yolo_w, yolo_h def _save_dataset_yaml(self): - yaml_path = os.path.join( - self._json_dir, 'YOLODataset', 'dataset.yaml') + yaml_path = os.path.join(self._json_dir, "YOLODataset", "dataset.yaml") - with open(yaml_path, 'w+') as yaml_file: - yaml_file.write('train: %s\n' % - os.path.join(self._image_dir_path, 'train')) - yaml_file.write('val: %s\n\n' % - os.path.join(self._image_dir_path, 'val')) - yaml_file.write('test: %s\n\n' % - os.path.join(self._image_dir_path, 'test')) - yaml_file.write('nc: %i\n\n' % len(self._label_id_map)) + with open(yaml_path, "w+") as yaml_file: + yaml_file.write("train: %s\n" % os.path.join(self._image_dir_path, "train")) + yaml_file.write("val: %s\n\n" % os.path.join(self._image_dir_path, "val")) + yaml_file.write("test: %s\n\n" % os.path.join(self._image_dir_path, "test")) + yaml_file.write("nc: %i\n\n" % len(self._label_id_map)) - names_str = '' + names_str = "" for label, _ in self._label_id_map.items(): names_str += "'%s', " % label - names_str = names_str.rstrip(', ') - yaml_file.write('names: [%s]' % names_str) + names_str = names_str.rstrip(", ") + yaml_file.write("names: [%s]" % names_str) From 8fc231ce6c8f199ecf2d09116472a7d1a304ee6e Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Thu, 11 Aug 2022 09:05:43 +0800 Subject: [PATCH 14/21] bug fixed --- src/labelme2yolo/l2y.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/labelme2yolo/l2y.py b/src/labelme2yolo/l2y.py index d5ee4bb..a93ee33 100644 --- a/src/labelme2yolo/l2y.py +++ b/src/labelme2yolo/l2y.py @@ -21,6 +21,10 @@ import PIL.ImageOps from sklearn.model_selection import train_test_split +# number of LabelMe2YOLO multiprocessing threads +NUM_THREADS = max(1, os.cpu_count() - 1) + + # copy form https://github.com/wkentaro/labelme/blob/main/labelme/utils/image.py def img_data_to_pil(img_data): f = io.BytesIO() @@ -199,7 +203,7 @@ class Labelme2YOLO(object): ("train/", "val/", "test/"), (train_json_names, val_json_names, test_json_names), ): - pool = Pool(os.cpu_count() - 1) + pool = Pool(NUM_THREADS) for json_name in json_names: pool.apply_async(self.covert_json_to_text, args=(target_dir, json_name)) pool.close() From fc5050795adfcca7a34a61a0c9f652c4beffd34e Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Thu, 11 Aug 2022 17:56:57 +0800 Subject: [PATCH 15/21] Update pyproject.toml --- pyproject.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bf2ca72..b96849c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,8 +24,9 @@ classifiers = [ ] dependencies = [ "opencv-python>=4.1.2", - "Pillow", - "scikit-learn" + "Pillow~=9.2.0", + "scikit-learn~=1.1.1", + "numpy~=1.23.1" ] dynamic = ["version"] From 6b7ebf6d1f9f4cc0333060befa6b74973573c493 Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Thu, 11 Aug 2022 18:01:05 +0800 Subject: [PATCH 16/21] Create dependabot.yml --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..91abb11 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" From bf23421e6bd984f3826110548a646f14eaf7beff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Aug 2022 10:01:40 +0000 Subject: [PATCH 17/21] Update scikit-learn requirement from ~=1.1.1 to ~=1.1.2 Updates the requirements on [scikit-learn](https://github.com/scikit-learn/scikit-learn) to permit the latest version. - [Release notes](https://github.com/scikit-learn/scikit-learn/releases) - [Commits](https://github.com/scikit-learn/scikit-learn/compare/1.1.1...1.1.2) --- updated-dependencies: - dependency-name: scikit-learn dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 71427c5..5e49dd1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ opencv-python>=4.1.2 Pillow~=9.2.0 -scikit-learn~=1.1.1 +scikit-learn~=1.1.2 numpy~=1.23.1 \ No newline at end of file From e87c06162f4d32f0eb71dcbe82a7e9d92d9db2f4 Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Fri, 12 Aug 2022 13:51:11 +0800 Subject: [PATCH 18/21] Create python-package.yml --- .github/workflows/python-package.yml | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/python-package.yml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..59ddc51 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,40 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python package + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10"] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest From 085e6d53b774b99c308099a764b69cf3fc46d51c Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Fri, 12 Aug 2022 22:43:11 +0800 Subject: [PATCH 19/21] Update requirements.txt --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5e49dd1..d53ce17 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ opencv-python>=4.1.2 Pillow~=9.2.0 scikit-learn~=1.1.2 - -numpy~=1.23.1 \ No newline at end of file +numpy~=1.23.1 From 9e5d3dd39776b8cea4aa6907a377c1d4f61bc15d Mon Sep 17 00:00:00 2001 From: Wang Xin Date: Fri, 12 Aug 2022 23:04:49 +0800 Subject: [PATCH 20/21] update requirements --- .github/workflows/python-package.yml | 40 ---------------------------- requirements.txt | 8 +++--- 2 files changed, 4 insertions(+), 44 deletions(-) delete mode 100644 .github/workflows/python-package.yml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml deleted file mode 100644 index 59ddc51..0000000 --- a/.github/workflows/python-package.yml +++ /dev/null @@ -1,40 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Python package - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] - - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with pytest - run: | - pytest diff --git a/requirements.txt b/requirements.txt index d53ce17..8ded313 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -opencv-python>=4.1.2 -Pillow~=9.2.0 -scikit-learn~=1.1.2 -numpy~=1.23.1 +opencv-python +Pillow +scikit-learn +numpy From 807275d57b866521783749d44c9fdd1ac89d98f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 09:26:21 +0000 Subject: [PATCH 21/21] Update pillow requirement from ~=9.2.0 to >=9.2,<9.4 Updates the requirements on [pillow](https://github.com/python-pillow/Pillow) to permit the latest version. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/9.2.0...9.3.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b96849c..0d1efe9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ ] dependencies = [ "opencv-python>=4.1.2", - "Pillow~=9.2.0", + "Pillow>=9.2,<9.4", "scikit-learn~=1.1.1", "numpy~=1.23.1" ]