diff --git a/CHANGELOG.md b/CHANGELOG.md index 9007de7dc77..999ab1d976a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - `annotation` application is replaced with `dataset_manager` (https://github.com/opencv/cvat/pull/1352) +- `_DATUMARO_INIT_LOGLEVEL` env. variable is removed in favor of regular `--loglevel` cli parameter (https://github.com/opencv/cvat/pull/1583) ### Fixed - Categories for empty projects with no sources are taken from own dataset (https://github.com/opencv/cvat/pull/1352) @@ -49,6 +50,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `tf.placeholder() is not compatible with eager execution` exception for auto_segmentation () - Canvas cannot be moved with move functionality on left mouse key () - Deep extreme cut request is sent when draw any shape with Make AI polygon option enabled () +- Fixed an error when exporting a task with cuboids to any format except CVAT () +- Synchronization with remote git repo () +- A problem with mask to polygons conversion when polygons are too small () ### Security - diff --git a/cvat-ui/src/components/create-task-page/styles.scss b/cvat-ui/src/components/create-task-page/styles.scss index df2bcc459cc..ccf664b2fcd 100644 --- a/cvat-ui/src/components/create-task-page/styles.scss +++ b/cvat-ui/src/components/create-task-page/styles.scss @@ -8,7 +8,8 @@ text-align: center; padding-top: 40px; overflow-y: auto; - height: 90%; + height: 100%; + padding-bottom: 40px; > div > span { font-size: 36px; diff --git a/cvat-ui/src/components/register-page/register-form.tsx b/cvat-ui/src/components/register-page/register-form.tsx index 813b7bbcaee..daa38c8edda 100644 --- a/cvat-ui/src/components/register-page/register-form.tsx +++ b/cvat-ui/src/components/register-page/register-form.tsx @@ -12,6 +12,7 @@ import Checkbox from 'antd/lib/checkbox'; import patterns from 'utils/validation-patterns'; import { UserAgreement } from 'reducers/interfaces' +import { Row, Col } from 'antd/lib/grid'; export interface UserConfirmation { name: string; @@ -107,9 +108,9 @@ class RegisterFormComponent extends React.PureComponent { form.validateFields((error, values): void => { if (!error) { values.confirmations = [] - + for (const userAgreement of userAgreements) { - + values.confirmations.push({ name: userAgreement.name, value: values[userAgreement.name] @@ -289,8 +290,14 @@ class RegisterFormComponent extends React.PureComponent { return (
- {this.renderFirstNameField()} - {this.renderLastNameField()} + + + {this.renderFirstNameField()} + + + {this.renderLastNameField()} + + {this.renderUsernameField()} {this.renderEmailField()} {this.renderPasswordField()} diff --git a/cvat-ui/src/components/register-page/register-page.tsx b/cvat-ui/src/components/register-page/register-page.tsx index 872766fdb80..bdde506c92d 100644 --- a/cvat-ui/src/components/register-page/register-page.tsx +++ b/cvat-ui/src/components/register-page/register-page.tsx @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: MIT +import './styles.scss'; import React from 'react'; import { RouteComponentProps } from 'react-router'; import { Link, withRouter } from 'react-router-dom'; @@ -29,8 +30,8 @@ function RegisterPageComponent( xs: { span: 14 }, sm: { span: 14 }, md: { span: 10 }, - lg: { span: 4 }, - xl: { span: 4 }, + lg: { span: 6 }, + xl: { span: 5 }, }; const { diff --git a/cvat-ui/src/components/register-page/styles.scss b/cvat-ui/src/components/register-page/styles.scss new file mode 100644 index 00000000000..3eaf5beaf51 --- /dev/null +++ b/cvat-ui/src/components/register-page/styles.scss @@ -0,0 +1,7 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +.ant-form-item { + margin-bottom: 12px; +} \ No newline at end of file diff --git a/cvat-ui/src/components/settings-page/styles.scss b/cvat-ui/src/components/settings-page/styles.scss index b3d475725ae..4858af60383 100644 --- a/cvat-ui/src/components/settings-page/styles.scss +++ b/cvat-ui/src/components/settings-page/styles.scss @@ -5,8 +5,9 @@ @import '../../base.scss'; .cvat-settings-page { - height: 90%; + height: 100%; overflow-y: auto; + padding-bottom: 15px; > div:nth-child(1) { margin-top: 30px; diff --git a/cvat-ui/src/components/tasks-page/top-bar.tsx b/cvat-ui/src/components/tasks-page/top-bar.tsx index 9f3c1e53202..72216d73003 100644 --- a/cvat-ui/src/components/tasks-page/top-bar.tsx +++ b/cvat-ui/src/components/tasks-page/top-bar.tsx @@ -52,6 +52,7 @@ function TopBarComponent(props: VisibleTopBarProps & RouteComponentProps): JSX.E onClick={ (): void => history.push('/tasks/create') } + icon='plus' > Create new task diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index 04a4be04115..d8152086b49 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -518,6 +518,8 @@ def convert_attrs(label, cvat_attrs): x0, y0, x1, y1 = anno_points anno = datumaro.Bbox(x0, y0, x1 - x0, y1 - y0, label=anno_label, attributes=anno_attr, group=anno_group) + elif shape_obj.type == ShapeType.CUBOID: + continue # Datumaro does not support cuboids else: raise Exception("Unknown shape type '%s'" % shape_obj.type) diff --git a/cvat/apps/git/git.py b/cvat/apps/git/git.py index bb960a23bd7..5d0e817829b 100644 --- a/cvat/apps/git/git.py +++ b/cvat/apps/git/git.py @@ -10,13 +10,12 @@ import shutil import subprocess from glob import glob -from tempfile import TemporaryDirectory +import zipfile import django_rq import git from django.db import transaction from django.utils import timezone -from pyunpack import Archive from cvat.apps.dataset_manager.task import export_task from cvat.apps.engine.log import slogger @@ -284,16 +283,16 @@ def push(self, user, scheme, host, db_task, last_save): if ext == '.zip': shutil.move(dump_name, self._annotation_file) elif ext == '.xml': - with TemporaryDirectory() as tmp_dir: - # TODO: remove extra packing-unpacking - Archive(src_path).extractall(tmp_dir) - anno_paths = glob(osp.join(tmp_dir, '**', '*.xml'), - recursive=True) - shutil.move(anno_paths[0], self._annotation_file) + with zipfile.ZipFile(dump_name) as archive: + for f in archive.namelist(): + if f.endswith('.xml'): + with open(self._annotation_file, 'wb') as output: + output.write(archive.read(f)) + break + os.remove(dump_name) else: raise Exception("Got unknown annotation file type") - os.remove(dump_name) self._rep.git.add(self._annotation_file) # Merge diffs diff --git a/datumaro/datumaro/cli/__main__.py b/datumaro/datumaro/cli/__main__.py index b68de226740..a7d7dd992a7 100644 --- a/datumaro/datumaro/cli/__main__.py +++ b/datumaro/datumaro/cli/__main__.py @@ -5,8 +5,6 @@ import argparse import logging as log -import logging.handlers -import os import sys from . import contexts, commands @@ -25,6 +23,25 @@ def loglevel(name): return _log_levels[name] +class _LogManager: + @classmethod + def init_logger(cls, args=None): + # Define minimalistic parser only to obtain loglevel + parser = argparse.ArgumentParser(add_help=False) + cls._define_loglevel_option(parser) + args, _ = parser.parse_known_args(args) + + log.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', + level=args.loglevel) + + @staticmethod + def _define_loglevel_option(parser): + parser.add_argument('--loglevel', type=loglevel, default='info', + help="Logging level (options: %s; default: %s)" % \ + (', '.join(_log_levels.keys()), "%(default)s")) + return parser + + def _make_subcommands_help(commands, help_line_start=0): desc = "" for command_name, _, command_help in commands: @@ -38,9 +55,7 @@ def make_parser(): formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('--version', action='version', version=VERSION) - parser.add_argument('--loglevel', type=loglevel, default='info', - help="Logging level (options: %s; default: %s)" % \ - (', '.join(_log_levels.keys()), "%(default)s")) + _LogManager._define_loglevel_option(parser) known_contexts = [ ('project', contexts.project, "Actions on projects (datasets)"), @@ -83,71 +98,13 @@ def make_parser(): return parser -class _LogManager: - _LOGLEVEL_ENV_NAME = '_DATUMARO_INIT_LOGLEVEL' - _BUFFER_SIZE = 1000 - _root = None - _init_handler = None - _default_handler = None - - @classmethod - def init_basic_logger(cls): - base_loglevel = os.getenv(cls._LOGLEVEL_ENV_NAME, 'info') - base_loglevel = loglevel(base_loglevel) - root = log.getLogger() - root.setLevel(base_loglevel) - - # NOTE: defer use of this handler until the logger - # is properly initialized, but keep logging enabled before this. - # Store messages obtained during initialization and print them after - # if necessary. - default_handler = log.StreamHandler() - default_handler.setFormatter( - log.Formatter('%(asctime)s %(levelname)s: %(message)s')) - - init_handler = logging.handlers.MemoryHandler(cls._BUFFER_SIZE, - target=default_handler) - root.addHandler(init_handler) - - cls._root = root - cls._init_handler = init_handler - cls._default_handler = default_handler - - @classmethod - def set_up_logger(cls, level): - log.getLogger().setLevel(level) - - if cls._init_handler: - # NOTE: Handlers are not capable of filtering with loglevel - # despite a level can be set for a handler. The level is checked - # by Logger. However, handler filters are checked at handler level. - class LevelFilter: - def __init__(self, level): - super().__init__() - self.level = level - - def filter(self, record): - return record.levelno >= self.level - filt = LevelFilter(level) - cls._default_handler.addFilter(filt) - - cls._root.removeHandler(cls._init_handler) - cls._init_handler.close() - del cls._init_handler - cls._init_handler = None - - cls._default_handler.removeFilter(filt) - - cls._root.addHandler(cls._default_handler) def main(args=None): - _LogManager.init_basic_logger() + _LogManager.init_logger(args) parser = make_parser() args = parser.parse_args(args) - _LogManager.set_up_logger(args.loglevel) - if 'command' not in args: parser.print_help() return 1 diff --git a/datumaro/datumaro/plugins/transforms.py b/datumaro/datumaro/plugins/transforms.py index 17f0b2a08fd..d37e284a985 100644 --- a/datumaro/datumaro/plugins/transforms.py +++ b/datumaro/datumaro/plugins/transforms.py @@ -216,7 +216,7 @@ def transform_item(self, item): log.debug("[%s]: item %s: " "Mask conversion to polygons resulted in too " "small polygons, which were discarded" % \ - (self.NAME, item.id)) + (self._get_name(__class__), item.id)) annotations.extend(polygons) else: annotations.append(ann) diff --git a/datumaro/tests/test_transforms.py b/datumaro/tests/test_transforms.py index 58c677a275d..522cf2b520f 100644 --- a/datumaro/tests/test_transforms.py +++ b/datumaro/tests/test_transforms.py @@ -1,3 +1,4 @@ +import logging as log import numpy as np from unittest import TestCase @@ -65,6 +66,33 @@ def __iter__(self): actual = transforms.MasksToPolygons(SrcExtractor()) compare_datasets(self, DstExtractor(), actual) + def test_mask_to_polygons_small_polygons_message(self): + class SrcExtractor(Extractor): + def __iter__(self): + items = [ + DatasetItem(id=1, image=np.zeros((5, 10, 3)), + annotations=[ + Mask(np.array([ + [0, 0, 0], + [0, 1, 0], + [0, 0, 0], + ]), + ), + ] + ), + ] + return iter(items) + + class DstExtractor(Extractor): + def __iter__(self): + return iter([ DatasetItem(id=1, image=np.zeros((5, 10, 3))), ]) + + with self.assertLogs(level=log.DEBUG) as logs: + actual = transforms.MasksToPolygons(SrcExtractor()) + + compare_datasets(self, DstExtractor(), actual) + self.assertRegex('\n'.join(logs.output), 'too small polygons') + def test_polygons_to_masks(self): class SrcExtractor(Extractor): def __iter__(self):