-
Notifications
You must be signed in to change notification settings - Fork 155
Add KaggleCoco and Bbox capability in KaggleImageCsv and KaggleImageTxt #1273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
wonjuleee
merged 12 commits into
open-edge-platform:develop
from
wonjuleee:add_kaggle_det
Feb 28, 2024
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
1c80822
add bbox import
wonjuleee 060e273
add kaggle_imag_csv/txt for det and kaggle_coco
wonjuleee 8c2dedd
update specs
wonjuleee 0c2efb6
add kaggle_coco unit test
wonjuleee f8144eb
add unit test for image_csv_det
wonjuleee c6bbb87
add unit test
wonjuleee 3e0a0df
update notebook for kaggle import
wonjuleee 0fd2c31
update notebook for kaggle import
wonjuleee 0b3e406
update changelog
wonjuleee 914d180
fix typo in kaggle notbook
wonjuleee fc8b3ed
update code from reviewer comment
wonjuleee de8412a
add more unit tests
wonjuleee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |||||||||||
|
|
||||||||||||
| import os | ||||||||||||
| import os.path as osp | ||||||||||||
| import re | ||||||||||||
| import warnings | ||||||||||||
| from typing import Dict, Optional, Type, TypeVar, Union | ||||||||||||
|
|
||||||||||||
|
|
@@ -24,6 +25,10 @@ | |||||||||||
| from datumaro.components.errors import InvalidAnnotationError, InvalidFieldError, MissingFieldError | ||||||||||||
| from datumaro.components.importer import ImportContext | ||||||||||||
| from datumaro.components.media import Image, ImageFromFile | ||||||||||||
| from datumaro.plugins.data_formats.coco.base import CocoInstancesBase | ||||||||||||
| from datumaro.plugins.data_formats.coco.format import CocoTask | ||||||||||||
| from datumaro.plugins.data_formats.coco.page_mapper import COCOPageMapper | ||||||||||||
| from datumaro.util import parse_json_file | ||||||||||||
| from datumaro.util.image import IMAGE_EXTENSIONS, load_image | ||||||||||||
|
|
||||||||||||
| T = TypeVar("T") | ||||||||||||
|
|
@@ -42,71 +47,133 @@ def __init__( | |||||||||||
| super().__init__(ctx=ctx) | ||||||||||||
|
|
||||||||||||
| self._subset = subset | ||||||||||||
|
|
||||||||||||
| self._path = path | ||||||||||||
| self._columns = columns | ||||||||||||
|
|
||||||||||||
| self._items, label_cat = self._load_items(ann_file, columns) | ||||||||||||
| self._categories = {AnnotationType.label: label_cat} | ||||||||||||
| if "media" not in columns: | ||||||||||||
| raise MissingFieldError("media") | ||||||||||||
|
|
||||||||||||
| def _load_items(self, ann_file: str, columns: Dict[str, Union[str, list]]): | ||||||||||||
| df = pd.read_csv(ann_file, header=None, on_bad_lines="skip") | ||||||||||||
| self._label_cat = LabelCategories() | ||||||||||||
| self._items = self._load_items(ann_file, columns) | ||||||||||||
| self._categories = {AnnotationType.label: self._label_cat} | ||||||||||||
|
|
||||||||||||
| indices = {} | ||||||||||||
| for key, field in columns.items(): | ||||||||||||
| if key == "bbox": | ||||||||||||
| indices[key] = [] | ||||||||||||
| for v in field: | ||||||||||||
| indices[key].append(list(df.iloc[0]).index(v)) | ||||||||||||
| else: | ||||||||||||
| indices[key] = list(df.iloc[0]).index(field) | ||||||||||||
| def _get_media_path(self, media_name: str): | ||||||||||||
| media_path = osp.join(self._path, media_name) | ||||||||||||
| if osp.exists(media_path): | ||||||||||||
| return media_path | ||||||||||||
|
|
||||||||||||
| label_cat = LabelCategories() | ||||||||||||
| items = [] | ||||||||||||
| for ind, row in df.iterrows(): | ||||||||||||
| if ind == 0: | ||||||||||||
| continue | ||||||||||||
| for ext in IMAGE_EXTENSIONS: | ||||||||||||
| media_path_with_ext = media_path + ext | ||||||||||||
| if osp.exists(media_path_with_ext): | ||||||||||||
| return media_path_with_ext | ||||||||||||
|
|
||||||||||||
| return None | ||||||||||||
|
|
||||||||||||
| def _parse_bbox_coords(self, bbox_str): | ||||||||||||
| coords = re.findall(r"[-+]?\d*\.\d+|\d+", bbox_str) | ||||||||||||
| if len(coords) != 4: | ||||||||||||
| raise ValueError("Bounding box coordinates must have exactly 4 values.") | ||||||||||||
|
|
||||||||||||
| # expected to output [x1, y1, x2, y2] | ||||||||||||
| return [float(coord.strip()) for coord in coords] | ||||||||||||
|
|
||||||||||||
| def _load_annotations(self, datas: list, indices: Dict[str, int], bbox_flag: bool): | ||||||||||||
| if "label" in indices: | ||||||||||||
| label_name = str(datas[indices["label"]]) | ||||||||||||
| label, cat = self._label_cat.find(label_name) | ||||||||||||
| if not cat: | ||||||||||||
| self._label_cat.add(label_name) | ||||||||||||
| label, _ = self._label_cat.find(label_name) | ||||||||||||
| else: | ||||||||||||
| _, cat = self._label_cat.find("object") | ||||||||||||
| if not cat: | ||||||||||||
| self._label_cat.add("object") | ||||||||||||
| label = 0 | ||||||||||||
|
|
||||||||||||
| if "label" in indices and not bbox_flag: | ||||||||||||
| return Label(label=label) | ||||||||||||
| if bbox_flag: | ||||||||||||
| if "bbox" in indices: | ||||||||||||
| coords = self._parse_bbox_coords(datas[indices["bbox"]]) | ||||||||||||
| return Bbox( | ||||||||||||
| label=label, | ||||||||||||
| x=coords[0], | ||||||||||||
| y=coords[1], | ||||||||||||
| w=coords[2] - coords[0], | ||||||||||||
| h=coords[3] - coords[1], | ||||||||||||
| ) | ||||||||||||
| if "width" in indices and "height" in indices: | ||||||||||||
| return Bbox( | ||||||||||||
| label=label, | ||||||||||||
| x=float(datas[indices["x1"]]), | ||||||||||||
| y=float(datas[indices["y1"]]), | ||||||||||||
| w=float(datas[indices["width"]]), | ||||||||||||
| h=float(datas[indices["height"]]), | ||||||||||||
| ) | ||||||||||||
| if "x2" in indices and "y2" in indices: | ||||||||||||
| return Bbox( | ||||||||||||
| label=label, | ||||||||||||
| x=float(datas[indices["x1"]]), | ||||||||||||
| y=float(datas[indices["y1"]]), | ||||||||||||
| w=float(datas[indices["x2"]]) - float(datas[indices["x1"]]), | ||||||||||||
| h=float(datas[indices["y2"]]) - float(datas[indices["y1"]]), | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| def _load_items(self, ann_file: str, columns: Dict[str, Union[str, list]]): | ||||||||||||
| df = pd.read_csv(ann_file, header=None, on_bad_lines="skip") | ||||||||||||
| df_fields = list(df.iloc[0]) | ||||||||||||
|
|
||||||||||||
| indices = {"media": df_fields.index(columns["media"])} | ||||||||||||
| if "label" in columns: | ||||||||||||
| indices.update({"label": df_fields.index(columns["label"])}) | ||||||||||||
|
Comment on lines
+126
to
+127
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
|
||||||||||||
| bbox_flag = False | ||||||||||||
| bbox_index = columns.get("bbox") | ||||||||||||
| if bbox_index: | ||||||||||||
| bbox_flag = True | ||||||||||||
| bbox_indices = {"x1", "x2", "y1", "y2", "width", "height"} | ||||||||||||
| if isinstance(bbox_index, str): | ||||||||||||
| indices["bbox"] = df_fields.index(bbox_index) | ||||||||||||
| elif isinstance(bbox_index, dict): | ||||||||||||
| indices.update( | ||||||||||||
| { | ||||||||||||
| key: df_fields.index(bbox_index[key]) | ||||||||||||
| for key in bbox_indices | ||||||||||||
| if bbox_index.get(key) | ||||||||||||
| } | ||||||||||||
| ) | ||||||||||||
| if not ( | ||||||||||||
| {"x1", "x2", "y1", "y2"} <= bbox_indices | ||||||||||||
| or {"x1", "y1", "width", "height"} <= bbox_indices | ||||||||||||
| ): | ||||||||||||
| warnings.warn("Insufficient box coordinate is given for importing bounding boxes.") | ||||||||||||
| bbox_flag = False | ||||||||||||
|
|
||||||||||||
| items = dict() | ||||||||||||
| for _, row in df.iloc[1:].iterrows(): # Skip header row | ||||||||||||
| data_info = list(row) | ||||||||||||
|
|
||||||||||||
| media_name = data_info[indices["media"]] | ||||||||||||
| id = osp.splitext(media_name)[0] | ||||||||||||
|
|
||||||||||||
| if not media_name.lower().endswith(tuple(IMAGE_EXTENSIONS)): | ||||||||||||
| for ext in IMAGE_EXTENSIONS: | ||||||||||||
| media_path = osp.join(self._path, media_name + ext) | ||||||||||||
| if osp.exists(media_path): | ||||||||||||
| break | ||||||||||||
| else: | ||||||||||||
| media_path = osp.join(self._path, media_name) | ||||||||||||
| item_id = osp.splitext(media_name)[0] | ||||||||||||
|
|
||||||||||||
| media_path = self._get_media_path(media_name) | ||||||||||||
| if not osp.exists(media_path): | ||||||||||||
| warnings.warn( | ||||||||||||
| f"'{media_path}' is not existed in the directory, " | ||||||||||||
| f"so we skip to create an dataset item according to {row}." | ||||||||||||
| ) | ||||||||||||
| continue | ||||||||||||
|
|
||||||||||||
| annotations = [] | ||||||||||||
| if "label" in indices: | ||||||||||||
| label_name = str(data_info[indices["label"]]) | ||||||||||||
| label, cat = label_cat.find(label_name) | ||||||||||||
|
|
||||||||||||
| if not cat: | ||||||||||||
| label_cat.add(label_name) | ||||||||||||
| label, _ = label_cat.find(label_name) | ||||||||||||
|
|
||||||||||||
| annotations.append(Label(label=label)) | ||||||||||||
|
|
||||||||||||
| items.append( | ||||||||||||
| DatasetItem( | ||||||||||||
| id=id, | ||||||||||||
| ann = self._load_annotations(data_info, indices, bbox_flag) | ||||||||||||
| if item_id in items: | ||||||||||||
| items[item_id].annotations.append(ann) | ||||||||||||
| else: | ||||||||||||
| items[item_id] = DatasetItem( | ||||||||||||
| id=item_id, | ||||||||||||
| subset=self._subset, | ||||||||||||
| media=Image.from_file(path=media_path), | ||||||||||||
| annotations=annotations, | ||||||||||||
| annotations=[ann], | ||||||||||||
| ) | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| return items, label_cat | ||||||||||||
| return items.values() | ||||||||||||
|
|
||||||||||||
| def categories(self): | ||||||||||||
| return self._categories | ||||||||||||
|
|
@@ -115,7 +182,7 @@ def __iter__(self): | |||||||||||
| yield from self._items | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| class KaggleImageTxtBase(DatasetBase): | ||||||||||||
| class KaggleImageTxtBase(KaggleImageCsvBase): | ||||||||||||
| def __init__( | ||||||||||||
| self, | ||||||||||||
| path: str, | ||||||||||||
|
|
@@ -125,77 +192,52 @@ def __init__( | |||||||||||
| subset: Optional[str] = DEFAULT_SUBSET_NAME, | ||||||||||||
| ctx: Optional[ImportContext] = None, | ||||||||||||
| ): | ||||||||||||
| super().__init__(ctx=ctx) | ||||||||||||
|
|
||||||||||||
| self._subset = subset | ||||||||||||
|
|
||||||||||||
| self._path = path | ||||||||||||
| self._columns = columns | ||||||||||||
|
|
||||||||||||
| self._items, label_cat = self._load_items(ann_file, columns) | ||||||||||||
| self._categories = {AnnotationType.label: label_cat} | ||||||||||||
|
|
||||||||||||
| def _load_items(self, ann_file: str, columns: Dict[str, Union[int, list]]): | ||||||||||||
| label_cat = LabelCategories() | ||||||||||||
| super().__init__(path=path, ann_file=ann_file, columns=columns, subset=subset, ctx=ctx) | ||||||||||||
|
|
||||||||||||
| def _load_items(self, ann_file: str, columns: Dict[str, Union[int, Dict]]): | ||||||||||||
| bbox_flag = False | ||||||||||||
| if "bbox" in columns: | ||||||||||||
| bbox_flag = True | ||||||||||||
| bbox_columns = columns.pop("bbox") | ||||||||||||
| if isinstance(bbox_columns, dict): | ||||||||||||
| if not ( | ||||||||||||
| all(item in bbox_columns for item in ["x1", "x2", "y1", "y2"]) | ||||||||||||
| or all(item in bbox_columns for item in ["x1", "y1", "width", "height"]) | ||||||||||||
| ): | ||||||||||||
| warnings.warn( | ||||||||||||
| "Insufficient box coordinate is given for importing bounding boxes." | ||||||||||||
| ) | ||||||||||||
| bbox_flag = False | ||||||||||||
| columns.update(bbox_columns) | ||||||||||||
|
|
||||||||||||
| item_ids = [] | ||||||||||||
| items = [] | ||||||||||||
| items = dict() | ||||||||||||
| with open(ann_file, "r", encoding="utf-8") as f: | ||||||||||||
| for line in f: | ||||||||||||
| line = line.split() | ||||||||||||
| line = re.split(r"\s|,", line) | ||||||||||||
|
|
||||||||||||
| media_name = line[columns["media"]] | ||||||||||||
| item_id = osp.splitext(media_name)[0] | ||||||||||||
|
|
||||||||||||
| if item_id in item_ids: | ||||||||||||
| warnings.warn( | ||||||||||||
| f"There is duplicated '{id}' in {ann_file}, " | ||||||||||||
| f"so we skip to create an dataset item according to {line}." | ||||||||||||
| ) | ||||||||||||
| continue | ||||||||||||
|
|
||||||||||||
| if not media_name.lower().endswith(tuple(IMAGE_EXTENSIONS)): | ||||||||||||
| for ext in IMAGE_EXTENSIONS: | ||||||||||||
| media_path = osp.join(self._path, media_name + ext) | ||||||||||||
| if osp.exists(media_path): | ||||||||||||
| break | ||||||||||||
| else: | ||||||||||||
| media_path = osp.join(self._path, media_name) | ||||||||||||
|
|
||||||||||||
| media_path = self._get_media_path(media_name) | ||||||||||||
| if not osp.exists(media_path): | ||||||||||||
| warnings.warn( | ||||||||||||
| f"'{media_path}' is not existed in the directory, " | ||||||||||||
| f"so we skip to create an dataset item according to {line}." | ||||||||||||
| ) | ||||||||||||
| continue | ||||||||||||
|
|
||||||||||||
| annotations = [] | ||||||||||||
| if "label" in columns: | ||||||||||||
| label_name = str(line[columns["label"]]) | ||||||||||||
| label, cat = label_cat.find(label_name) | ||||||||||||
|
|
||||||||||||
| if not cat: | ||||||||||||
| label_cat.add(label_name) | ||||||||||||
| label, _ = label_cat.find(label_name) | ||||||||||||
|
|
||||||||||||
| annotations.append(Label(label=label)) | ||||||||||||
|
|
||||||||||||
| item_ids.append(item_id) | ||||||||||||
| items.append( | ||||||||||||
| DatasetItem( | ||||||||||||
| ann = self._load_annotations(line, columns, bbox_flag) | ||||||||||||
| if item_id in items: | ||||||||||||
| items[item_id].annotations.append(ann) | ||||||||||||
| else: | ||||||||||||
| items[item_id] = DatasetItem( | ||||||||||||
| id=item_id, | ||||||||||||
| subset=self._subset, | ||||||||||||
| media=Image.from_file(path=media_path), | ||||||||||||
| annotations=annotations, | ||||||||||||
| annotations=[ann], | ||||||||||||
| ) | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| return items, label_cat | ||||||||||||
|
|
||||||||||||
| def categories(self): | ||||||||||||
| return self._categories | ||||||||||||
|
|
||||||||||||
| def __iter__(self): | ||||||||||||
| yield from self._items | ||||||||||||
| return items.values() | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| class KaggleImageMaskBase(DatasetBase): | ||||||||||||
|
|
@@ -433,3 +475,50 @@ def _parse_annotations(self, img_file: str, ann_file: str): | |||||||||||
| annotations.append(Bbox(id=obj_id, label=label_id, x=x, y=y, w=w, h=h)) | ||||||||||||
|
|
||||||||||||
| return annotations | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| class KaggleCocoBase(CocoInstancesBase, SubsetBase): | ||||||||||||
| def __init__( | ||||||||||||
| self, | ||||||||||||
| path: str, | ||||||||||||
| ann_file: str, | ||||||||||||
| *, | ||||||||||||
| subset: Optional[str] = None, | ||||||||||||
| ctx: Optional[ImportContext] = None, | ||||||||||||
| stream: bool = False, | ||||||||||||
| ): | ||||||||||||
| SubsetBase.__init__(self, subset=subset, ctx=ctx) | ||||||||||||
|
|
||||||||||||
| self._rootpath = path | ||||||||||||
| self._images_dir = path | ||||||||||||
| self._path = ann_file | ||||||||||||
| self._task = CocoTask.instances | ||||||||||||
| self._merge_instance_polygons = False | ||||||||||||
|
|
||||||||||||
| keep_original_category_ids = False | ||||||||||||
|
|
||||||||||||
| self._stream = stream | ||||||||||||
| if not stream: | ||||||||||||
| self._page_mapper = None # No use in case of stream = False | ||||||||||||
|
|
||||||||||||
| json_data = parse_json_file(ann_file) | ||||||||||||
|
|
||||||||||||
| self._load_categories( | ||||||||||||
| json_data, | ||||||||||||
| keep_original_ids=keep_original_category_ids, | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| self._items = self._load_items(json_data) | ||||||||||||
|
|
||||||||||||
| del json_data | ||||||||||||
| else: | ||||||||||||
| self._page_mapper = COCOPageMapper(ann_file) | ||||||||||||
|
|
||||||||||||
| categories_data = self._page_mapper.stream_parse_categories_data() | ||||||||||||
|
|
||||||||||||
| self._load_categories( | ||||||||||||
| {"categories": categories_data}, | ||||||||||||
| keep_original_ids=keep_original_category_ids, | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| self._length = None | ||||||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| image_name,label_name,x1,y1,x2,y2 | ||
| 1.jpg,dog,0,1,1,2 | ||
| 1.jpg,cat,1,2,3,3 | ||
| 2.jpg,cat,0,0,1,1 | ||
| 3.jpg,dog,0,2,2,4 | ||
| 3.jpg,dog,0,0,1,1 | ||
| 3.jpg,cat,1,1,2,2 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems better to get label index or use default value to avoid check this twice.