Merge branch 'develop' into dk/cvat-ui-tags

main
Dmitry Kalinin 6 years ago
commit 23a658f2c0

@ -15,7 +15,7 @@ Next steps should work on clear Ubuntu 18.04.
- Install necessary dependencies: - Install necessary dependencies:
```sh ```sh
$ sudo apt update && apt install -y nodejs npm curl redis-server python3-dev python3-pip python3-venv libldap2-dev libsasl2-dev $ sudo apt-get update && apt-get --no-install-recommends install -y nodejs npm curl redis-server python3-dev python3-pip python3-venv libldap2-dev libsasl2-dev
``` ```
- Install [Visual Studio Code](https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions) - Install [Visual Studio Code](https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions)

@ -21,14 +21,16 @@ ENV DJANGO_CONFIGURATION=${DJANGO_CONFIGURATION}
# Install necessary apt packages # Install necessary apt packages
RUN apt-get update && \ RUN apt-get update && \
apt-get install -yq \ apt-get --no-install-recommends install -yq \
software-properties-common && \ software-properties-common && \
add-apt-repository ppa:mc3man/xerus-media -y && \ add-apt-repository ppa:mc3man/xerus-media -y && \
add-apt-repository ppa:mc3man/gstffmpeg-keep -y && \ add-apt-repository ppa:mc3man/gstffmpeg-keep -y && \
apt-get update && \ apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -yq \ DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends install -yq \
apache2 \ apache2 \
apache2-dev \ apache2-dev \
apt-utils \
build-essential \
libapache2-mod-xsendfile \ libapache2-mod-xsendfile \
supervisor \ supervisor \
ffmpeg \ ffmpeg \
@ -44,7 +46,7 @@ RUN apt-get update && \
poppler-utils \ poppler-utils \
curl && \ curl && \
curl https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash && \ curl https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash && \
apt-get install -y git-lfs && git lfs install && \ apt-get --no-install-recommends install -y git-lfs && git lfs install && \
if [ -z ${socks_proxy} ]; then \ if [ -z ${socks_proxy} ]; then \
echo export "GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o ConnectTimeout=30\"" >> ${HOME}/.bashrc; \ echo export "GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o ConnectTimeout=30\"" >> ${HOME}/.bashrc; \
else \ else \

@ -7,9 +7,12 @@ RUN curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - &
echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list && \ echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list && \
curl https://deb.nodesource.com/setup_9.x | bash - && \ curl https://deb.nodesource.com/setup_9.x | bash - && \
apt-get update && \ apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -yq \ DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends install -yq \
apt-utils \
build-essential \
google-chrome-stable \ google-chrome-stable \
nodejs && \ nodejs \
python3-dev && \
rm -rf /var/lib/apt/lists/*; rm -rf /var/lib/apt/lists/*;
RUN python3 -m pip install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGURATION}.txt RUN python3 -m pip install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGURATION}.txt
@ -29,4 +32,4 @@ RUN mkdir -p tests && cd tests && npm install \
qunit; \ qunit; \
echo "export PATH=~/tests/node_modules/.bin:${PATH}" >> ~/.bashrc; echo "export PATH=~/tests/node_modules/.bin:${PATH}" >> ~/.bashrc;
ENTRYPOINT [] ENTRYPOINT []

@ -13,10 +13,10 @@
sudo add-apt-repository ppa:graphics-drivers/ppa sudo add-apt-repository ppa:graphics-drivers/ppa
sudo apt-get update sudo apt-get update
sudo apt-cache search nvidia-* # find latest nvidia driver sudo apt-cache search nvidia-* # find latest nvidia driver
sudo apt-get install nvidia-* # install the nvidia driver sudo apt-get --no-install-recommends install nvidia-* # install the nvidia driver
sudo apt-get install mesa-common-dev sudo apt-get --no-install-recommends install mesa-common-dev
sudo apt-get install freeglut3-dev sudo apt-get --no-install-recommends install freeglut3-dev
sudo apt-get install nvidia-modprobe sudo apt-get --no-install-recommends install nvidia-modprobe
``` ```
#### Reboot your PC and verify installation by `nvidia-smi` command. #### Reboot your PC and verify installation by `nvidia-smi` command.

@ -22,7 +22,7 @@ cd /tmp/components/openvino
tar -xzf `ls | grep "openvino_toolkit"` tar -xzf `ls | grep "openvino_toolkit"`
cd `ls -d */ | grep "openvino_toolkit"` cd `ls -d */ | grep "openvino_toolkit"`
apt-get update && apt-get install -y sudo cpio && \ apt-get update && apt-get --no-install-recommends install -y sudo cpio && \
if [ -f "install_cv_sdk_dependencies.sh" ]; then ./install_cv_sdk_dependencies.sh; \ if [ -f "install_cv_sdk_dependencies.sh" ]; then ./install_cv_sdk_dependencies.sh; \
else ./install_openvino_dependencies.sh; fi && SUDO_FORCE_REMOVE=yes apt-get remove -y sudo else ./install_openvino_dependencies.sh; fi && SUDO_FORCE_REMOVE=yes apt-get remove -y sudo

@ -27,7 +27,7 @@ server. Proxy is an advanced topic and it is not covered by the guide.
```sh ```sh
sudo apt-get update sudo apt-get update
sudo apt-get install -y \ sudo apt-get --no-install-recommends install -y \
apt-transport-https \ apt-transport-https \
ca-certificates \ ca-certificates \
curl \ curl \
@ -39,7 +39,7 @@ server. Proxy is an advanced topic and it is not covered by the guide.
$(lsb_release -cs) \ $(lsb_release -cs) \
stable" stable"
sudo apt-get update sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io sudo apt-get --no-install-recommends install -y docker-ce docker-ce-cli containerd.io
``` ```
- Perform [post-installation steps](https://docs.docker.com/install/linux/linux-postinstall/) - Perform [post-installation steps](https://docs.docker.com/install/linux/linux-postinstall/)
@ -57,7 +57,7 @@ server. Proxy is an advanced topic and it is not covered by the guide.
defining and running multi-container docker applications. defining and running multi-container docker applications.
```bash ```bash
sudo apt-get install -y python3-pip sudo apt-get --no-install-recommends install -y python3-pip
sudo python3 -m pip install docker-compose sudo python3 -m pip install docker-compose
``` ```
@ -65,7 +65,7 @@ server. Proxy is an advanced topic and it is not covered by the guide.
[GitHub repository](https://github.com/opencv/cvat). [GitHub repository](https://github.com/opencv/cvat).
```bash ```bash
sudo apt-get install -y git sudo apt-get --no-install-recommends install -y git
git clone https://github.com/opencv/cvat git clone https://github.com/opencv/cvat
cd cvat cd cvat
``` ```
@ -103,7 +103,7 @@ server. Proxy is an advanced topic and it is not covered by the guide.
curl https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - curl https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
sudo apt-get update sudo apt-get update
sudo apt-get install -y google-chrome-stable sudo apt-get --no-install-recommends install -y google-chrome-stable
``` ```
- Open the installed Google Chrome browser and go to [localhost:8080](http://localhost:8080). - Open the installed Google Chrome browser and go to [localhost:8080](http://localhost:8080).

@ -25,7 +25,15 @@ There are two different formats for annotation and interpolation modes at the mo
<label> <label>
<name>String: name of the label (e.g. car, person)</name> <name>String: name of the label (e.g. car, person)</name>
<attributes> <attributes>
<attribute>String: attributes for the label (e.g. @select=quality:good,bad)</attribute> <attribute>
<name>String: attribute name</name>
<mutable>Boolean: mutable (allow different values between frames)</mutable>
<input_type>String: select, checkbox, radio, number, text</input_type>
<default_value>String: default value</default_value>
<values>String: possible values, separated by newlines
ex. value 2
ex. value 3</values>
</attribute>
</attributes> </attributes>
</label> </label>
</labels> </labels>

@ -271,7 +271,7 @@ class Git:
display_name += " for images" if self._task_mode == "annotation" else " for videos" display_name += " for images" if self._task_mode == "annotation" else " for videos"
cvat_dumper = AnnotationDumper.objects.get(display_name=display_name) cvat_dumper = AnnotationDumper.objects.get(display_name=display_name)
dump_name = os.path.join(db_task.get_task_dirname(), dump_name = os.path.join(db_task.get_task_dirname(),
"git_annotation_{}.".format(timestamp) + "dump") "git_annotation_{}.xml".format(timestamp))
dump_task_data( dump_task_data(
pk=self._tid, pk=self._tid,
user=user, user=user,
@ -283,7 +283,7 @@ class Git:
ext = os.path.splitext(self._path)[1] ext = os.path.splitext(self._path)[1]
if ext == '.zip': if ext == '.zip':
subprocess.call('zip -j -r "{}" "{}"'.format(self._annotation_file, dump_name), shell=True) subprocess.run(args=['7z', 'a', self._annotation_file, dump_name])
elif ext == '.xml': elif ext == '.xml':
shutil.copyfile(dump_name, self._annotation_file) shutil.copyfile(dump_name, self._annotation_file)
else: else:

@ -132,8 +132,8 @@ def build_import_parser(parser_ctor=argparse.ArgumentParser):
help="Overwrite existing files in the save directory") help="Overwrite existing files in the save directory")
parser.add_argument('-i', '--input-path', required=True, dest='source', parser.add_argument('-i', '--input-path', required=True, dest='source',
help="Path to import project from") help="Path to import project from")
parser.add_argument('-f', '--format', required=True, parser.add_argument('-f', '--format',
help="Source project format") help="Source project format. Will try to detect, if not specified.")
parser.add_argument('extra_args', nargs=argparse.REMAINDER, parser.add_argument('extra_args', nargs=argparse.REMAINDER,
help="Additional arguments for importer (pass '-- -h' for help)") help="Additional arguments for importer (pass '-- -h' for help)")
parser.set_defaults(command=import_command) parser.set_defaults(command=import_command)
@ -164,22 +164,53 @@ def import_command(args):
if project_name is None: if project_name is None:
project_name = osp.basename(project_dir) project_name = osp.basename(project_dir)
try: env = Environment()
env = Environment() log.info("Importing project from '%s'" % args.source)
importer = env.make_importer(args.format)
except KeyError: if not args.format:
raise CliException("Importer for format '%s' is not found" % \ if args.extra_args:
args.format) raise CliException("Extra args can not be used without format")
extra_args = {} log.info("Trying to detect dataset format...")
if hasattr(importer, 'from_cmdline'):
extra_args = importer.from_cmdline(args.extra_args) matches = []
for format_name in env.importers.items:
log.debug("Checking '%s' format...", format_name)
importer = env.make_importer(format_name)
try:
match = importer.detect(args.source)
if match:
log.debug("format matched")
matches.append((format_name, importer))
except NotImplementedError:
log.debug("Format '%s' does not support auto detection.",
format_name)
if len(matches) == 0:
log.error("Failed to detect dataset format automatically. "
"Try to specify format with '-f/--format' parameter.")
return 1
elif len(matches) != 1:
log.error("Multiple formats match the dataset: %s. "
"Try to specify format with '-f/--format' parameter.",
', '.join(m[0] for m in matches))
return 2
format_name, importer = matches[0]
args.format = format_name
else:
try:
importer = env.make_importer(args.format)
if hasattr(importer, 'from_cmdline'):
extra_args = importer.from_cmdline(args.extra_args)
except KeyError:
raise CliException("Importer for format '%s' is not found" % \
args.format)
log.info("Importing project from '%s' as '%s'" % \ log.info("Importing project as '%s'" % args.format)
(args.source, args.format))
source = osp.abspath(args.source) source = osp.abspath(args.source)
project = importer(source, **extra_args) project = importer(source, **locals().get('extra_args', {}))
project.config.project_name = project_name project.config.project_name = project_name
project.config.project_dir = project_dir project.config.project_dir = project_dir

@ -743,6 +743,10 @@ class SourceExtractor(Extractor):
pass pass
class Importer: class Importer:
@classmethod
def detect(cls, path):
raise NotImplementedError()
def __call__(self, path, **extra_params): def __call__(self, path, **extra_params):
raise NotImplementedError() raise NotImplementedError()

@ -9,6 +9,7 @@ import logging as log
import os.path as osp import os.path as osp
from datumaro.components.extractor import Importer from datumaro.components.extractor import Importer
from datumaro.util.log_utils import logging_disabled
from .format import CocoTask, CocoPath from .format import CocoTask, CocoPath
@ -22,6 +23,11 @@ class CocoImporter(Importer):
CocoTask.image_info: 'coco_image_info', CocoTask.image_info: 'coco_image_info',
} }
@classmethod
def detect(cls, path):
with logging_disabled(log.WARN):
return len(cls.find_subsets(path)) != 0
def __call__(self, path, **extra_params): def __call__(self, path, **extra_params):
from datumaro.components.project import Project # cyclic import from datumaro.components.project import Project # cyclic import
project = Project() project = Project()
@ -53,7 +59,7 @@ class CocoImporter(Importer):
if osp.basename(osp.normpath(path)) != CocoPath.ANNOTATIONS_DIR: if osp.basename(osp.normpath(path)) != CocoPath.ANNOTATIONS_DIR:
path = osp.join(path, CocoPath.ANNOTATIONS_DIR) path = osp.join(path, CocoPath.ANNOTATIONS_DIR)
subset_paths += glob(osp.join(path, '*_*.json')) subset_paths += glob(osp.join(path, '*_*.json'))
subsets = defaultdict(dict) subsets = defaultdict(dict)
for subset_path in subset_paths: for subset_path in subset_paths:

@ -5,7 +5,7 @@
from collections import OrderedDict from collections import OrderedDict
import os.path as osp import os.path as osp
import xml.etree as ET from defusedxml import ElementTree
from datumaro.components.extractor import (SourceExtractor, from datumaro.components.extractor import (SourceExtractor,
DEFAULT_SUBSET_NAME, DatasetItem, DEFAULT_SUBSET_NAME, DatasetItem,
@ -64,7 +64,7 @@ class CvatExtractor(SourceExtractor):
@classmethod @classmethod
def _parse(cls, path): def _parse(cls, path):
context = ET.ElementTree.iterparse(path, events=("start", "end")) context = ElementTree.iterparse(path, events=("start", "end"))
context = iter(context) context = iter(context)
categories, frame_size = cls._parse_meta(context) categories, frame_size = cls._parse_meta(context)

@ -15,18 +15,15 @@ from .format import CvatPath
class CvatImporter(Importer): class CvatImporter(Importer):
EXTRACTOR_NAME = 'cvat' EXTRACTOR_NAME = 'cvat'
@classmethod
def detect(cls, path):
return len(cls.find_subsets(path)) != 0
def __call__(self, path, **extra_params): def __call__(self, path, **extra_params):
from datumaro.components.project import Project # cyclic import from datumaro.components.project import Project # cyclic import
project = Project() project = Project()
if path.endswith('.xml') and osp.isfile(path): subset_paths = self.find_subsets(path)
subset_paths = [path]
else:
subset_paths = glob(osp.join(path, '*.xml'))
if osp.basename(osp.normpath(path)) != CvatPath.ANNOTATIONS_DIR:
path = osp.join(path, CvatPath.ANNOTATIONS_DIR)
subset_paths += glob(osp.join(path, '*.xml'))
if len(subset_paths) == 0: if len(subset_paths) == 0:
raise Exception("Failed to find 'cvat' dataset at '%s'" % path) raise Exception("Failed to find 'cvat' dataset at '%s'" % path)
@ -46,3 +43,15 @@ class CvatImporter(Importer):
}) })
return project return project
@staticmethod
def find_subsets(path):
if path.endswith('.xml') and osp.isfile(path):
subset_paths = [path]
else:
subset_paths = glob(osp.join(path, '*.xml'))
if osp.basename(osp.normpath(path)) != CvatPath.ANNOTATIONS_DIR:
path = osp.join(path, CvatPath.ANNOTATIONS_DIR)
subset_paths += glob(osp.join(path, '*.xml'))
return subset_paths

@ -15,19 +15,15 @@ from .format import DatumaroPath
class DatumaroImporter(Importer): class DatumaroImporter(Importer):
EXTRACTOR_NAME = 'datumaro' EXTRACTOR_NAME = 'datumaro'
@classmethod
def detect(cls, path):
return len(cls.find_subsets(path)) != 0
def __call__(self, path, **extra_params): def __call__(self, path, **extra_params):
from datumaro.components.project import Project # cyclic import from datumaro.components.project import Project # cyclic import
project = Project() project = Project()
if path.endswith('.json') and osp.isfile(path): subset_paths = self.find_subsets(path)
subset_paths = [path]
else:
subset_paths = glob(osp.join(path, '*.json'))
if osp.basename(osp.normpath(path)) != DatumaroPath.ANNOTATIONS_DIR:
path = osp.join(path, DatumaroPath.ANNOTATIONS_DIR)
subset_paths += glob(osp.join(path, '*.json'))
if len(subset_paths) == 0: if len(subset_paths) == 0:
raise Exception("Failed to find 'datumaro' dataset at '%s'" % path) raise Exception("Failed to find 'datumaro' dataset at '%s'" % path)
@ -46,3 +42,15 @@ class DatumaroImporter(Importer):
}) })
return project return project
@staticmethod
def find_subsets(path):
if path.endswith('.json') and osp.isfile(path):
subset_paths = [path]
else:
subset_paths = glob(osp.join(path, '*.json'))
if osp.basename(osp.normpath(path)) != DatumaroPath.ANNOTATIONS_DIR:
path = osp.join(path, DatumaroPath.ANNOTATIONS_DIR)
subset_paths += glob(osp.join(path, '*.json'))
return subset_paths

@ -48,9 +48,12 @@ class OpenVinoLauncher(Launcher):
@staticmethod @staticmethod
def _check_instruction_set(instruction): def _check_instruction_set(instruction):
return instruction == str.strip( return instruction == str.strip(
# Let's ignore a warning from bandit about using shell=True.
# In this case it isn't a security issue and we use some
# shell features like pipes.
subprocess.check_output( subprocess.check_output(
'lscpu | grep -o "{}" | head -1'.format(instruction), shell=True 'lscpu | grep -o "{}" | head -1'.format(instruction),
).decode('utf-8') shell=True).decode('utf-8') # nosec
) )
@staticmethod @staticmethod

@ -13,15 +13,15 @@ from datumaro.components.extractor import Importer
class TfDetectionApiImporter(Importer): class TfDetectionApiImporter(Importer):
EXTRACTOR_NAME = 'tf_detection_api' EXTRACTOR_NAME = 'tf_detection_api'
@classmethod
def detect(cls, path):
return len(cls.find_subsets(path)) != 0
def __call__(self, path, **extra_params): def __call__(self, path, **extra_params):
from datumaro.components.project import Project # cyclic import from datumaro.components.project import Project # cyclic import
project = Project() project = Project()
if path.endswith('.tfrecord') and osp.isfile(path): subset_paths = self.find_subsets(path)
subset_paths = [path]
else:
subset_paths = glob(osp.join(path, '*.tfrecord'))
if len(subset_paths) == 0: if len(subset_paths) == 0:
raise Exception( raise Exception(
"Failed to find 'tf_detection_api' dataset at '%s'" % path) "Failed to find 'tf_detection_api' dataset at '%s'" % path)
@ -42,3 +42,10 @@ class TfDetectionApiImporter(Importer):
return project return project
@staticmethod
def find_subsets(path):
if path.endswith('.tfrecord') and osp.isfile(path):
subset_paths = [path]
else:
subset_paths = glob(osp.join(path, '*.tfrecord'))
return subset_paths

@ -317,6 +317,9 @@ class _Converter:
self.save_segm_lists(subset_name, segm_list) self.save_segm_lists(subset_name, segm_list)
def save_action_lists(self, subset_name, action_list): def save_action_lists(self, subset_name, action_list):
if not action_list:
return
os.makedirs(self._action_subsets_dir, exist_ok=True) os.makedirs(self._action_subsets_dir, exist_ok=True)
ann_file = osp.join(self._action_subsets_dir, subset_name + '.txt') ann_file = osp.join(self._action_subsets_dir, subset_name + '.txt')
@ -342,11 +345,11 @@ class _Converter:
(item, 1 + obj_id, 1 if presented else -1)) (item, 1 + obj_id, 1 if presented else -1))
def save_class_lists(self, subset_name, class_lists): def save_class_lists(self, subset_name, class_lists):
os.makedirs(self._cls_subsets_dir, exist_ok=True) if not class_lists:
if len(class_lists) == 0:
return return
os.makedirs(self._cls_subsets_dir, exist_ok=True)
for label in self._label_map: for label in self._label_map:
ann_file = osp.join(self._cls_subsets_dir, ann_file = osp.join(self._cls_subsets_dir,
'%s_%s.txt' % (label, subset_name)) '%s_%s.txt' % (label, subset_name))
@ -360,6 +363,9 @@ class _Converter:
f.write('%s % d\n' % (item, 1 if presented else -1)) f.write('%s % d\n' % (item, 1 if presented else -1))
def save_clsdet_lists(self, subset_name, clsdet_list): def save_clsdet_lists(self, subset_name, clsdet_list):
if not clsdet_list:
return
os.makedirs(self._cls_subsets_dir, exist_ok=True) os.makedirs(self._cls_subsets_dir, exist_ok=True)
ann_file = osp.join(self._cls_subsets_dir, subset_name + '.txt') ann_file = osp.join(self._cls_subsets_dir, subset_name + '.txt')
@ -368,6 +374,9 @@ class _Converter:
f.write('%s\n' % item) f.write('%s\n' % item)
def save_segm_lists(self, subset_name, segm_list): def save_segm_lists(self, subset_name, segm_list):
if not segm_list:
return
os.makedirs(self._segm_subsets_dir, exist_ok=True) os.makedirs(self._segm_subsets_dir, exist_ok=True)
ann_file = osp.join(self._segm_subsets_dir, subset_name + '.txt') ann_file = osp.join(self._segm_subsets_dir, subset_name + '.txt')
@ -376,6 +385,9 @@ class _Converter:
f.write('%s\n' % item) f.write('%s\n' % item)
def save_layout_lists(self, subset_name, layout_list): def save_layout_lists(self, subset_name, layout_list):
if not layout_list:
return
os.makedirs(self._layout_subsets_dir, exist_ok=True) os.makedirs(self._layout_subsets_dir, exist_ok=True)
ann_file = osp.join(self._layout_subsets_dir, subset_name + '.txt') ann_file = osp.join(self._layout_subsets_dir, subset_name + '.txt')

@ -5,16 +5,16 @@
from collections import defaultdict from collections import defaultdict
import logging as log import logging as log
import os import numpy as np
import os.path as osp import os.path as osp
from xml.etree import ElementTree as ET from defusedxml import ElementTree
from datumaro.components.extractor import (SourceExtractor, Extractor, from datumaro.components.extractor import (SourceExtractor,
DEFAULT_SUBSET_NAME, DatasetItem, DEFAULT_SUBSET_NAME, DatasetItem,
AnnotationType, Label, Mask, Bbox, CompiledMask AnnotationType, Label, Mask, Bbox, CompiledMask
) )
from datumaro.util import dir_items from datumaro.util import dir_items
from datumaro.util.image import lazy_image, Image from datumaro.util.image import Image
from datumaro.util.mask_tools import lazy_mask, invert_colormap from datumaro.util.mask_tools import lazy_mask, invert_colormap
from .format import ( from .format import (
@ -24,717 +24,276 @@ from .format import (
_inverse_inst_colormap = invert_colormap(VocInstColormap) _inverse_inst_colormap = invert_colormap(VocInstColormap)
class VocExtractor(SourceExtractor): class _VocExtractor(SourceExtractor):
class Subset(Extractor): def __init__(self, path):
def __init__(self, name, parent):
super().__init__()
self._parent = parent
self._name = name
self.items = []
def __iter__(self):
for item_id in self.items:
yield self._parent._get(item_id, self._name)
def __len__(self):
return len(self.items)
def categories(self):
return self._parent.categories()
def _load_subsets(self, subsets_dir):
dir_files = dir_items(subsets_dir, '.txt', truncate_ext=True)
subset_names = [s for s in dir_files if '_' not in s]
subsets = {}
for subset_name in subset_names:
subset_file_name = subset_name
if subset_name == DEFAULT_SUBSET_NAME:
subset_name = None
subset = __class__.Subset(subset_name, self)
subset.items = []
with open(osp.join(subsets_dir, subset_file_name + '.txt'), 'r') as f:
for line in f:
line = line.split()[0].strip()
if line:
subset.items.append(line)
subsets[subset_name] = subset
return subsets
def _load_cls_annotations(self, subsets_dir, subset_names):
subset_file_names = [n if n else DEFAULT_SUBSET_NAME
for n in subset_names]
dir_files = dir_items(subsets_dir, '.txt', truncate_ext=True)
label_annotations = defaultdict(list)
label_anno_files = [s for s in dir_files \
if '_' in s and s[s.rfind('_') + 1:] in subset_file_names]
for ann_filename in label_anno_files:
with open(osp.join(subsets_dir, ann_filename + '.txt'), 'r') as f:
label = ann_filename[:ann_filename.rfind('_')]
label_id = self._get_label_id(label)
for line in f:
item, present = line.split()
if present == '1':
label_annotations[item].append(label_id)
self._annotations[VocTask.classification] = dict(label_annotations)
def _load_det_annotations(self):
det_anno_dir = osp.join(self._path, VocPath.ANNOTATIONS_DIR)
det_anno_items = dir_items(det_anno_dir, '.xml', truncate_ext=True)
det_annotations = dict()
for ann_item in det_anno_items:
with open(osp.join(det_anno_dir, ann_item + '.xml'), 'r') as f:
ann_file_data = f.read()
det_annotations[ann_item] = ann_file_data
self._annotations[VocTask.detection] = det_annotations
def _load_categories(self):
label_map = None
label_map_path = osp.join(self._path, VocPath.LABELMAP_FILE)
if osp.isfile(label_map_path):
label_map = parse_label_map(label_map_path)
self._categories = make_voc_categories(label_map)
def __init__(self, path, task):
super().__init__() super().__init__()
assert osp.isfile(path), path
self._path = path self._path = path
self._subsets = {} self._dataset_dir = osp.dirname(osp.dirname(osp.dirname(path)))
self._categories = {}
self._annotations = {} subset = osp.splitext(osp.basename(path))[0]
self._task = task if subset == DEFAULT_SUBSET_NAME:
subset = None
self._subset = subset
self._load_categories() self._categories = self._load_categories(self._dataset_dir)
log.debug("Loaded labels: %s", ', '.join("'%s'" % l.name
for l in self._categories[AnnotationType.label].items))
self._items = self._load_subset_list(path)
def categories(self):
return self._categories
def __len__(self): def __len__(self):
length = 0 return len(self._items)
for subset in self._subsets.values():
length += len(subset)
return length
def subsets(self): def subsets(self):
return list(self._subsets) if self._subset:
return [self._subset]
return None
def get_subset(self, name): def get_subset(self, name):
return self._subsets[name] if name != self._subset:
return None
def categories(self): return self
return self._categories
def __iter__(self):
for subset in self._subsets.values():
for item in subset:
yield item
def _get(self, item_id, subset_name):
image = osp.join(self._path, VocPath.IMAGES_DIR,
item_id + VocPath.IMAGE_EXT)
det_annotations = self._annotations.get(VocTask.detection)
if det_annotations is not None:
det_annotations = det_annotations.get(item_id)
if det_annotations is not None:
root_elem = ET.fromstring(det_annotations)
height = root_elem.find('size/height')
if height is not None:
height = int(height.text)
width = root_elem.find('size/width')
if width is not None:
width = int(width.text)
if height and width:
image = Image(path=image, size=(height, width))
annotations = self._get_annotations(item_id)
return DatasetItem(annotations=annotations,
id=item_id, subset=subset_name, image=image)
def _get_label_id(self, label): def _get_label_id(self, label):
label_id, _ = self._categories[AnnotationType.label].find(label) label_id, _ = self._categories[AnnotationType.label].find(label)
if label_id is None: assert label_id is not None, label
log.debug("Unknown label '%s'. Loaded labels: %s",
label,
', '.join("'%s'" % s.name
for s in self._categories[AnnotationType.label].items))
raise Exception("Unknown label '%s'" % label)
return label_id return label_id
@staticmethod @staticmethod
def _lazy_extract_mask(mask, c): def _load_categories(dataset_path):
return lambda: mask == c label_map = None
label_map_path = osp.join(dataset_path, VocPath.LABELMAP_FILE)
if osp.isfile(label_map_path):
label_map = parse_label_map(label_map_path)
return make_voc_categories(label_map)
@staticmethod
def _load_subset_list(subset_path):
with open(subset_path) as f:
return [line.split()[0] for line in f]
class VocClassificationExtractor(_VocExtractor):
def __iter__(self):
raw_anns = self._load_annotations()
for item_id in self._items:
image = osp.join(self._dataset_dir, VocPath.IMAGES_DIR,
item_id + VocPath.IMAGE_EXT)
anns = self._parse_annotations(raw_anns, item_id)
yield DatasetItem(id=item_id, subset=self._subset,
image=image, annotations=anns)
def _load_annotations(self):
annotations = defaultdict(list)
task_dir = osp.dirname(self._path)
anno_files = [s for s in dir_items(task_dir, '.txt')
if s.endswith('_' + osp.basename(self._path))]
for ann_filename in anno_files:
with open(osp.join(task_dir, ann_filename)) as f:
label = ann_filename[:ann_filename.rfind('_')]
label_id = self._get_label_id(label)
for line in f:
item, present = line.split()
if present == '1':
annotations[item].append(label_id)
return dict(annotations)
@staticmethod
def _parse_annotations(raw_anns, item_id):
return [Label(label_id) for label_id in raw_anns.get(item_id, [])]
def _get_annotations(self, item_id): class _VocXmlExtractor(_VocExtractor):
def __init__(self, path, task):
super().__init__(path)
self._task = task
def __iter__(self):
anno_dir = osp.join(self._dataset_dir, VocPath.ANNOTATIONS_DIR)
for item_id in self._items:
image = osp.join(self._dataset_dir, VocPath.IMAGES_DIR,
item_id + VocPath.IMAGE_EXT)
anns = []
ann_file = osp.join(anno_dir, item_id + '.xml')
if osp.isfile(ann_file):
root_elem = ElementTree.parse(ann_file)
height = root_elem.find('size/height')
if height is not None:
height = int(height.text)
width = root_elem.find('size/width')
if width is not None:
width = int(width.text)
if height and width:
image = Image(path=image, size=(height, width))
anns = self._parse_annotations(root_elem)
yield DatasetItem(id=item_id, subset=self._subset,
image=image, annotations=anns)
def _parse_annotations(self, root_elem):
item_annotations = [] item_annotations = []
if self._task is VocTask.segmentation: for obj_id, object_elem in enumerate(root_elem.findall('object')):
class_mask = None obj_id += 1
segm_path = osp.join(self._path, VocPath.SEGMENTATION_DIR, attributes = {}
item_id + VocPath.SEGM_EXT) group = obj_id
if osp.isfile(segm_path):
inverse_cls_colormap = \
self._categories[AnnotationType.mask].inverse_colormap
class_mask = lazy_mask(segm_path, inverse_cls_colormap)
instances_mask = None
inst_path = osp.join(self._path, VocPath.INSTANCES_DIR,
item_id + VocPath.SEGM_EXT)
if osp.isfile(inst_path):
instances_mask = lazy_mask(inst_path, _inverse_inst_colormap)
if instances_mask is not None:
compiled_mask = CompiledMask(class_mask, instances_mask)
if class_mask is not None:
label_cat = self._categories[AnnotationType.label]
instance_labels = compiled_mask.get_instance_labels(
class_count=len(label_cat.items))
else:
instance_labels = {i: None
for i in range(compiled_mask.instance_count)}
for instance_id, label_id in instance_labels.items():
image = compiled_mask.lazy_extract(instance_id)
attributes = dict()
if label_id is not None:
actions = {a: False
for a in label_cat.items[label_id].attributes
}
attributes.update(actions)
item_annotations.append(Mask(
image=image, label=label_id,
attributes=attributes, group=instance_id
))
elif class_mask is not None:
log.warn("item '%s': has only class segmentation, "
"instance masks will not be available" % item_id)
classes = class_mask.image.unique()
for label_id in classes:
image = self._lazy_extract_mask(class_mask, label_id)
item_annotations.append(Mask(image=image, label=label_id))
cls_annotations = self._annotations.get(VocTask.classification)
if cls_annotations is not None and \
self._task is VocTask.classification:
item_labels = cls_annotations.get(item_id)
if item_labels is not None:
for label_id in item_labels:
item_annotations.append(Label(label_id))
det_annotations = self._annotations.get(VocTask.detection)
if det_annotations is not None:
det_annotations = det_annotations.get(item_id)
if det_annotations is not None:
root_elem = ET.fromstring(det_annotations)
for obj_id, object_elem in enumerate(root_elem.findall('object')):
obj_id += 1
attributes = {}
group = obj_id
obj_label_id = None obj_label_id = None
label_elem = object_elem.find('name') label_elem = object_elem.find('name')
if label_elem is not None: if label_elem is not None:
obj_label_id = self._get_label_id(label_elem.text) obj_label_id = self._get_label_id(label_elem.text)
obj_bbox = self._parse_bbox(object_elem) obj_bbox = self._parse_bbox(object_elem)
if obj_label_id is None or obj_bbox is None: if obj_label_id is None or obj_bbox is None:
continue continue
difficult_elem = object_elem.find('difficult') difficult_elem = object_elem.find('difficult')
attributes['difficult'] = difficult_elem is not None and \ attributes['difficult'] = difficult_elem is not None and \
difficult_elem.text == '1' difficult_elem.text == '1'
truncated_elem = object_elem.find('truncated') truncated_elem = object_elem.find('truncated')
attributes['truncated'] = truncated_elem is not None and \ attributes['truncated'] = truncated_elem is not None and \
truncated_elem.text == '1' truncated_elem.text == '1'
occluded_elem = object_elem.find('occluded') occluded_elem = object_elem.find('occluded')
attributes['occluded'] = occluded_elem is not None and \ attributes['occluded'] = occluded_elem is not None and \
occluded_elem.text == '1' occluded_elem.text == '1'
pose_elem = object_elem.find('pose') pose_elem = object_elem.find('pose')
if pose_elem is not None: if pose_elem is not None:
attributes['pose'] = pose_elem.text attributes['pose'] = pose_elem.text
point_elem = object_elem.find('point') point_elem = object_elem.find('point')
if point_elem is not None: if point_elem is not None:
point_x = point_elem.find('x') point_x = point_elem.find('x')
point_y = point_elem.find('y') point_y = point_elem.find('y')
point = [float(point_x.text), float(point_y.text)] point = [float(point_x.text), float(point_y.text)]
attributes['point'] = point attributes['point'] = point
actions_elem = object_elem.find('actions') actions_elem = object_elem.find('actions')
actions = {a: False actions = {a: False
for a in self._categories[AnnotationType.label] \ for a in self._categories[AnnotationType.label] \
.items[obj_label_id].attributes} .items[obj_label_id].attributes}
if actions_elem is not None: if actions_elem is not None:
for action_elem in actions_elem: for action_elem in actions_elem:
actions[action_elem.tag] = (action_elem.text == '1') actions[action_elem.tag] = (action_elem.text == '1')
for action, present in actions.items(): for action, present in actions.items():
attributes[action] = present attributes[action] = present
has_parts = False has_parts = False
for part_elem in object_elem.findall('part'): for part_elem in object_elem.findall('part'):
part = part_elem.find('name').text part = part_elem.find('name').text
part_label_id = self._get_label_id(part) part_label_id = self._get_label_id(part)
part_bbox = self._parse_bbox(part_elem) part_bbox = self._parse_bbox(part_elem)
if self._task is not VocTask.person_layout: if self._task is not VocTask.person_layout:
break break
if part_bbox is None: if part_bbox is None:
continue
has_parts = True
item_annotations.append(Bbox(*part_bbox, label=part_label_id,
group=group))
if self._task is VocTask.person_layout and not has_parts:
continue
if self._task is VocTask.action_classification and not actions:
continue continue
has_parts = True
item_annotations.append(Bbox(*part_bbox, label=part_label_id,
group=group))
if self._task is VocTask.person_layout and not has_parts:
continue
if self._task is VocTask.action_classification and not actions:
continue
item_annotations.append(Bbox(*obj_bbox, label=obj_label_id, item_annotations.append(Bbox(*obj_bbox, label=obj_label_id,
attributes=attributes, id=obj_id, group=group)) attributes=attributes, id=obj_id, group=group))
return item_annotations return item_annotations
@staticmethod @staticmethod
def _parse_bbox(object_elem): def _parse_bbox(object_elem):
bbox_elem = object_elem.find('bndbox') bbox_elem = object_elem.find('bndbox')
if bbox_elem is None:
return None
xmin = float(bbox_elem.find('xmin').text) xmin = float(bbox_elem.find('xmin').text)
xmax = float(bbox_elem.find('xmax').text) xmax = float(bbox_elem.find('xmax').text)
ymin = float(bbox_elem.find('ymin').text) ymin = float(bbox_elem.find('ymin').text)
ymax = float(bbox_elem.find('ymax').text) ymax = float(bbox_elem.find('ymax').text)
return [xmin, ymin, xmax - xmin, ymax - ymin] return [xmin, ymin, xmax - xmin, ymax - ymin]
class VocClassificationExtractor(VocExtractor): class VocDetectionExtractor(_VocXmlExtractor):
def __init__(self, path):
super().__init__(path, task=VocTask.classification)
subsets_dir = osp.join(path, VocPath.SUBSETS_DIR, 'Main')
subsets = self._load_subsets(subsets_dir)
self._subsets = subsets
self._load_cls_annotations(subsets_dir, subsets)
class VocDetectionExtractor(VocExtractor):
def __init__(self, path): def __init__(self, path):
super().__init__(path, task=VocTask.detection) super().__init__(path, task=VocTask.detection)
subsets_dir = osp.join(path, VocPath.SUBSETS_DIR, 'Main') class VocLayoutExtractor(_VocXmlExtractor):
subsets = self._load_subsets(subsets_dir)
self._subsets = subsets
self._load_det_annotations()
class VocSegmentationExtractor(VocExtractor):
def __init__(self, path):
super().__init__(path, task=VocTask.segmentation)
subsets_dir = osp.join(path, VocPath.SUBSETS_DIR, 'Segmentation')
subsets = self._load_subsets(subsets_dir)
self._subsets = subsets
class VocLayoutExtractor(VocExtractor):
def __init__(self, path): def __init__(self, path):
super().__init__(path, task=VocTask.person_layout) super().__init__(path, task=VocTask.person_layout)
subsets_dir = osp.join(path, VocPath.SUBSETS_DIR, 'Layout') class VocActionExtractor(_VocXmlExtractor):
subsets = self._load_subsets(subsets_dir)
self._subsets = subsets
self._load_det_annotations()
class VocActionExtractor(VocExtractor):
def __init__(self, path): def __init__(self, path):
super().__init__(path, task=VocTask.action_classification) super().__init__(path, task=VocTask.action_classification)
subsets_dir = osp.join(path, VocPath.SUBSETS_DIR, 'Action') class VocSegmentationExtractor(_VocExtractor):
subsets = self._load_subsets(subsets_dir)
self._subsets = subsets
self._load_det_annotations()
class VocResultsExtractor(Extractor):
class Subset(Extractor):
def __init__(self, name, parent):
super().__init__()
self._parent = parent
self._name = name
self.items = []
def __iter__(self):
for item in self.items:
yield self._parent._get(item, self._name)
def __len__(self):
return len(self.items)
def categories(self):
return self._parent.categories()
_SUPPORTED_TASKS = {
VocTask.classification: {
'dir': 'Main',
'mark': 'cls',
'ext': '.txt',
'path' : ['%(comp)s_cls_%(subset)s_%(label)s.txt'],
'comp': ['comp1', 'comp2'],
},
VocTask.detection: {
'dir': 'Main',
'mark': 'det',
'ext': '.txt',
'path': ['%(comp)s_det_%(subset)s_%(label)s.txt'],
'comp': ['comp3', 'comp4'],
},
VocTask.segmentation: {
'dir': 'Segmentation',
'mark': ['cls', 'inst'],
'ext': '.png',
'path': ['%(comp)s_%(subset)s_cls', '%(item)s.png'],
'comp': ['comp5', 'comp6'],
},
VocTask.person_layout: {
'dir': 'Layout',
'mark': 'layout',
'ext': '.xml',
'path': ['%(comp)s_layout_%(subset)s.xml'],
'comp': ['comp7', 'comp8'],
},
VocTask.action_classification: {
'dir': 'Action',
'mark': 'action',
'ext': '.txt',
'path': ['%(comp)s_action_%(subset)s_%(label)s.txt'],
'comp': ['comp9', 'comp10'],
},
}
def _parse_txt_ann(self, path, subsets, annotations, task):
task_desc = self._SUPPORTED_TASKS[task]
task_dir = osp.join(path, task_desc['dir'])
ann_ext = task_desc['ext']
if not osp.isdir(task_dir):
return
ann_files = dir_items(task_dir, ann_ext, truncate_ext=True)
for ann_file in ann_files:
ann_parts = filter(None, ann_file.strip().split('_'))
if len(ann_parts) != 4:
continue
_, mark, subset_name, label = ann_parts
if mark != task_desc['mark']:
continue
label_id = self._get_label_id(label)
anns = defaultdict(list)
with open(osp.join(task_dir, ann_file + ann_ext), 'r') as f:
for line in f:
line_parts = line.split()
item = line_parts[0]
anns[item].append((label_id, *line_parts[1:]))
subset = VocResultsExtractor.Subset(subset_name, self)
subset.items = list(anns)
subsets[subset_name] = subset
annotations[subset_name] = dict(anns)
def _parse_classification(self, path, subsets, annotations):
self._parse_txt_ann(path, subsets, annotations,
VocTask.classification)
def _parse_detection(self, path, subsets, annotations):
self._parse_txt_ann(path, subsets, annotations,
VocTask.detection)
def _parse_action(self, path, subsets, annotations):
self._parse_txt_ann(path, subsets, annotations,
VocTask.action_classification)
def _load_categories(self):
label_map = None
label_map_path = osp.join(self._path, VocPath.LABELMAP_FILE)
if osp.isfile(label_map_path):
label_map = parse_label_map(label_map_path)
self._categories = make_voc_categories(label_map)
def _get_label_id(self, label):
label_id = self._categories[AnnotationType.label].find(label)
assert label_id is not None
return label_id
def __init__(self, path):
super().__init__()
self._path = path
self._subsets = {}
self._annotations = {}
self._load_categories()
def __len__(self):
length = 0
for subset in self._subsets.values():
length += len(subset)
return length
def subsets(self):
return list(self._subsets)
def get_subset(self, name):
return self._subsets[name]
def categories(self):
return self._categories
def __iter__(self): def __iter__(self):
for subset in self._subsets.values(): for item_id in self._items:
for item in subset: image = osp.join(self._dataset_dir, VocPath.IMAGES_DIR,
yield item item_id + VocPath.IMAGE_EXT)
anns = self._load_annotations(item_id)
def _get(self, item, subset_name): yield DatasetItem(id=item_id, subset=self._subset,
image = None image=image, annotations=anns)
image_path = osp.join(self._path, VocPath.IMAGES_DIR,
item + VocPath.IMAGE_EXT)
if osp.isfile(image_path):
image = lazy_image(image_path)
annotations = self._get_annotations(item, subset_name)
return DatasetItem(annotations=annotations,
id=item, subset=subset_name, image=image)
def _get_annotations(self, item, subset_name):
raise NotImplementedError()
class VocComp_1_2_Extractor(VocResultsExtractor):
def __init__(self, path):
super().__init__(path)
subsets = {}
annotations = defaultdict(dict)
self._parse_classification(path, subsets, annotations)
self._subsets = subsets
self._annotations = dict(annotations)
def _get_annotations(self, item, subset_name):
annotations = []
cls_ann = self._annotations[subset_name].get(item)
if cls_ann is not None:
for desc in cls_ann:
label_id, conf = desc
annotations.append(Label(
int(label_id),
attributes={ 'score': float(conf) }
))
return annotations
class VocComp_3_4_Extractor(VocResultsExtractor):
def __init__(self, path):
super().__init__(path)
subsets = {}
annotations = defaultdict(dict)
self._parse_detection(path, subsets, annotations)
self._subsets = subsets
self._annotations = dict(annotations)
def _get_annotations(self, item, subset_name):
annotations = []
det_ann = self._annotations[subset_name].get(item)
if det_ann is not None:
for desc in det_ann:
label_id, conf, left, top, right, bottom = desc
annotations.append(Bbox(
x=float(left), y=float(top),
w=float(right) - float(left), h=float(bottom) - float(top),
label=int(label_id),
attributes={ 'score': float(conf) }
))
return annotations
class VocComp_5_6_Extractor(VocResultsExtractor):
def __init__(self, path):
super().__init__(path)
subsets = {}
annotations = defaultdict(dict)
task_dir = osp.join(path, 'Segmentation')
if not osp.isdir(task_dir):
return
ann_files = os.listdir(task_dir)
for ann_dir in ann_files:
ann_parts = filter(None, ann_dir.strip().split('_'))
if len(ann_parts) != 4:
continue
_, subset_name, mark = ann_parts
if mark not in ['cls', 'inst']:
continue
item_dir = osp.join(task_dir, ann_dir)
items = dir_items(item_dir, '.png', truncate_ext=True)
items = { name: osp.join(item_dir, item + '.png') \
for name, item in items }
subset = VocResultsExtractor.Subset(subset_name, self)
subset.items = list(items)
subsets[subset_name] = subset
annotations[subset_name][mark] = items
self._subsets = subsets @staticmethod
self._annotations = dict(annotations) def _lazy_extract_mask(mask, c):
return lambda: mask == c
def _get_annotations(self, item, subset_name): def _load_annotations(self, item_id):
annotations = [] item_annotations = []
segm_ann = self._annotations[subset_name] class_mask = None
cls_image_path = segm_ann.get(item) segm_path = osp.join(self._dataset_dir, VocPath.SEGMENTATION_DIR,
if cls_image_path and osp.isfile(cls_image_path): item_id + VocPath.SEGM_EXT)
if osp.isfile(segm_path):
inverse_cls_colormap = \ inverse_cls_colormap = \
self._categories[AnnotationType.mask].inverse_colormap self._categories[AnnotationType.mask].inverse_colormap
annotations.append(Mask( class_mask = lazy_mask(segm_path, inverse_cls_colormap)
image=lazy_mask(cls_image_path, inverse_cls_colormap),
attributes={ 'class': True }
))
inst_ann = self._annotations[subset_name]
inst_image_path = inst_ann.get(item)
if inst_image_path and osp.isfile(inst_image_path):
annotations.append(Mask(
image=lazy_mask(inst_image_path, _inverse_inst_colormap),
attributes={ 'instances': True }
))
return annotations
class VocComp_7_8_Extractor(VocResultsExtractor):
def __init__(self, path):
super().__init__(path)
subsets = {} instances_mask = None
annotations = defaultdict(dict) inst_path = osp.join(self._dataset_dir, VocPath.INSTANCES_DIR,
item_id + VocPath.SEGM_EXT)
if osp.isfile(inst_path):
instances_mask = lazy_mask(inst_path, _inverse_inst_colormap)
task = VocTask.person_layout if instances_mask is not None:
task_desc = self._SUPPORTED_TASKS[task] compiled_mask = CompiledMask(class_mask, instances_mask)
task_dir = osp.join(path, task_desc['dir'])
if not osp.isdir(task_dir):
return
ann_ext = task_desc['ext'] if class_mask is not None:
ann_files = dir_items(task_dir, ann_ext, truncate_ext=True) label_cat = self._categories[AnnotationType.label]
instance_labels = compiled_mask.get_instance_labels(
class_count=len(label_cat.items))
else:
instance_labels = {i: None
for i in range(compiled_mask.instance_count)}
for ann_file in ann_files: for instance_id, label_id in instance_labels.items():
ann_parts = filter(None, ann_file.strip().split('_')) image = compiled_mask.lazy_extract(instance_id)
if len(ann_parts) != 4:
continue
_, mark, subset_name, _ = ann_parts
if mark != task_desc['mark']:
continue
layouts = {} attributes = {}
root = ET.parse(osp.join(task_dir, ann_file + ann_ext)) if label_id is not None:
root_elem = root.getroot() actions = {a: False
for layout_elem in root_elem.findall('layout'): for a in label_cat.items[label_id].attributes
item = layout_elem.find('image').text
obj_id = int(layout_elem.find('object').text)
conf = float(layout_elem.find('confidence').text)
parts = []
for part_elem in layout_elem.findall('part'):
label_id = self._get_label_id(part_elem.find('class').text)
bbox_elem = part_elem.find('bndbox')
xmin = float(bbox_elem.find('xmin').text)
xmax = float(bbox_elem.find('xmax').text)
ymin = float(bbox_elem.find('ymin').text)
ymax = float(bbox_elem.find('ymax').text)
bbox = [xmin, ymin, xmax - xmin, ymax - ymin]
parts.append((label_id, bbox))
layouts[item] = [obj_id, conf, parts]
subset = VocResultsExtractor.Subset(subset_name, self)
subset.items = list(layouts)
subsets[subset_name] = subset
annotations[subset_name] = layouts
self._subsets = subsets
self._annotations = dict(annotations)
def _get_annotations(self, item, subset_name):
annotations = []
layout_ann = self._annotations[subset_name].get(item)
if layout_ann is not None:
for desc in layout_ann:
obj_id, conf, parts = desc
attributes = {
'score': conf,
'object_id': obj_id,
}
for part in parts:
label_id, bbox = part
annotations.append(Bbox(
*bbox, label=label_id,
attributes=attributes))
return annotations
class VocComp_9_10_Extractor(VocResultsExtractor):
def __init__(self, path):
super().__init__(path)
subsets = {}
annotations = defaultdict(dict)
self._parse_action(path, subsets, annotations)
self._subsets = subsets
self._annotations = dict(annotations)
def _load_categories(self):
from collections import OrderedDict
from .format import VocAction
label_map = OrderedDict((a.name, [[], [], []]) for a in VocAction)
self._categories = make_voc_categories(label_map)
def _get_annotations(self, item, subset_name):
annotations = []
action_ann = self._annotations[subset_name].get(item)
if action_ann is not None:
for desc in action_ann:
action_id, obj_id, conf = desc
annotations.append(Label(
action_id,
attributes={
'score': conf,
'object_id': int(obj_id),
} }
attributes.update(actions)
item_annotations.append(Mask(
image=image, label=label_id,
attributes=attributes, group=instance_id
)) ))
elif class_mask is not None:
log.warn("item '%s': has only class segmentation, "
"instance masks will not be available" % item_id)
class_mask = class_mask()
classes = np.unique(class_mask)
for label_id in classes:
image = self._lazy_extract_mask(class_mask, label_id)
item_annotations.append(Mask(image=image, label=label_id))
return annotations return item_annotations

@ -3,11 +3,10 @@
# #
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
import os from glob import glob
import os.path as osp import os.path as osp
from datumaro.components.extractor import Importer from datumaro.components.extractor import Importer
from datumaro.util import find
from .format import VocTask, VocPath from .format import VocTask, VocPath
@ -21,61 +20,37 @@ class VocImporter(Importer):
(VocTask.action_classification, 'voc_action', 'Action'), (VocTask.action_classification, 'voc_action', 'Action'),
] ]
@classmethod
def detect(cls, path):
return len(cls.find_subsets(path)) != 0
def __call__(self, path, **extra_params): def __call__(self, path, **extra_params):
from datumaro.components.project import Project # cyclic import from datumaro.components.project import Project # cyclic import
project = Project() project = Project()
for task, extractor_type, task_dir in self._TASKS: subset_paths = self.find_subsets(path)
task_dir = osp.join(path, VocPath.SUBSETS_DIR, task_dir) if len(subset_paths) == 0:
if not osp.isdir(task_dir): raise Exception("Failed to find 'voc' dataset at '%s'" % path)
continue
project.add_source(task.name, { for task, extractor_type, subset_path in subset_paths:
'url': path, project.add_source('%s-%s' %
(task.name, osp.splitext(osp.basename(subset_path))[0]),
{
'url': subset_path,
'format': extractor_type, 'format': extractor_type,
'options': dict(extra_params), 'options': dict(extra_params),
}) })
if len(project.config.sources) == 0:
raise Exception("Failed to find 'voc' dataset at '%s'" % path)
return project return project
@staticmethod
class VocResultsImporter: def find_subsets(path):
_TASKS = [ subset_paths = []
('comp1', 'voc_comp_1_2', 'Main'), for task, extractor_type, task_dir in __class__._TASKS:
('comp2', 'voc_comp_1_2', 'Main'), task_dir = osp.join(path, VocPath.SUBSETS_DIR, task_dir)
('comp3', 'voc_comp_3_4', 'Main'),
('comp4', 'voc_comp_3_4', 'Main'),
('comp5', 'voc_comp_5_6', 'Segmentation'),
('comp6', 'voc_comp_5_6', 'Segmentation'),
('comp7', 'voc_comp_7_8', 'Layout'),
('comp8', 'voc_comp_7_8', 'Layout'),
('comp9', 'voc_comp_9_10', 'Action'),
('comp10', 'voc_comp_9_10', 'Action'),
]
def __call__(self, path, **extra_params):
from datumaro.components.project import Project # cyclic import
project = Project()
for task_name, extractor_type, task_dir in self._TASKS:
task_dir = osp.join(path, task_dir)
if not osp.isdir(task_dir): if not osp.isdir(task_dir):
continue continue
dir_items = os.listdir(task_dir) task_subsets = [p for p in glob(osp.join(task_dir, '*.txt'))
if not find(dir_items, lambda x: x == task_name): if '_' not in osp.basename(p)]
continue subset_paths += [(task, extractor_type, p) for p in task_subsets]
return subset_paths
project.add_source(task_name, {
'url': task_dir,
'format': extractor_type,
'options': dict(extra_params),
})
if len(project.config.sources) == 0:
raise Exception("Failed to find 'voc_results' dataset at '%s'" % \
path)
return project

@ -11,16 +11,16 @@ from datumaro.components.extractor import Importer
class YoloImporter(Importer): class YoloImporter(Importer):
@classmethod
def detect(cls, path):
return len(cls.find_configs(path)) != 0
def __call__(self, path, **extra_params): def __call__(self, path, **extra_params):
from datumaro.components.project import Project # cyclic import from datumaro.components.project import Project # cyclic import
project = Project() project = Project()
if path.endswith('.data') and osp.isfile(path): config_paths = self.find_configs(path)
config_paths = [path] if len(config_paths) == 0:
else:
config_paths = glob(osp.join(path, '*.data'))
if not osp.exists(path) or not config_paths:
raise Exception("Failed to find 'yolo' dataset at '%s'" % path) raise Exception("Failed to find 'yolo' dataset at '%s'" % path)
for config_path in config_paths: for config_path in config_paths:
@ -35,4 +35,12 @@ class YoloImporter(Importer):
'options': dict(extra_params), 'options': dict(extra_params),
}) })
return project return project
@staticmethod
def find_configs(path):
if path.endswith('.data') and osp.isfile(path):
config_paths = [path]
else:
config_paths = glob(osp.join(path, '*.data'))
return config_paths

@ -0,0 +1,16 @@
# Copyright (C) 2020 Intel Corporation
#
# SPDX-License-Identifier: MIT
from contextlib import contextmanager
import logging
@contextmanager
def logging_disabled(max_level=logging.CRITICAL):
previous_level = logging.root.manager.disable
logging.disable(max_level)
try:
yield
finally:
logging.disable(previous_level)

@ -1,4 +1,5 @@
Cython>=0.27.3 # include before pycocotools Cython>=0.27.3 # include before pycocotools
defusedxml>=0.6.0
GitPython>=3.0.8 GitPython>=3.0.8
lxml>=4.4.1 lxml>=4.4.1
matplotlib<3.1 # 3.1+ requires python3.6, but we have 3.5 in cvat matplotlib<3.1 # 3.1+ requires python3.6, but we have 3.5 in cvat

@ -48,14 +48,15 @@ setuptools.setup(
], ],
python_requires='>=3.5', python_requires='>=3.5',
install_requires=[ install_requires=[
'defusedxml',
'GitPython', 'GitPython',
'lxml', 'lxml',
'matplotlib', 'matplotlib',
'numpy', 'numpy',
'opencv-python', 'opencv-python',
'Pillow', 'Pillow',
'PyYAML',
'pycocotools', 'pycocotools',
'PyYAML',
'scikit-image', 'scikit-image',
'tensorboardX', 'tensorboardX',
], ],

@ -136,6 +136,12 @@ class CocoImporterTest(TestCase):
compare_datasets(self, DstExtractor(), dataset) compare_datasets(self, DstExtractor(), dataset)
def test_can_detect(self):
with TestDir() as test_dir:
self.COCO_dataset_generate(test_dir)
self.assertTrue(CocoImporter.detect(test_dir))
class CocoConverterTest(TestCase): class CocoConverterTest(TestCase):
def _test_save_and_load(self, source_dataset, converter, test_dir, def _test_save_and_load(self, source_dataset, converter, test_dir,
target_dataset=None, importer_args=None): target_dataset=None, importer_args=None):

@ -16,88 +16,94 @@ from datumaro.util.image import save_image, Image
from datumaro.util.test_utils import TestDir, compare_datasets from datumaro.util.test_utils import TestDir, compare_datasets
class CvatExtractorTest(TestCase): def generate_dummy_cvat(path):
@staticmethod images_dir = osp.join(path, CvatPath.IMAGES_DIR)
def generate_dummy_cvat(path): anno_dir = osp.join(path, CvatPath.ANNOTATIONS_DIR)
images_dir = osp.join(path, CvatPath.IMAGES_DIR)
anno_dir = osp.join(path, CvatPath.ANNOTATIONS_DIR) os.makedirs(images_dir)
os.makedirs(anno_dir)
os.makedirs(images_dir)
os.makedirs(anno_dir) root_elem = ET.Element('annotations')
ET.SubElement(root_elem, 'version').text = '1.1'
root_elem = ET.Element('annotations')
ET.SubElement(root_elem, 'version').text = '1.1' meta_elem = ET.SubElement(root_elem, 'meta')
task_elem = ET.SubElement(meta_elem, 'task')
meta_elem = ET.SubElement(root_elem, 'meta') ET.SubElement(task_elem, 'z_order').text = 'True'
task_elem = ET.SubElement(meta_elem, 'task') ET.SubElement(task_elem, 'mode').text = 'interpolation'
ET.SubElement(task_elem, 'z_order').text = 'True'
ET.SubElement(task_elem, 'mode').text = 'interpolation' labels_elem = ET.SubElement(task_elem, 'labels')
labels_elem = ET.SubElement(task_elem, 'labels') label1_elem = ET.SubElement(labels_elem, 'label')
ET.SubElement(label1_elem, 'name').text = 'label1'
label1_elem = ET.SubElement(labels_elem, 'label') label1_attrs_elem = ET.SubElement(label1_elem, 'attributes')
ET.SubElement(label1_elem, 'name').text = 'label1'
label1_attrs_elem = ET.SubElement(label1_elem, 'attributes') label1_a1_elem = ET.SubElement(label1_attrs_elem, 'attribute')
ET.SubElement(label1_a1_elem, 'name').text = 'a1'
label1_a1_elem = ET.SubElement(label1_attrs_elem, 'attribute') ET.SubElement(label1_a1_elem, 'input_type').text = 'checkbox'
ET.SubElement(label1_a1_elem, 'name').text = 'a1' ET.SubElement(label1_a1_elem, 'default_value').text = 'false'
ET.SubElement(label1_a1_elem, 'input_type').text = 'checkbox' ET.SubElement(label1_a1_elem, 'values').text = 'false\ntrue'
ET.SubElement(label1_a1_elem, 'default_value').text = 'false'
ET.SubElement(label1_a1_elem, 'values').text = 'false\ntrue' label1_a2_elem = ET.SubElement(label1_attrs_elem, 'attribute')
ET.SubElement(label1_a2_elem, 'name').text = 'a2'
label1_a2_elem = ET.SubElement(label1_attrs_elem, 'attribute') ET.SubElement(label1_a2_elem, 'input_type').text = 'radio'
ET.SubElement(label1_a2_elem, 'name').text = 'a2' ET.SubElement(label1_a2_elem, 'default_value').text = 'v1'
ET.SubElement(label1_a2_elem, 'input_type').text = 'radio' ET.SubElement(label1_a2_elem, 'values').text = 'v1\nv2\nv3'
ET.SubElement(label1_a2_elem, 'default_value').text = 'v1'
ET.SubElement(label1_a2_elem, 'values').text = 'v1\nv2\nv3' label2_elem = ET.SubElement(labels_elem, 'label')
ET.SubElement(label2_elem, 'name').text = 'label2'
label2_elem = ET.SubElement(labels_elem, 'label')
ET.SubElement(label2_elem, 'name').text = 'label2' # item 1
save_image(osp.join(images_dir, 'img0.jpg'), np.ones((8, 8, 3)))
# item 1 item1_elem = ET.SubElement(root_elem, 'image')
save_image(osp.join(images_dir, 'img0.jpg'), np.ones((8, 8, 3))) item1_elem.attrib.update({
item1_elem = ET.SubElement(root_elem, 'image') 'id': '0', 'name': 'img0', 'width': '8', 'height': '8'
item1_elem.attrib.update({ })
'id': '0', 'name': 'img0', 'width': '8', 'height': '8'
}) item1_ann1_elem = ET.SubElement(item1_elem, 'box')
item1_ann1_elem.attrib.update({
item1_ann1_elem = ET.SubElement(item1_elem, 'box') 'label': 'label1', 'occluded': '1', 'z_order': '1',
item1_ann1_elem.attrib.update({ 'xtl': '0', 'ytl': '2', 'xbr': '4', 'ybr': '4'
'label': 'label1', 'occluded': '1', 'z_order': '1', })
'xtl': '0', 'ytl': '2', 'xbr': '4', 'ybr': '4' item1_ann1_a1_elem = ET.SubElement(item1_ann1_elem, 'attribute')
}) item1_ann1_a1_elem.attrib['name'] = 'a1'
item1_ann1_a1_elem = ET.SubElement(item1_ann1_elem, 'attribute') item1_ann1_a1_elem.text = 'true'
item1_ann1_a1_elem.attrib['name'] = 'a1' item1_ann1_a2_elem = ET.SubElement(item1_ann1_elem, 'attribute')
item1_ann1_a1_elem.text = 'true' item1_ann1_a2_elem.attrib['name'] = 'a2'
item1_ann1_a2_elem = ET.SubElement(item1_ann1_elem, 'attribute') item1_ann1_a2_elem.text = 'v3'
item1_ann1_a2_elem.attrib['name'] = 'a2'
item1_ann1_a2_elem.text = 'v3' item1_ann2_elem = ET.SubElement(item1_elem, 'polyline')
item1_ann2_elem.attrib.update({
item1_ann2_elem = ET.SubElement(item1_elem, 'polyline') 'label': '', 'points': '1.0,2;3,4;5,6;7,8'
item1_ann2_elem.attrib.update({ })
'label': '', 'points': '1.0,2;3,4;5,6;7,8'
}) # item 2
save_image(osp.join(images_dir, 'img1.jpg'), np.ones((10, 10, 3)))
# item 2 item2_elem = ET.SubElement(root_elem, 'image')
save_image(osp.join(images_dir, 'img1.jpg'), np.ones((10, 10, 3))) item2_elem.attrib.update({
item2_elem = ET.SubElement(root_elem, 'image') 'id': '1', 'name': 'img1', 'width': '10', 'height': '10'
item2_elem.attrib.update({ })
'id': '1', 'name': 'img1', 'width': '10', 'height': '10'
}) item2_ann1_elem = ET.SubElement(item2_elem, 'polygon')
item2_ann1_elem.attrib.update({
item2_ann1_elem = ET.SubElement(item2_elem, 'polygon') 'label': '', 'points': '1,2;3,4;6,5', 'z_order': '1',
item2_ann1_elem.attrib.update({ })
'label': '', 'points': '1,2;3,4;6,5', 'z_order': '1',
}) item2_ann2_elem = ET.SubElement(item2_elem, 'points')
item2_ann2_elem.attrib.update({
item2_ann2_elem = ET.SubElement(item2_elem, 'points') 'label': 'label2', 'points': '1,2;3,4;5,6', 'z_order': '2',
item2_ann2_elem.attrib.update({ })
'label': 'label2', 'points': '1,2;3,4;5,6', 'z_order': '2',
}) with open(osp.join(anno_dir, 'train.xml'), 'w') as f:
f.write(ET.tostring(root_elem, encoding='unicode'))
with open(osp.join(anno_dir, 'train.xml'), 'w') as f:
f.write(ET.tostring(root_elem, encoding='unicode')) class CvatImporterTest(TestCase):
def test_can_detect(self):
with TestDir() as test_dir:
generate_dummy_cvat(test_dir)
self.assertTrue(CvatImporter.detect(test_dir))
class CvatExtractorTest(TestCase):
def test_can_load(self): def test_can_load(self):
class TestExtractor(Extractor): class TestExtractor(Extractor):
def __iter__(self): def __iter__(self):
@ -130,7 +136,7 @@ class CvatExtractorTest(TestCase):
} }
with TestDir() as test_dir: with TestDir() as test_dir:
self.generate_dummy_cvat(test_dir) generate_dummy_cvat(test_dir)
source_dataset = TestExtractor() source_dataset = TestExtractor()
parsed_dataset = CvatImporter()(test_dir).make_dataset() parsed_dataset = CvatImporter()(test_dir).make_dataset()

@ -8,6 +8,7 @@ from datumaro.components.extractor import (Extractor, DatasetItem,
PolyLine, Bbox, Caption, PolyLine, Bbox, Caption,
LabelCategories, MaskCategories, PointsCategories LabelCategories, MaskCategories, PointsCategories
) )
from datumaro.plugins.datumaro_format.importer import DatumaroImporter
from datumaro.plugins.datumaro_format.converter import DatumaroConverter from datumaro.plugins.datumaro_format.converter import DatumaroConverter
from datumaro.util.mask_tools import generate_colormap from datumaro.util.mask_tools import generate_colormap
from datumaro.util.image import Image from datumaro.util.image import Image
@ -98,4 +99,10 @@ class DatumaroConverterTest(TestCase):
self.assertEqual( self.assertEqual(
source_dataset.categories(), source_dataset.categories(),
parsed_dataset.categories()) parsed_dataset.categories())
def test_can_detect(self):
with TestDir() as test_dir:
DatumaroConverter()(self.TestExtractor(), save_dir=test_dir)
self.assertTrue(DatumaroImporter.detect(test_dir))

@ -170,3 +170,32 @@ class TfrecordConverterTest(TestCase):
parsed = TfDetectionApiExtractor._parse_labelmap(text) parsed = TfDetectionApiExtractor._parse_labelmap(text)
self.assertEqual(expected, parsed) self.assertEqual(expected, parsed)
class TfrecordImporterTest(TestCase):
def test_can_detect(self):
class TestExtractor(Extractor):
def __iter__(self):
return iter([
DatasetItem(id=1, subset='train',
image=np.ones((16, 16, 3)),
annotations=[
Bbox(0, 4, 4, 8, label=2),
]
),
])
def categories(self):
label_cat = LabelCategories()
for label in range(10):
label_cat.add('label_' + str(label))
return {
AnnotationType.label: label_cat,
}
def generate_dummy_tfrecord(path):
TfDetectionApiConverter()(TestExtractor(), save_dir=path)
with TestDir() as test_dir:
generate_dummy_tfrecord(test_dir)
self.assertTrue(TfDetectionApiImporter.detect(test_dir))

@ -189,14 +189,14 @@ class VocExtractorTest(TestCase):
for l in VOC.VocLabel if l.value % 2 == 1 for l in VOC.VocLabel if l.value % 2 == 1
] ]
), ),
DatasetItem(id='2007_000002', subset='test')
]) ])
with TestDir() as test_dir: with TestDir() as test_dir:
generate_dummy_voc(test_dir) generate_dummy_voc(test_dir)
parsed_dataset = VocClassificationExtractor(test_dir)
compare_datasets(self, DstExtractor(), parsed_dataset) parsed_train = VocClassificationExtractor(
osp.join(test_dir, 'ImageSets', 'Main', 'train.txt'))
compare_datasets(self, DstExtractor(), parsed_train)
def test_can_load_voc_det(self): def test_can_load_voc_det(self):
class DstExtractor(TestExtractorBase): class DstExtractor(TestExtractorBase):
@ -229,14 +229,13 @@ class VocExtractorTest(TestCase):
), ),
] ]
), ),
DatasetItem(id='2007_000002', subset='test')
]) ])
with TestDir() as test_dir: with TestDir() as test_dir:
generate_dummy_voc(test_dir) generate_dummy_voc(test_dir)
parsed_dataset = VocDetectionExtractor(test_dir) parsed_train = VocDetectionExtractor(
compare_datasets(self, DstExtractor(), parsed_dataset) osp.join(test_dir, 'ImageSets', 'Main', 'train.txt'))
compare_datasets(self, DstExtractor(), parsed_train)
def test_can_load_voc_segm(self): def test_can_load_voc_segm(self):
class DstExtractor(TestExtractorBase): class DstExtractor(TestExtractorBase):
@ -250,14 +249,13 @@ class VocExtractorTest(TestCase):
), ),
] ]
), ),
DatasetItem(id='2007_000002', subset='test')
]) ])
with TestDir() as test_dir: with TestDir() as test_dir:
generate_dummy_voc(test_dir) generate_dummy_voc(test_dir)
parsed_dataset = VocSegmentationExtractor(test_dir) parsed_train = VocSegmentationExtractor(
compare_datasets(self, DstExtractor(), parsed_dataset) osp.join(test_dir, 'ImageSets', 'Segmentation', 'train.txt'))
compare_datasets(self, DstExtractor(), parsed_train)
def test_can_load_voc_layout(self): def test_can_load_voc_layout(self):
class DstExtractor(TestExtractorBase): class DstExtractor(TestExtractorBase):
@ -285,14 +283,13 @@ class VocExtractorTest(TestCase):
) )
] ]
), ),
DatasetItem(id='2007_000002', subset='test')
]) ])
with TestDir() as test_dir: with TestDir() as test_dir:
generate_dummy_voc(test_dir) generate_dummy_voc(test_dir)
parsed_dataset = VocLayoutExtractor(test_dir) parsed_train = VocLayoutExtractor(
compare_datasets(self, DstExtractor(), parsed_dataset) osp.join(test_dir, 'ImageSets', 'Layout', 'train.txt'))
compare_datasets(self, DstExtractor(), parsed_train)
def test_can_load_voc_action(self): def test_can_load_voc_action(self):
class DstExtractor(TestExtractorBase): class DstExtractor(TestExtractorBase):
@ -316,14 +313,13 @@ class VocExtractorTest(TestCase):
), ),
] ]
), ),
DatasetItem(id='2007_000002', subset='test')
]) ])
with TestDir() as test_dir: with TestDir() as test_dir:
generate_dummy_voc(test_dir) generate_dummy_voc(test_dir)
parsed_dataset = VocActionExtractor(test_dir) parsed_train = VocActionExtractor(
compare_datasets(self, DstExtractor(), parsed_dataset) osp.join(test_dir, 'ImageSets', 'Action', 'train.txt'))
compare_datasets(self, DstExtractor(), parsed_train)
class VocConverterTest(TestCase): class VocConverterTest(TestCase):
def _test_save_and_load(self, source_dataset, converter, test_dir, def _test_save_and_load(self, source_dataset, converter, test_dir,
@ -757,16 +753,26 @@ class VocImportTest(TestCase):
dataset = Project.import_from(test_dir, 'voc').make_dataset() dataset = Project.import_from(test_dir, 'voc').make_dataset()
self.assertEqual(len(VOC.VocTask), len(dataset.sources)) self.assertEqual(len(VOC.VocTask) * len(subsets),
len(dataset.sources))
self.assertEqual(set(subsets), set(dataset.subsets())) self.assertEqual(set(subsets), set(dataset.subsets()))
self.assertEqual( self.assertEqual(
sum([len(s) for _, s in subsets.items()]), sum([len(s) for _, s in subsets.items()]),
len(dataset)) len(dataset))
def test_can_detect_voc(self):
with TestDir() as test_dir:
generate_dummy_voc(test_dir)
dataset_found = VocImporter.detect(test_dir)
self.assertTrue(dataset_found)
class VocFormatTest(TestCase): class VocFormatTest(TestCase):
def test_can_write_and_parse_labelmap(self): def test_can_write_and_parse_labelmap(self):
src_label_map = VOC.make_voc_label_map() src_label_map = VOC.make_voc_label_map()
src_label_map['qq'] = [None, ['part1', 'part2'], ['act1', 'act2']] src_label_map['qq'] = [None, ['part1', 'part2'], ['act1', 'act2']]
src_label_map['ww'] = [(10, 20, 30), [], ['act3']]
with TestDir() as test_dir: with TestDir() as test_dir:
file_path = osp.join(test_dir, 'test.txt') file_path = osp.join(test_dir, 'test.txt')
@ -774,4 +780,4 @@ class VocFormatTest(TestCase):
VOC.write_label_map(file_path, src_label_map) VOC.write_label_map(file_path, src_label_map)
dst_label_map = VOC.parse_label_map(file_path) dst_label_map = VOC.parse_label_map(file_path)
self.assertEqual(src_label_map, dst_label_map) self.assertEqual(src_label_map, dst_label_map)

@ -114,3 +114,29 @@ class YoloFormatTest(TestCase):
image_info={'1': (10, 15)}).make_dataset() image_info={'1': (10, 15)}).make_dataset()
compare_datasets(self, source_dataset, parsed_dataset) compare_datasets(self, source_dataset, parsed_dataset)
class YoloImporterTest(TestCase):
def test_can_detect(self):
class TestExtractor(Extractor):
def __iter__(self):
return iter([
DatasetItem(id=1, subset='train',
image=Image(path='1.jpg', size=(10, 15)),
annotations=[
Bbox(0, 2, 4, 2, label=2),
Bbox(3, 3, 2, 3, label=4),
]),
])
def categories(self):
label_categories = LabelCategories()
for i in range(10):
label_categories.add('label_' + str(i))
return {
AnnotationType.label: label_categories,
}
with TestDir() as test_dir:
YoloConverter()(TestExtractor(), save_dir=test_dir)
self.assertTrue(YoloImporter.detect(test_dir))
Loading…
Cancel
Save