Skip to content

Commit

Permalink
Ignore empty lines in YOLO annotations (cvat-ai#221)
Browse files Browse the repository at this point in the history
* Ignore empty lines in yolo annotations

* Add type hints for image class, catch image opening errors in image.size

* update changelog
  • Loading branch information
Maxim Zhiltsov authored Apr 20, 2021
1 parent b3eaf4a commit 2f9614a
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Image extension in CVAT format export (<https://github.com/openvinotoolkit/datumaro/pull/214>)
- Added a label "face" for bounding boxes in Wider Face (<https://github.com/openvinotoolkit/datumaro/pull/215>)
- Allowed adding "difficult", "truncated", "occluded" attributes when converting to Pascal VOC if these attributes are not present (<https://github.com/openvinotoolkit/datumaro/pull/216>)
- Empty lines in YOLO annotations are ignored (<https://github.com/openvinotoolkit/datumaro/pull/221>)

### Security
-
Expand Down
5 changes: 5 additions & 0 deletions datumaro/components/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,11 @@ def mean_std(dataset):
var = lambda i, s: s[i][1]

for i, item in enumerate(dataset):
size = item.image.size
if size is None:
log.warning("Item %s: can't detect image size, "
"the image will be skipped from pixel statistics", item.id)
continue
counts[i] = np.prod(item.image.size)

image = item.image.data
Expand Down
6 changes: 4 additions & 2 deletions datumaro/plugins/yolo_format/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def __init__(self, config_path, image_info=None):
with open(list_path, 'r', encoding='utf-8') as f:
subset.items = OrderedDict(
(self.name_from_path(p), self.localize_path(p))
for p in f
for p in f if p.strip()
)
subsets[subset_name] = subset

Expand Down Expand Up @@ -176,7 +176,9 @@ def _load_categories(names_path):

with open(names_path, 'r', encoding='utf-8') as f:
for label in f:
label_categories.add(label.strip())
label = label.strip()
if label:
label_categories.add(label)

return label_categories

Expand Down
34 changes: 20 additions & 14 deletions datumaro/util/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@

from enum import Enum
from io import BytesIO
from typing import Iterator, Iterable, Union
from typing import Any, Callable, Iterator, Iterable, Optional, Tuple, Union
import numpy as np
import os
import os.path as osp

_IMAGE_BACKENDS = Enum('_IMAGE_BACKENDS', ['cv2', 'PIL'])
_IMAGE_BACKEND = None
_image_loading_errors = (FileNotFoundError, )
try:
import cv2
_IMAGE_BACKEND = _IMAGE_BACKENDS.cv2
except ImportError:
import PIL
_IMAGE_BACKEND = _IMAGE_BACKENDS.PIL
_image_loading_errors = (*_image_loading_errors, PIL.UnidentifiedImageError)

from datumaro.util.image_cache import ImageCache as _ImageCache
from datumaro.util.os_util import walk
Expand All @@ -33,6 +35,8 @@ def load_image(path, dtype=np.float32):
if _IMAGE_BACKEND == _IMAGE_BACKENDS.cv2:
import cv2
image = cv2.imread(path, cv2.IMREAD_UNCHANGED)
if image is None:
raise FileNotFoundError("Can't open image: %s" % path)
image = image.astype(dtype)
elif _IMAGE_BACKEND == _IMAGE_BACKENDS.PIL:
from PIL import Image
Expand All @@ -43,8 +47,6 @@ def load_image(path, dtype=np.float32):
else:
raise NotImplementedError()

if image is None:
raise ValueError("Can't open image '%s'" % path)
assert len(image.shape) in {2, 3}
if len(image.shape) == 3:
assert image.shape[2] in {3, 4}
Expand Down Expand Up @@ -227,15 +229,16 @@ def __hash__(self):
return hash((id(self), self.path, self.loader))

class Image:
def __init__(self, data=None, path=None, loader=None, cache=None,
size=None):
assert size is None or len(size) == 2
def __init__(self, data: Union[None, Callable, np.ndarray] = None,
path: Optional[str] = None, loader: Optional[Callable] = None,
size: Optional[Tuple[int, int]] = None, cache: Any = None):
assert size is None or len(size) == 2, size
if size is not None:
assert len(size) == 2 and 0 < size[0] and 0 < size[1], size
size = tuple(size)
self._size = size # (H, W)

assert path is None or isinstance(path, str)
assert path is None or isinstance(path, str), path
if path is None:
path = ''
elif path:
Expand All @@ -254,15 +257,15 @@ def __init__(self, data=None, path=None, loader=None, cache=None,
self._size = data.shape[:2]

@property
def path(self):
def path(self) -> str:
return self._path

@property
def ext(self):
def ext(self) -> str:
return osp.splitext(osp.basename(self.path))[1]

@property
def data(self):
def data(self) -> np.ndarray:
if callable(self._data):
data = self._data()
else:
Expand All @@ -273,17 +276,20 @@ def data(self):
return data

@property
def has_data(self):
def has_data(self) -> bool:
return self._data is not None

@property
def has_size(self):
def has_size(self) -> bool:
return self._size is not None or isinstance(self._data, np.ndarray)

@property
def size(self):
def size(self) -> Optional[Tuple[int, int]]:
if self._size is None:
data = self.data
try:
data = self.data
except _image_loading_errors:
return None
if data is not None:
self._size = data.shape[:2]
return self._size
Expand Down

0 comments on commit 2f9614a

Please sign in to comment.