diff --git a/README.md b/README.md index d92aaf2..30f5cf2 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,3 @@ This project is SuperAnnotate annotations JSON schema generator. ``` $ pip install superannotate_schemas ``` - -## Example -``` -$ superannotate_schema validate -$ superannotate_schema validate , -``` -- To print validation report set --verbose True -- To store reports in the file set --report-path diff --git a/requirements.txt b/requirements.txt index 41c6b5e..01bfa40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,4 @@ -dnspython==2.1.0 -fire==0.4.0 -idna==3.3 -pydantic==1.8.2 -six==1.16.0 -termcolor==1.1.0 -typing-extensions==3.10.0.2 -typingx==0.5.3 -pydantic[email] -email-validator>=1.0.3 \ No newline at end of file +attrs~=23.1.0 +pyrsistent~=0.20.0 +six~=1.16.0 +twisted~=23.10.0 \ No newline at end of file diff --git a/src/superannotate_schemas/__init__.py b/src/superannotate_schemas/__init__.py index c19b4b6..ca57d34 100644 --- a/src/superannotate_schemas/__init__.py +++ b/src/superannotate_schemas/__init__.py @@ -1,14 +1,36 @@ -import os -import sys +""" +An implementation of JSON Schema for Python -WORKING_DIR = os.path.split(os.path.realpath(__file__))[0] -sys.path.append(WORKING_DIR) +The main functionality is provided by the validator classes for each of the +supported JSON Schema versions. -from superannotate_schemas.validators import AnnotationValidators +Most commonly, `validate` is the quickest way to simply validate a given +instance under a schema, and will create a validator for you. +""" -__version__ = '1.0.45dev5' +from superannotate_schemas.exceptions import ( + ErrorTree, FormatError, RefResolutionError, SchemaError, ValidationError +) +from superannotate_schemas._format import ( + FormatChecker, + draft3_format_checker, + draft4_format_checker, + draft6_format_checker, + draft7_format_checker, +) +from superannotate_schemas._types import TypeChecker +from superannotate_schemas.validators import ( + Draft3Validator, + Draft4Validator, + Draft6Validator, + Draft7Validator, + RefResolver, + validate, +) -__all__ = [ - "__version__", - "AnnotationValidators" -] +try: + from importlib import metadata +except ImportError: # for Python<3.8 + import importlib_metadata as metadata + +__version__ = '1.0.46b1' diff --git a/src/superannotate_schemas/__main__.py b/src/superannotate_schemas/__main__.py new file mode 100644 index 0000000..8efda73 --- /dev/null +++ b/src/superannotate_schemas/__main__.py @@ -0,0 +1,2 @@ +from superannotate_schemas.cli import main +main() diff --git a/src/superannotate_schemas/_format.py b/src/superannotate_schemas/_format.py new file mode 100644 index 0000000..9f64f62 --- /dev/null +++ b/src/superannotate_schemas/_format.py @@ -0,0 +1,425 @@ +import datetime +import re +import socket +import struct + +from superannotate_schemas.compat import str_types +from superannotate_schemas.exceptions import FormatError + + +class FormatChecker(object): + """ + A ``format`` property checker. + + JSON Schema does not mandate that the ``format`` property actually do any + validation. If validation is desired however, instances of this class can + be hooked into validators to enable format validation. + + `FormatChecker` objects always return ``True`` when asked about + formats that they do not know how to validate. + + To check a custom format using a function that takes an instance and + returns a ``bool``, use the `FormatChecker.checks` or + `FormatChecker.cls_checks` decorators. + + Arguments: + + formats (~collections.Iterable): + + The known formats to validate. This argument can be used to + limit which formats will be used during validation. + """ + + checkers = {} + + def __init__(self, formats=None): + if formats is None: + self.checkers = self.checkers.copy() + else: + self.checkers = dict((k, self.checkers[k]) for k in formats) + + def __repr__(self): + return "".format(sorted(self.checkers)) + + def checks(self, format, raises=()): + """ + Register a decorated function as validating a new format. + + Arguments: + + format (str): + + The format that the decorated function will check. + + raises (Exception): + + The exception(s) raised by the decorated function when an + invalid instance is found. + + The exception object will be accessible as the + `jsonschema.exceptions.ValidationError.cause` attribute of the + resulting validation error. + """ + + def _checks(func): + self.checkers[format] = (func, raises) + return func + return _checks + + cls_checks = classmethod(checks) + + def check(self, instance, format): + """ + Check whether the instance conforms to the given format. + + Arguments: + + instance (*any primitive type*, i.e. str, number, bool): + + The instance to check + + format (str): + + The format that instance should conform to + + + Raises: + + FormatError: if the instance does not conform to ``format`` + """ + + if format not in self.checkers: + return + + func, raises = self.checkers[format] + result, cause = None, None + try: + result = func(instance) + except raises as e: + cause = e + if not result: + raise FormatError( + "%r is not a %r" % (instance, format), cause=cause, + ) + + def conforms(self, instance, format): + """ + Check whether the instance conforms to the given format. + + Arguments: + + instance (*any primitive type*, i.e. str, number, bool): + + The instance to check + + format (str): + + The format that instance should conform to + + Returns: + + bool: whether it conformed + """ + + try: + self.check(instance, format) + except FormatError: + return False + else: + return True + + +draft3_format_checker = FormatChecker() +draft4_format_checker = FormatChecker() +draft6_format_checker = FormatChecker() +draft7_format_checker = FormatChecker() + + +_draft_checkers = dict( + draft3=draft3_format_checker, + draft4=draft4_format_checker, + draft6=draft6_format_checker, + draft7=draft7_format_checker, +) + + +def _checks_drafts( + name=None, + draft3=None, + draft4=None, + draft6=None, + draft7=None, + raises=(), +): + draft3 = draft3 or name + draft4 = draft4 or name + draft6 = draft6 or name + draft7 = draft7 or name + + def wrap(func): + if draft3: + func = _draft_checkers["draft3"].checks(draft3, raises)(func) + if draft4: + func = _draft_checkers["draft4"].checks(draft4, raises)(func) + if draft6: + func = _draft_checkers["draft6"].checks(draft6, raises)(func) + if draft7: + func = _draft_checkers["draft7"].checks(draft7, raises)(func) + + # Oy. This is bad global state, but relied upon for now, until + # deprecation. See https://github.com/Julian/jsonschema/issues/519 + # and test_format_checkers_come_with_defaults + FormatChecker.cls_checks(draft7 or draft6 or draft4 or draft3, raises)( + func, + ) + return func + return wrap + + +@_checks_drafts(name="idn-email") +@_checks_drafts(name="email") +def is_email(instance): + if not isinstance(instance, str_types): + return True + return "@" in instance + + +_ipv4_re = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$") + + +@_checks_drafts( + draft3="ip-address", draft4="ipv4", draft6="ipv4", draft7="ipv4", +) +def is_ipv4(instance): + if not isinstance(instance, str_types): + return True + if not _ipv4_re.match(instance): + return False + return all(0 <= int(component) <= 255 for component in instance.split(".")) + + +if hasattr(socket, "inet_pton"): + # FIXME: Really this only should raise struct.error, but see the sadness + # that is https://twistedmatrix.com/trac/ticket/9409 + @_checks_drafts( + name="ipv6", raises=(socket.error, struct.error, ValueError), + ) + def is_ipv6(instance): + if not isinstance(instance, str_types): + return True + return socket.inet_pton(socket.AF_INET6, instance) + + +_host_name_re = re.compile(r"^[A-Za-z0-9][A-Za-z0-9\.\-]{1,255}$") + + +@_checks_drafts( + draft3="host-name", + draft4="hostname", + draft6="hostname", + draft7="hostname", +) +def is_host_name(instance): + if not isinstance(instance, str_types): + return True + if not _host_name_re.match(instance): + return False + components = instance.split(".") + for component in components: + if len(component) > 63: + return False + return True + + +try: + # The built-in `idna` codec only implements RFC 3890, so we go elsewhere. + import idna +except ImportError: + pass +else: + @_checks_drafts(draft7="idn-hostname", raises=idna.IDNAError) + def is_idn_host_name(instance): + if not isinstance(instance, str_types): + return True + idna.encode(instance) + return True + + +try: + import rfc3987 +except ImportError: + try: + from rfc3986_validator import validate_rfc3986 + except ImportError: + pass + else: + @_checks_drafts(name="uri") + def is_uri(instance): + if not isinstance(instance, str_types): + return True + return validate_rfc3986(instance, rule="URI") + + @_checks_drafts( + draft6="uri-reference", + draft7="uri-reference", + raises=ValueError, + ) + def is_uri_reference(instance): + if not isinstance(instance, str_types): + return True + return validate_rfc3986(instance, rule="URI_reference") + +else: + @_checks_drafts(draft7="iri", raises=ValueError) + def is_iri(instance): + if not isinstance(instance, str_types): + return True + return rfc3987.parse(instance, rule="IRI") + + @_checks_drafts(draft7="iri-reference", raises=ValueError) + def is_iri_reference(instance): + if not isinstance(instance, str_types): + return True + return rfc3987.parse(instance, rule="IRI_reference") + + @_checks_drafts(name="uri", raises=ValueError) + def is_uri(instance): + if not isinstance(instance, str_types): + return True + return rfc3987.parse(instance, rule="URI") + + @_checks_drafts( + draft6="uri-reference", + draft7="uri-reference", + raises=ValueError, + ) + def is_uri_reference(instance): + if not isinstance(instance, str_types): + return True + return rfc3987.parse(instance, rule="URI_reference") + + +try: + from strict_rfc3339 import validate_rfc3339 +except ImportError: + try: + from rfc3339_validator import validate_rfc3339 + except ImportError: + validate_rfc3339 = None + +if validate_rfc3339: + @_checks_drafts(name="date-time") + def is_datetime(instance): + if not isinstance(instance, str_types): + return True + return validate_rfc3339(instance) + + @_checks_drafts(draft7="time") + def is_time(instance): + if not isinstance(instance, str_types): + return True + return is_datetime("1970-01-01T" + instance) + + +@_checks_drafts(name="regex", raises=re.error) +def is_regex(instance): + if not isinstance(instance, str_types): + return True + return re.compile(instance) + + +@_checks_drafts(draft3="date", draft7="date", raises=ValueError) +def is_date(instance): + if not isinstance(instance, str_types): + return True + return datetime.datetime.strptime(instance, "%Y-%m-%d") + + +@_checks_drafts(draft3="time", raises=ValueError) +def is_draft3_time(instance): + if not isinstance(instance, str_types): + return True + return datetime.datetime.strptime(instance, "%H:%M:%S") + + +try: + import webcolors +except ImportError: + pass +else: + def is_css_color_code(instance): + return webcolors.normalize_hex(instance) + + @_checks_drafts(draft3="color", raises=(ValueError, TypeError)) + def is_css21_color(instance): + if ( + not isinstance(instance, str_types) or + instance.lower() in webcolors.css21_names_to_hex + ): + return True + return is_css_color_code(instance) + + def is_css3_color(instance): + if instance.lower() in webcolors.css3_names_to_hex: + return True + return is_css_color_code(instance) + + +try: + import jsonpointer +except ImportError: + pass +else: + @_checks_drafts( + draft6="json-pointer", + draft7="json-pointer", + raises=jsonpointer.JsonPointerException, + ) + def is_json_pointer(instance): + if not isinstance(instance, str_types): + return True + return jsonpointer.JsonPointer(instance) + + # TODO: I don't want to maintain this, so it + # needs to go either into jsonpointer (pending + # https://github.com/stefankoegl/python-json-pointer/issues/34) or + # into a new external library. + @_checks_drafts( + draft7="relative-json-pointer", + raises=jsonpointer.JsonPointerException, + ) + def is_relative_json_pointer(instance): + # Definition taken from: + # https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3 + if not isinstance(instance, str_types): + return True + non_negative_integer, rest = [], "" + for i, character in enumerate(instance): + if character.isdigit(): + non_negative_integer.append(character) + continue + + if not non_negative_integer: + return False + + rest = instance[i:] + break + return (rest == "#") or jsonpointer.JsonPointer(rest) + + +try: + import uritemplate.exceptions +except ImportError: + pass +else: + @_checks_drafts( + draft6="uri-template", + draft7="uri-template", + raises=uritemplate.exceptions.InvalidTemplate, + ) + def is_uri_template( + instance, + template_validator=uritemplate.Validator().force_balanced_braces(), + ): + template = uritemplate.URITemplate(instance) + return template_validator.validate(template) diff --git a/src/superannotate_schemas/_legacy_validators.py b/src/superannotate_schemas/_legacy_validators.py new file mode 100644 index 0000000..823f38e --- /dev/null +++ b/src/superannotate_schemas/_legacy_validators.py @@ -0,0 +1,141 @@ +from superannotate_schemas import _utils +from superannotate_schemas.compat import iteritems +from superannotate_schemas.exceptions import ValidationError + + +def dependencies_draft3(validator, dependencies, instance, schema): + if not validator.is_type(instance, "object"): + return + + for property, dependency in iteritems(dependencies): + if property not in instance: + continue + + if validator.is_type(dependency, "object"): + for error in validator.descend( + instance, dependency, schema_path=property, + ): + yield error + elif validator.is_type(dependency, "string"): + if dependency not in instance: + yield ValidationError( + "%r is a dependency of %r" % (dependency, property) + ) + else: + for each in dependency: + if each not in instance: + message = "%r is a dependency of %r" + yield ValidationError(message % (each, property)) + + +def disallow_draft3(validator, disallow, instance, schema): + for disallowed in _utils.ensure_list(disallow): + if validator.is_valid(instance, {"type": [disallowed]}): + yield ValidationError( + "%r is disallowed for %r" % (disallowed, instance) + ) + + +def extends_draft3(validator, extends, instance, schema): + if validator.is_type(extends, "object"): + for error in validator.descend(instance, extends): + yield error + return + for index, subschema in enumerate(extends): + for error in validator.descend(instance, subschema, schema_path=index): + yield error + + +def items_draft3_draft4(validator, items, instance, schema): + if not validator.is_type(instance, "array"): + return + + if validator.is_type(items, "object"): + for index, item in enumerate(instance): + for error in validator.descend(item, items, path=index): + yield error + else: + for (index, item), subschema in zip(enumerate(instance), items): + for error in validator.descend( + item, subschema, path=index, schema_path=index, + ): + yield error + + +def minimum_draft3_draft4(validator, minimum, instance, schema): + if not validator.is_type(instance, "number"): + return + + if schema.get("exclusiveMinimum", False): + failed = instance <= minimum + cmp = "less than or equal to" + else: + failed = instance < minimum + cmp = "less than" + + if failed: + yield ValidationError( + "%r is %s the minimum of %r" % (instance, cmp, minimum) + ) + + +def maximum_draft3_draft4(validator, maximum, instance, schema): + if not validator.is_type(instance, "number"): + return + + if schema.get("exclusiveMaximum", False): + failed = instance >= maximum + cmp = "greater than or equal to" + else: + failed = instance > maximum + cmp = "greater than" + + if failed: + yield ValidationError( + "%r is %s the maximum of %r" % (instance, cmp, maximum) + ) + + +def properties_draft3(validator, properties, instance, schema): + if not validator.is_type(instance, "object"): + return + + for property, subschema in iteritems(properties): + if property in instance: + for error in validator.descend( + instance[property], + subschema, + path=property, + schema_path=property, + ): + yield error + elif subschema.get("required", False): + error = ValidationError("%r is a required property" % property) + error._set( + validator="required", + validator_value=subschema["required"], + instance=instance, + schema=schema, + ) + error.path.appendleft(property) + error.schema_path.extend([property, "required"]) + yield error + + +def type_draft3(validator, types, instance, schema): + types = _utils.ensure_list(types) + + all_errors = [] + for index, type in enumerate(types): + if validator.is_type(type, "object"): + errors = list(validator.descend(instance, type, schema_path=index)) + if not errors: + return + all_errors.extend(errors) + else: + if validator.is_type(instance, type): + return + else: + yield ValidationError( + _utils.types_msg(instance, types), context=all_errors, + ) diff --git a/src/superannotate_schemas/_reflect.py b/src/superannotate_schemas/_reflect.py new file mode 100644 index 0000000..534906f --- /dev/null +++ b/src/superannotate_schemas/_reflect.py @@ -0,0 +1,155 @@ +# -*- test-case-name: twisted.test.test_reflect -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +Standardized versions of various cool and/or strange things that you can do +with Python's reflection capabilities. +""" + +import sys + +from superannotate_schemas.compat import PY3 + + +class _NoModuleFound(Exception): + """ + No module was found because none exists. + """ + + + +class InvalidName(ValueError): + """ + The given name is not a dot-separated list of Python objects. + """ + + + +class ModuleNotFound(InvalidName): + """ + The module associated with the given name doesn't exist and it can't be + imported. + """ + + + +class ObjectNotFound(InvalidName): + """ + The object associated with the given name doesn't exist and it can't be + imported. + """ + + + +if PY3: + def reraise(exception, traceback): + raise exception.with_traceback(traceback) +else: + exec("""def reraise(exception, traceback): + raise exception.__class__, exception, traceback""") + +reraise.__doc__ = """ +Re-raise an exception, with an optional traceback, in a way that is compatible +with both Python 2 and Python 3. + +Note that on Python 3, re-raised exceptions will be mutated, with their +C{__traceback__} attribute being set. + +@param exception: The exception instance. +@param traceback: The traceback to use, or C{None} indicating a new traceback. +""" + + +def _importAndCheckStack(importName): + """ + Import the given name as a module, then walk the stack to determine whether + the failure was the module not existing, or some code in the module (for + example a dependent import) failing. This can be helpful to determine + whether any actual application code was run. For example, to distiguish + administrative error (entering the wrong module name), from programmer + error (writing buggy code in a module that fails to import). + + @param importName: The name of the module to import. + @type importName: C{str} + @raise Exception: if something bad happens. This can be any type of + exception, since nobody knows what loading some arbitrary code might + do. + @raise _NoModuleFound: if no module was found. + """ + try: + return __import__(importName) + except ImportError: + excType, excValue, excTraceback = sys.exc_info() + while excTraceback: + execName = excTraceback.tb_frame.f_globals["__name__"] + # in Python 2 execName is None when an ImportError is encountered, + # where in Python 3 execName is equal to the importName. + if execName is None or execName == importName: + reraise(excValue, excTraceback) + excTraceback = excTraceback.tb_next + raise _NoModuleFound() + + + +def namedAny(name): + """ + Retrieve a Python object by its fully qualified name from the global Python + module namespace. The first part of the name, that describes a module, + will be discovered and imported. Each subsequent part of the name is + treated as the name of an attribute of the object specified by all of the + name which came before it. For example, the fully-qualified name of this + object is 'twisted.python.reflect.namedAny'. + + @type name: L{str} + @param name: The name of the object to return. + + @raise InvalidName: If the name is an empty string, starts or ends with + a '.', or is otherwise syntactically incorrect. + + @raise ModuleNotFound: If the name is syntactically correct but the + module it specifies cannot be imported because it does not appear to + exist. + + @raise ObjectNotFound: If the name is syntactically correct, includes at + least one '.', but the module it specifies cannot be imported because + it does not appear to exist. + + @raise AttributeError: If an attribute of an object along the way cannot be + accessed, or a module along the way is not found. + + @return: the Python object identified by 'name'. + """ + if not name: + raise InvalidName('Empty module name') + + names = name.split('.') + + # if the name starts or ends with a '.' or contains '..', the __import__ + # will raise an 'Empty module name' error. This will provide a better error + # message. + if '' in names: + raise InvalidName( + "name must be a string giving a '.'-separated list of Python " + "identifiers, not %r" % (name,)) + + topLevelPackage = None + moduleNames = names[:] + while not topLevelPackage: + if moduleNames: + trialname = '.'.join(moduleNames) + try: + topLevelPackage = _importAndCheckStack(trialname) + except _NoModuleFound: + moduleNames.pop() + else: + if len(names) == 1: + raise ModuleNotFound("No module named %r" % (name,)) + else: + raise ObjectNotFound('%r does not name an object' % (name,)) + + obj = topLevelPackage + for n in names[1:]: + obj = getattr(obj, n) + + return obj diff --git a/src/superannotate_schemas/_types.py b/src/superannotate_schemas/_types.py new file mode 100644 index 0000000..4c367c6 --- /dev/null +++ b/src/superannotate_schemas/_types.py @@ -0,0 +1,188 @@ +import numbers + +from pyrsistent import pmap +import attr + +from superannotate_schemas.compat import int_types, str_types +from superannotate_schemas.exceptions import UndefinedTypeCheck + + +def is_array(checker, instance): + return isinstance(instance, list) + + +def is_bool(checker, instance): + return isinstance(instance, bool) + + +def is_integer(checker, instance): + # bool inherits from int, so ensure bools aren't reported as ints + if isinstance(instance, bool): + return False + return isinstance(instance, int_types) + + +def is_null(checker, instance): + return instance is None + + +def is_number(checker, instance): + # bool inherits from int, so ensure bools aren't reported as ints + if isinstance(instance, bool): + return False + return isinstance(instance, numbers.Number) + + +def is_object(checker, instance): + return isinstance(instance, dict) + + +def is_string(checker, instance): + return isinstance(instance, str_types) + + +def is_any(checker, instance): + return True + + +@attr.s(frozen=True) +class TypeChecker(object): + """ + A ``type`` property checker. + + A `TypeChecker` performs type checking for an `IValidator`. Type + checks to perform are updated using `TypeChecker.redefine` or + `TypeChecker.redefine_many` and removed via `TypeChecker.remove`. + Each of these return a new `TypeChecker` object. + + Arguments: + + type_checkers (dict): + + The initial mapping of types to their checking functions. + """ + _type_checkers = attr.ib(default=pmap(), converter=pmap) + + def is_type(self, instance, type): + """ + Check if the instance is of the appropriate type. + + Arguments: + + instance (object): + + The instance to check + + type (str): + + The name of the type that is expected. + + Returns: + + bool: Whether it conformed. + + + Raises: + + `jsonschema.exceptions.UndefinedTypeCheck`: + if type is unknown to this object. + """ + try: + fn = self._type_checkers[type] + except KeyError: + raise UndefinedTypeCheck(type) + + return fn(self, instance) + + def redefine(self, type, fn): + """ + Produce a new checker with the given type redefined. + + Arguments: + + type (str): + + The name of the type to check. + + fn (collections.Callable): + + A function taking exactly two parameters - the type + checker calling the function and the instance to check. + The function should return true if instance is of this + type and false otherwise. + + Returns: + + A new `TypeChecker` instance. + """ + return self.redefine_many({type: fn}) + + def redefine_many(self, definitions=()): + """ + Produce a new checker with the given types redefined. + + Arguments: + + definitions (dict): + + A dictionary mapping types to their checking functions. + + Returns: + + A new `TypeChecker` instance. + """ + return attr.evolve( + self, type_checkers=self._type_checkers.update(definitions), + ) + + def remove(self, *types): + """ + Produce a new checker with the given types forgotten. + + Arguments: + + types (~collections.Iterable): + + the names of the types to remove. + + Returns: + + A new `TypeChecker` instance + + Raises: + + `jsonschema.exceptions.UndefinedTypeCheck`: + + if any given type is unknown to this object + """ + + checkers = self._type_checkers + for each in types: + try: + checkers = checkers.remove(each) + except KeyError: + raise UndefinedTypeCheck(each) + return attr.evolve(self, type_checkers=checkers) + + +draft3_type_checker = TypeChecker( + { + u"any": is_any, + u"array": is_array, + u"boolean": is_bool, + u"integer": is_integer, + u"object": is_object, + u"null": is_null, + u"number": is_number, + u"string": is_string, + }, +) +draft4_type_checker = draft3_type_checker.remove(u"any") +draft6_type_checker = draft4_type_checker.redefine( + u"integer", + lambda checker, instance: ( + is_integer(checker, instance) or + isinstance(instance, float) and instance.is_integer() + ), +) +draft7_type_checker = draft6_type_checker diff --git a/src/superannotate_schemas/_utils.py b/src/superannotate_schemas/_utils.py new file mode 100644 index 0000000..b4daaf3 --- /dev/null +++ b/src/superannotate_schemas/_utils.py @@ -0,0 +1,212 @@ +import itertools +import json +import pkgutil +import re + +from superannotate_schemas.compat import MutableMapping, str_types, urlsplit + + +class URIDict(MutableMapping): + """ + Dictionary which uses normalized URIs as keys. + """ + + def normalize(self, uri): + return urlsplit(uri).geturl() + + def __init__(self, *args, **kwargs): + self.store = dict() + self.store.update(*args, **kwargs) + + def __getitem__(self, uri): + return self.store[self.normalize(uri)] + + def __setitem__(self, uri, value): + self.store[self.normalize(uri)] = value + + def __delitem__(self, uri): + del self.store[self.normalize(uri)] + + def __iter__(self): + return iter(self.store) + + def __len__(self): + return len(self.store) + + def __repr__(self): + return repr(self.store) + + +class Unset(object): + """ + An as-of-yet unset attribute or unprovided default parameter. + """ + + def __repr__(self): + return "" + + +def load_schema(name): + """ + Load a schema from ./schemas/``name``.json and return it. + """ + + data = pkgutil.get_data("superannotate_schemas", "schemas/{0}.json".format(name)) + return json.loads(data.decode("utf-8")) + + +def indent(string, times=1): + """ + A dumb version of `textwrap.indent` from Python 3.3. + """ + + return "\n".join(" " * (4 * times) + line for line in string.splitlines()) + + +def format_as_index(indices): + """ + Construct a single string containing indexing operations for the indices. + + For example, [1, 2, "foo"] -> [1][2]["foo"] + + Arguments: + + indices (sequence): + + The indices to format. + """ + + if not indices: + return "" + return "[%s]" % "][".join(repr(index) for index in indices) + + +def find_additional_properties(instance, schema): + """ + Return the set of additional properties for the given ``instance``. + + Weeds out properties that should have been validated by ``properties`` and + / or ``patternProperties``. + + Assumes ``instance`` is dict-like already. + """ + + properties = schema.get("properties", {}) + patterns = "|".join(schema.get("patternProperties", {})) + for property in instance: + if property not in properties: + if patterns and re.search(patterns, property): + continue + yield property + + +def extras_msg(extras): + """ + Create an error message for extra items or properties. + """ + + if len(extras) == 1: + verb = "was" + else: + verb = "were" + return ", ".join(repr(extra) for extra in extras), verb + + +def types_msg(instance, types): + """ + Create an error message for a failure to match the given types. + + If the ``instance`` is an object and contains a ``name`` property, it will + be considered to be a description of that object and used as its type. + + Otherwise the message is simply the reprs of the given ``types``. + """ + + reprs = [] + for type in types: + try: + reprs.append(repr(type["name"])) + except Exception: + reprs.append(repr(type)) + return "%r is not of type %s" % (instance, ", ".join(reprs)) + + +def flatten(suitable_for_isinstance): + """ + isinstance() can accept a bunch of really annoying different types: + * a single type + * a tuple of types + * an arbitrary nested tree of tuples + + Return a flattened tuple of the given argument. + """ + + types = set() + + if not isinstance(suitable_for_isinstance, tuple): + suitable_for_isinstance = (suitable_for_isinstance,) + for thing in suitable_for_isinstance: + if isinstance(thing, tuple): + types.update(flatten(thing)) + else: + types.add(thing) + return tuple(types) + + +def ensure_list(thing): + """ + Wrap ``thing`` in a list if it's a single str. + + Otherwise, return it unchanged. + """ + + if isinstance(thing, str_types): + return [thing] + return thing + + +def equal(one, two): + """ + Check if two things are equal, but evade booleans and ints being equal. + """ + return unbool(one) == unbool(two) + + +def unbool(element, true=object(), false=object()): + """ + A hack to make True and 1 and False and 0 unique for ``uniq``. + """ + + if element is True: + return true + elif element is False: + return false + return element + + +def uniq(container): + """ + Check if all of a container's elements are unique. + + Successively tries first to rely that the elements are hashable, then + falls back on them being sortable, and finally falls back on brute + force. + """ + + try: + return len(set(unbool(i) for i in container)) == len(container) + except TypeError: + try: + sort = sorted(unbool(i) for i in container) + sliced = itertools.islice(sort, 1, None) + for i, j in zip(sort, sliced): + if i == j: + return False + except (NotImplementedError, TypeError): + seen = [] + for e in container: + e = unbool(e) + if e in seen: + return False + seen.append(e) + return True diff --git a/src/superannotate_schemas/_validators.py b/src/superannotate_schemas/_validators.py new file mode 100644 index 0000000..2cd2022 --- /dev/null +++ b/src/superannotate_schemas/_validators.py @@ -0,0 +1,373 @@ +import re + +from superannotate_schemas._utils import ( + ensure_list, + equal, + extras_msg, + find_additional_properties, + types_msg, + unbool, + uniq, +) +from superannotate_schemas.exceptions import FormatError, ValidationError +from superannotate_schemas.compat import iteritems + + +def patternProperties(validator, patternProperties, instance, schema): + if not validator.is_type(instance, "object"): + return + + for pattern, subschema in iteritems(patternProperties): + for k, v in iteritems(instance): + if re.search(pattern, k): + for error in validator.descend( + v, subschema, path=k, schema_path=pattern, + ): + yield error + + +def propertyNames(validator, propertyNames, instance, schema): + if not validator.is_type(instance, "object"): + return + + for property in instance: + for error in validator.descend( + instance=property, + schema=propertyNames, + ): + yield error + + +def additionalProperties(validator, aP, instance, schema): + if not validator.is_type(instance, "object"): + return + + extras = set(find_additional_properties(instance, schema)) + + if validator.is_type(aP, "object"): + for extra in extras: + for error in validator.descend(instance[extra], aP, path=extra): + yield error + elif not aP and extras: + if "patternProperties" in schema: + patterns = sorted(schema["patternProperties"]) + if len(extras) == 1: + verb = "does" + else: + verb = "do" + error = "%s %s not match any of the regexes: %s" % ( + ", ".join(map(repr, sorted(extras))), + verb, + ", ".join(map(repr, patterns)), + ) + yield ValidationError(error) + else: + error = "Additional properties are not allowed (%s %s unexpected)" + yield ValidationError(error % extras_msg(extras)) + + +def items(validator, items, instance, schema): + if not validator.is_type(instance, "array"): + return + + if validator.is_type(items, "array"): + for (index, item), subschema in zip(enumerate(instance), items): + for error in validator.descend( + item, subschema, path=index, schema_path=index, + ): + yield error + else: + for index, item in enumerate(instance): + for error in validator.descend(item, items, path=index): + yield error + + +def additionalItems(validator, aI, instance, schema): + if ( + not validator.is_type(instance, "array") or + validator.is_type(schema.get("items", {}), "object") + ): + return + + len_items = len(schema.get("items", [])) + if validator.is_type(aI, "object"): + for index, item in enumerate(instance[len_items:], start=len_items): + for error in validator.descend(item, aI, path=index): + yield error + elif not aI and len(instance) > len(schema.get("items", [])): + error = "Additional items are not allowed (%s %s unexpected)" + yield ValidationError( + error % + extras_msg(instance[len(schema.get("items", [])):]) + ) + + +def const(validator, const, instance, schema): + if not equal(instance, const): + yield ValidationError("%r was expected" % (const,)) + + +def contains(validator, contains, instance, schema): + if not validator.is_type(instance, "array"): + return + + if not any(validator.is_valid(element, contains) for element in instance): + yield ValidationError( + "None of %r are valid under the given schema" % (instance,) + ) + + +def exclusiveMinimum(validator, minimum, instance, schema): + if not validator.is_type(instance, "number"): + return + + if instance <= minimum: + yield ValidationError( + "%r is less than or equal to the minimum of %r" % ( + instance, minimum, + ), + ) + + +def exclusiveMaximum(validator, maximum, instance, schema): + if not validator.is_type(instance, "number"): + return + + if instance >= maximum: + yield ValidationError( + "%r is greater than or equal to the maximum of %r" % ( + instance, maximum, + ), + ) + + +def minimum(validator, minimum, instance, schema): + if not validator.is_type(instance, "number"): + return + + if instance < minimum: + yield ValidationError( + "%r is less than the minimum of %r" % (instance, minimum) + ) + + +def maximum(validator, maximum, instance, schema): + if not validator.is_type(instance, "number"): + return + + if instance > maximum: + yield ValidationError( + "%r is greater than the maximum of %r" % (instance, maximum) + ) + + +def multipleOf(validator, dB, instance, schema): + if not validator.is_type(instance, "number"): + return + + if isinstance(dB, float): + quotient = instance / dB + failed = int(quotient) != quotient + else: + failed = instance % dB + + if failed: + yield ValidationError("%r is not a multiple of %r" % (instance, dB)) + + +def minItems(validator, mI, instance, schema): + if validator.is_type(instance, "array") and len(instance) < mI: + yield ValidationError("%r is too short" % (instance,)) + + +def maxItems(validator, mI, instance, schema): + if validator.is_type(instance, "array") and len(instance) > mI: + yield ValidationError("%r is too long" % (instance,)) + + +def uniqueItems(validator, uI, instance, schema): + if ( + uI and + validator.is_type(instance, "array") and + not uniq(instance) + ): + yield ValidationError("%r has non-unique elements" % (instance,)) + + +def pattern(validator, patrn, instance, schema): + if ( + validator.is_type(instance, "string") and + not re.search(patrn, instance) + ): + yield ValidationError("%r does not match %r" % (instance, patrn)) + + +def format(validator, format, instance, schema): + if validator.format_checker is not None: + try: + validator.format_checker.check(instance, format) + except FormatError as error: + yield ValidationError(error.message, cause=error.cause) + + +def minLength(validator, mL, instance, schema): + if validator.is_type(instance, "string") and len(instance) < mL: + yield ValidationError("%r is too short" % (instance,)) + + +def maxLength(validator, mL, instance, schema): + if validator.is_type(instance, "string") and len(instance) > mL: + yield ValidationError("%r is too long" % (instance,)) + + +def dependencies(validator, dependencies, instance, schema): + if not validator.is_type(instance, "object"): + return + + for property, dependency in iteritems(dependencies): + if property not in instance: + continue + + if validator.is_type(dependency, "array"): + for each in dependency: + if each not in instance: + message = "%r is a dependency of %r" + yield ValidationError(message % (each, property)) + else: + for error in validator.descend( + instance, dependency, schema_path=property, + ): + yield error + + +def enum(validator, enums, instance, schema): + if instance == 0 or instance == 1: + unbooled = unbool(instance) + if all(unbooled != unbool(each) for each in enums): + yield ValidationError("%r is not one of %r" % (instance, enums)) + elif instance not in enums: + yield ValidationError("%r is not one of %r" % (instance, enums)) + + +def ref(validator, ref, instance, schema): + resolve = getattr(validator.resolver, "resolve", None) + if resolve is None: + with validator.resolver.resolving(ref) as resolved: + for error in validator.descend(instance, resolved): + yield error + else: + scope, resolved = validator.resolver.resolve(ref) + validator.resolver.push_scope(scope) + + try: + for error in validator.descend(instance, resolved): + yield error + finally: + validator.resolver.pop_scope() + + +def type(validator, types, instance, schema): + types = ensure_list(types) + + if not any(validator.is_type(instance, type) for type in types): + yield ValidationError(types_msg(instance, types)) + + +def properties(validator, properties, instance, schema): + if not validator.is_type(instance, "object"): + return + + for property, subschema in iteritems(properties): + if property in instance: + for error in validator.descend( + instance[property], + subschema, + path=property, + schema_path=property, + ): + yield error + + +def required(validator, required, instance, schema): + if not validator.is_type(instance, "object"): + return + for property in required: + if property not in instance: + yield ValidationError("%r is a required property" % property) + + +def minProperties(validator, mP, instance, schema): + if validator.is_type(instance, "object") and len(instance) < mP: + yield ValidationError( + "%r does not have enough properties" % (instance,) + ) + + +def maxProperties(validator, mP, instance, schema): + if not validator.is_type(instance, "object"): + return + if validator.is_type(instance, "object") and len(instance) > mP: + yield ValidationError("%r has too many properties" % (instance,)) + + +def allOf(validator, allOf, instance, schema): + for index, subschema in enumerate(allOf): + for error in validator.descend(instance, subschema, schema_path=index): + yield error + + +def anyOf(validator, anyOf, instance, schema): + all_errors = [] + for index, subschema in enumerate(anyOf): + errs = list(validator.descend(instance, subschema, schema_path=index)) + if not errs: + break + all_errors.extend(errs) + else: + yield ValidationError( + "%r is not valid under any of the given schemas" % (instance,), + context=all_errors, + ) + + +def oneOf(validator, oneOf, instance, schema): + subschemas = enumerate(oneOf) + all_errors = [] + for index, subschema in subschemas: + errs = list(validator.descend(instance, subschema, schema_path=index)) + if not errs: + first_valid = subschema + break + all_errors.extend(errs) + else: + yield ValidationError( + "%r is not valid under any of the given schemas" % (instance,), + context=all_errors, + ) + + more_valid = [s for i, s in subschemas if validator.is_valid(instance, s)] + if more_valid: + more_valid.append(first_valid) + reprs = ", ".join(repr(schema) for schema in more_valid) + yield ValidationError( + "%r is valid under each of %s" % (instance, reprs) + ) + + +def not_(validator, not_schema, instance, schema): + if validator.is_valid(instance, not_schema): + yield ValidationError( + "%r is not allowed for %r" % (not_schema, instance) + ) + + +def if_(validator, if_schema, instance, schema): + if validator.is_valid(instance, if_schema): + if u"then" in schema: + then = schema[u"then"] + for error in validator.descend(instance, then, schema_path="then"): + yield error + elif u"else" in schema: + else_ = schema[u"else"] + for error in validator.descend(instance, else_, schema_path="else"): + yield error diff --git a/src/superannotate_schemas/benchmarks/__init__.py b/src/superannotate_schemas/benchmarks/__init__.py new file mode 100644 index 0000000..e3dcc68 --- /dev/null +++ b/src/superannotate_schemas/benchmarks/__init__.py @@ -0,0 +1,5 @@ +""" +Benchmarks for validation. + +This package is *not* public API. +""" diff --git a/src/superannotate_schemas/benchmarks/issue232.py b/src/superannotate_schemas/benchmarks/issue232.py new file mode 100644 index 0000000..866a91d --- /dev/null +++ b/src/superannotate_schemas/benchmarks/issue232.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +""" +A performance benchmark using the example from issue #232. + +See https://github.com/Julian/jsonschema/pull/232. +""" +from twisted.python.filepath import FilePath +from pyperf import Runner +from pyrsistent import m + +from superannotate_schemas.tests._suite import Version +import jsonschema + + +issue232 = Version( + path=FilePath(__file__).sibling("issue232"), + remotes=m(), + name="issue232", +) + + +if __name__ == "__main__": + issue232.benchmark( + runner=Runner(), + Validator=jsonschema.Draft4Validator, + ) diff --git a/src/superannotate_schemas/benchmarks/json_schema_test_suite.py b/src/superannotate_schemas/benchmarks/json_schema_test_suite.py new file mode 100644 index 0000000..6527028 --- /dev/null +++ b/src/superannotate_schemas/benchmarks/json_schema_test_suite.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +""" +A performance benchmark using the official test suite. + +This benchmarks jsonschema using every valid example in the +JSON-Schema-Test-Suite. It will take some time to complete. +""" +from pyperf import Runner + +from superannotate_schemas.tests._suite import Suite + + +if __name__ == "__main__": + Suite().benchmark(runner=Runner()) diff --git a/src/superannotate_schemas/bin/app.py b/src/superannotate_schemas/bin/app.py deleted file mode 100755 index c4a930e..0000000 --- a/src/superannotate_schemas/bin/app.py +++ /dev/null @@ -1,11 +0,0 @@ -import fire - -from superannotate_schemas.bin.interface import CLIInterface - - -def main(): - fire.Fire(CLIInterface) - - -if __name__ == "__main__": - main() diff --git a/src/superannotate_schemas/bin/interface.py b/src/superannotate_schemas/bin/interface.py deleted file mode 100644 index 7cd5f18..0000000 --- a/src/superannotate_schemas/bin/interface.py +++ /dev/null @@ -1,76 +0,0 @@ -import os -from os.path import expanduser -import json -import errno -from pathlib import Path - -from superannotate_schemas.schemas.external import PixelAnnotation as ExternalPixelAnnotation -from superannotate_schemas.schemas.external import VectorAnnotation as ExternalVectorAnnotation -from superannotate_schemas.schemas.external import VideoAnnotation as ExternalVideoAnnotation -from superannotate_schemas.schemas.external import DocumentAnnotation as ExternalDocumentAnnotation - -from superannotate_schemas.schemas.internal import PixelAnnotation as InternalPixelAnnotation -from superannotate_schemas.schemas.internal import VectorAnnotation as InternalVectorAnnotation -from superannotate_schemas.schemas.internal import VideoAnnotation as InternalVideoAnnotation -from superannotate_schemas.schemas.internal import DocumentAnnotation as InternalDocumentAnnotation -from superannotate_schemas.exceptions import InvalidInput - -from superannotate_schemas import __version__ -from superannotate_schemas.utils import uniquify -from superannotate_schemas.validators import AnnotationValidators - - -class CLIInterface: - """ - This is to validate Pixel, Vector, Image and Document annotations. - """ - DEFAULT_PATH = "schemas/" - EXTERNAL_SCHEMAS = ( - ExternalPixelAnnotation, ExternalVectorAnnotation, ExternalDocumentAnnotation, ExternalVideoAnnotation - ) - INTERNAL_SCHEMAS = ( - InternalPixelAnnotation, InternalVectorAnnotation, InternalDocumentAnnotation, InternalVideoAnnotation - ) - - @staticmethod - def _create_folder(path: str): - if not os.path.exists(os.path.dirname(path)): - try: - os.makedirs(os.path.dirname(path)) - except OSError as exc: - if exc.errno != errno.EEXIST: - raise - - @staticmethod - def validate(*paths, project_type, internal=False, verbose=False, report_path=None): - if not paths: - raise InvalidInput("Please provide paths.") - if project_type not in AnnotationValidators.VALIDATORS.keys(): - raise InvalidInput( - f"Invalid project type, valid types are: {', '.join(AnnotationValidators.VALIDATORS.keys())}" - ) - - validator_class = AnnotationValidators.get_validator(project_type, internal) - validation_result = [] - for path in paths: - if Path(path).is_file(): - with open(path, "r") as file: - data = json.load(file) - validator = validator_class(data) - if not validator.is_valid(): - report = validator.generate_report() - if verbose: - print(f"{'-'* 4}{path}\n{report}") - if report_path: - with open(uniquify(f"{report_path}/{(Path(path).name)}"), "w") as validation_report: - validation_report.write(report) - else: - validation_result.append({path: False}) - else: - print(f"Skip {path}") - if not verbose: - print(validation_result) - - @staticmethod - def version(): - return f"Version : {__version__}" diff --git a/src/superannotate_schemas/cli.py b/src/superannotate_schemas/cli.py new file mode 100644 index 0000000..989d6f1 --- /dev/null +++ b/src/superannotate_schemas/cli.py @@ -0,0 +1,90 @@ +""" +The ``jsonschema`` command line. +""" +from __future__ import absolute_import +import argparse +import json +import sys + +from superannotate_schemas import __version__ +from superannotate_schemas._reflect import namedAny +from superannotate_schemas.validators import validator_for + + +def _namedAnyWithDefault(name): + if "." not in name: + name = "jsonschema." + name + return namedAny(name) + + +def _json_file(path): + with open(path) as file: + return json.load(file) + + +parser = argparse.ArgumentParser( + description="JSON Schema Validation CLI", +) +parser.add_argument( + "-i", "--instance", + action="append", + dest="instances", + type=_json_file, + help=( + "a path to a JSON instance (i.e. filename.json) " + "to validate (may be specified multiple times)" + ), +) +parser.add_argument( + "-F", "--error-format", + default="{error.instance}: {error.message}\n", + help=( + "the format to use for each error output message, specified in " + "a form suitable for passing to str.format, which will be called " + "with 'error' for each error" + ), +) +parser.add_argument( + "-V", "--validator", + type=_namedAnyWithDefault, + help=( + "the fully qualified object name of a validator to use, or, for " + "validators that are registered with jsonschema, simply the name " + "of the class." + ), +) +parser.add_argument( + "--version", + action="version", + version=__version__, +) +parser.add_argument( + "schema", + help="the JSON Schema to validate with (i.e. schema.json)", + type=_json_file, +) + + +def parse_args(args): + arguments = vars(parser.parse_args(args=args or ["--help"])) + if arguments["validator"] is None: + arguments["validator"] = validator_for(arguments["schema"]) + return arguments + + +def main(args=sys.argv[1:]): + sys.exit(run(arguments=parse_args(args=args))) + + +def run(arguments, stdout=sys.stdout, stderr=sys.stderr): + error_format = arguments["error_format"] + validator = arguments["validator"](schema=arguments["schema"]) + + validator.check_schema(arguments["schema"]) + + errored = False + for instance in arguments["instances"] or (): + for error in validator.iter_errors(instance): + stderr.write(error_format.format(error=error)) + errored = True + return errored diff --git a/src/superannotate_schemas/compat.py b/src/superannotate_schemas/compat.py new file mode 100644 index 0000000..47e0980 --- /dev/null +++ b/src/superannotate_schemas/compat.py @@ -0,0 +1,55 @@ +""" +Python 2/3 compatibility helpers. + +Note: This module is *not* public API. +""" +import contextlib +import operator +import sys + + +try: + from collections.abc import MutableMapping, Sequence # noqa +except ImportError: + from collections import MutableMapping, Sequence # noqa + +PY3 = sys.version_info[0] >= 3 + +if PY3: + zip = zip + from functools import lru_cache + from io import StringIO as NativeIO + from urllib.parse import ( + unquote, urljoin, urlunsplit, SplitResult, urlsplit + ) + from urllib.request import pathname2url, urlopen + str_types = str, + int_types = int, + iteritems = operator.methodcaller("items") +else: + from itertools import izip as zip # noqa + from io import BytesIO as NativeIO + from urlparse import urljoin, urlunsplit, SplitResult, urlsplit + from urllib import pathname2url, unquote # noqa + import urllib2 # noqa + def urlopen(*args, **kwargs): + return contextlib.closing(urllib2.urlopen(*args, **kwargs)) + + str_types = basestring + int_types = int, long + iteritems = operator.methodcaller("iteritems") + + from functools32 import lru_cache + + +def urldefrag(url): + if "#" in url: + s, n, p, q, frag = urlsplit(url) + defrag = urlunsplit((s, n, p, q, "")) + else: + defrag = url + frag = "" + return defrag, frag + + +# flake8: noqa diff --git a/src/superannotate_schemas/exceptions.py b/src/superannotate_schemas/exceptions.py index 72474e4..6347838 100644 --- a/src/superannotate_schemas/exceptions.py +++ b/src/superannotate_schemas/exceptions.py @@ -1,17 +1,374 @@ -class AppException(Exception): +""" +Validation errors, and some surrounding helpers. +""" +from collections import defaultdict, deque +import itertools +import pprint +import textwrap + +import attr + +from superannotate_schemas import _utils +from superannotate_schemas.compat import PY3, iteritems + + +WEAK_MATCHES = frozenset(["anyOf", "oneOf"]) +STRONG_MATCHES = frozenset() + +_unset = _utils.Unset() + + +class _Error(Exception): + def __init__( + self, + message, + validator=_unset, + path=(), + cause=None, + context=(), + validator_value=_unset, + instance=_unset, + schema=_unset, + schema_path=(), + parent=None, + ): + super(_Error, self).__init__( + message, + validator, + path, + cause, + context, + validator_value, + instance, + schema, + schema_path, + parent, + ) + self.message = message + self.path = self.relative_path = deque(path) + self.schema_path = self.relative_schema_path = deque(schema_path) + self.context = list(context) + self.cause = self.__cause__ = cause + self.validator = validator + self.validator_value = validator_value + self.instance = instance + self.schema = schema + self.parent = parent + + for error in context: + error.parent = self + + def __repr__(self): + return "<%s: %r>" % (self.__class__.__name__, self.message) + + def __unicode__(self): + essential_for_verbose = ( + self.validator, self.validator_value, self.instance, self.schema, + ) + if any(m is _unset for m in essential_for_verbose): + return self.message + + pschema = pprint.pformat(self.schema, width=72) + pinstance = pprint.pformat(self.instance, width=72) + return self.message + textwrap.dedent(""" + + Failed validating %r in %s%s: + %s + + On %s%s: + %s + """.rstrip() + ) % ( + self.validator, + self._word_for_schema_in_error_message, + _utils.format_as_index(list(self.relative_schema_path)[:-1]), + _utils.indent(pschema), + self._word_for_instance_in_error_message, + _utils.format_as_index(self.relative_path), + _utils.indent(pinstance), + ) + + if PY3: + __str__ = __unicode__ + else: + def __str__(self): + return unicode(self).encode("utf-8") + + @classmethod + def create_from(cls, other): + return cls(**other._contents()) + + @property + def absolute_path(self): + parent = self.parent + if parent is None: + return self.relative_path + + path = deque(self.relative_path) + path.extendleft(reversed(parent.absolute_path)) + return path + + @property + def absolute_schema_path(self): + parent = self.parent + if parent is None: + return self.relative_schema_path + + path = deque(self.relative_schema_path) + path.extendleft(reversed(parent.absolute_schema_path)) + return path + + def _set(self, **kwargs): + for k, v in iteritems(kwargs): + if getattr(self, k) is _unset: + setattr(self, k, v) + + def _contents(self): + attrs = ( + "message", "cause", "context", "validator", "validator_value", + "path", "schema_path", "instance", "schema", "parent", + ) + return dict((attr, getattr(self, attr)) for attr in attrs) + + +class ValidationError(_Error): """ - Base exception for Licensee App. All exceptions thrown by inviter should - extend this. + An instance was invalid under a provided schema. """ - def __init__(self, message): - super().__init__(message) + _word_for_schema_in_error_message = "schema" + _word_for_instance_in_error_message = "instance" - self.message = message + +class SchemaError(_Error): + """ + A schema was invalid under its corresponding metaschema. + """ + + _word_for_schema_in_error_message = "metaschema" + _word_for_instance_in_error_message = "schema" + + +@attr.s(hash=True) +class RefResolutionError(Exception): + """ + A ref could not be resolved. + """ + + _cause = attr.ib() def __str__(self): + return str(self._cause) + + +class UndefinedTypeCheck(Exception): + """ + A type checker was asked to check a type it did not have registered. + """ + + def __init__(self, type): + self.type = type + + def __unicode__(self): + return "Type %r is unknown to this type checker" % self.type + + if PY3: + __str__ = __unicode__ + else: + def __str__(self): + return unicode(self).encode("utf-8") + + +class UnknownType(Exception): + """ + A validator was asked to validate an instance against an unknown type. + """ + + def __init__(self, type, instance, schema): + self.type = type + self.instance = instance + self.schema = schema + + def __unicode__(self): + pschema = pprint.pformat(self.schema, width=72) + pinstance = pprint.pformat(self.instance, width=72) + return textwrap.dedent(""" + Unknown type %r for validator with schema: + %s + + While checking instance: + %s + """.rstrip() + ) % (self.type, _utils.indent(pschema), _utils.indent(pinstance)) + + if PY3: + __str__ = __unicode__ + else: + def __str__(self): + return unicode(self).encode("utf-8") + + +class FormatError(Exception): + """ + Validating a format failed. + """ + + def __init__(self, message, cause=None): + super(FormatError, self).__init__(message, cause) + self.message = message + self.cause = self.__cause__ = cause + + def __unicode__(self): return self.message + if PY3: + __str__ = __unicode__ + else: + def __str__(self): + return self.message.encode("utf-8") + + +class ErrorTree(object): + """ + ErrorTrees make it easier to check which validations failed. + """ + + _instance = _unset + + def __init__(self, errors=()): + self.errors = {} + self._contents = defaultdict(self.__class__) + + for error in errors: + container = self + for element in error.path: + container = container[element] + container.errors[error.validator] = error + + container._instance = error.instance + + def __contains__(self, index): + """ + Check whether ``instance[index]`` has any errors. + """ + + return index in self._contents + + def __getitem__(self, index): + """ + Retrieve the child tree one level down at the given ``index``. + + If the index is not in the instance that this tree corresponds to and + is not known by this tree, whatever error would be raised by + ``instance.__getitem__`` will be propagated (usually this is some + subclass of `exceptions.LookupError`. + """ + + if self._instance is not _unset and index not in self: + self._instance[index] + return self._contents[index] + + def __setitem__(self, index, value): + """ + Add an error to the tree at the given ``index``. + """ + self._contents[index] = value + + def __iter__(self): + """ + Iterate (non-recursively) over the indices in the instance with errors. + """ + + return iter(self._contents) + + def __len__(self): + """ + Return the `total_errors`. + """ + return self.total_errors + + def __repr__(self): + return "<%s (%s total errors)>" % (self.__class__.__name__, len(self)) + + @property + def total_errors(self): + """ + The total number of errors in the entire tree, including children. + """ + + child_errors = sum(len(tree) for _, tree in iteritems(self._contents)) + return len(self.errors) + child_errors + + +def by_relevance(weak=WEAK_MATCHES, strong=STRONG_MATCHES): + """ + Create a key function that can be used to sort errors by relevance. + + Arguments: + weak (set): + a collection of validator names to consider to be "weak". + If there are two errors at the same level of the instance + and one is in the set of weak validator names, the other + error will take priority. By default, :validator:`anyOf` and + :validator:`oneOf` are considered weak validators and will + be superseded by other same-level validation errors. + + strong (set): + a collection of validator names to consider to be "strong" + """ + def relevance(error): + validator = error.validator + return -len(error.path), validator not in weak, validator in strong + return relevance + + +relevance = by_relevance() + + +def best_match(errors, key=relevance): + """ + Try to find an error that appears to be the best match among given errors. + + In general, errors that are higher up in the instance (i.e. for which + `ValidationError.path` is shorter) are considered better matches, + since they indicate "more" is wrong with the instance. + + If the resulting match is either :validator:`oneOf` or :validator:`anyOf`, + the *opposite* assumption is made -- i.e. the deepest error is picked, + since these validators only need to match once, and any other errors may + not be relevant. + + Arguments: + errors (collections.Iterable): + + the errors to select from. Do not provide a mixture of + errors from different validation attempts (i.e. from + different instances or schemas), since it won't produce + sensical output. + + key (collections.Callable): + + the key to use when sorting errors. See `relevance` and + transitively `by_relevance` for more details (the default is + to sort with the defaults of that function). Changing the + default is only useful if you want to change the function + that rates errors but still want the error context descent + done by this function. + + Returns: + the best matching error, or ``None`` if the iterable was empty + + .. note:: + + This function is a heuristic. Its return value may change for a given + set of inputs from version to version if better heuristics are added. + """ + errors = iter(errors) + best = next(errors, None) + if best is None: + return + best = max(itertools.chain([best], errors), key=key) -class InvalidInput(AppException): - pass + while best.context: + best = min(best.context, key=key) + return best diff --git a/src/superannotate_schemas/schemas/base.py b/src/superannotate_schemas/schemas/base.py deleted file mode 100644 index 288af07..0000000 --- a/src/superannotate_schemas/schemas/base.py +++ /dev/null @@ -1,300 +0,0 @@ -import datetime -from typing import Dict -from typing import List -from math import isnan -from typing import Optional -from typing import Union - -from pydantic import BaseModel as PyDanticBaseModel -from pydantic import conlist -from pydantic import constr -from pydantic import Extra -from pydantic import StrictFloat -from pydantic import StrictInt -from pydantic import StrictStr -from pydantic import StrictBool -from pydantic import Field -from pydantic import StrRegexError -from pydantic import ValidationError -from pydantic.error_wrappers import ErrorWrapper -from pydantic.errors import EnumMemberError -from pydantic import validator -from pydantic.validators import strict_str_validator -from pydantic.validators import strict_float_validator -from pydantic.validators import strict_int_validator -from pydantic.validators import number_multiple_validator -from pydantic.color import Color -from pydantic.color import ColorType - -from superannotate_schemas.schemas.enums import CreationTypeEnum -from superannotate_schemas.schemas.enums import BaseImageRoleEnum -from superannotate_schemas.schemas.enums import VectorAnnotationTypeEnum -from superannotate_schemas.schemas.enums import AnnotationStatusEnum -from superannotate_schemas.schemas.enums import ClassTypeEnum -from superannotate_schemas.schemas.enums import DocumentAnnotationTypeEnum -from superannotate_schemas.schemas.constances import DATE_REGEX -from superannotate_schemas.schemas.constances import DATE_TIME_FORMAT_ERROR_MESSAGE -from superannotate_schemas.schemas.constances import POINT_LABEL_VALUE_FORMAT_ERROR_MESSAGE -from superannotate_schemas.schemas.constances import POINT_LABEL_KEY_FORMAT_ERROR_MESSAGE -from superannotate_schemas.schemas.constances import INVALID_DICT_MESSAGE - - -def enum_error_handling(self) -> str: - permitted = ", ".join(repr(v.value) for v in self.enum_values) - return f"Invalid value, permitted: {permitted}" - - -EnumMemberError.__str__ = enum_error_handling - -NotEmptyStr = constr(strict=True, min_length=1) - -StrictNumber = Union[StrictInt, StrictFloat] - - -class EmailStr(StrictStr): - @classmethod - def validate(cls, value: Union[str]) -> Union[str]: - try: - constr( - regex=r"^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" - ).validate(value) - except StrRegexError: - raise ValueError("Invalid email") - return value - - -class BaseModel(PyDanticBaseModel): - - class Config: - extra = Extra.allow - use_enum_values = True - error_msg_templates = { - "type_error.integer": "integer type expected", - "type_error.string": "str type expected", - "value_error.missing": "field required", - } - - -class StrictPointNumber(BaseModel): - __root__: Union[StrictInt, StrictFloat] - - @classmethod - def __get_validators__(cls): - yield cls._validate_types - - @classmethod - def _validate_types(cls, value): - is_valid_float, is_valid_int = True, True - try: - cls._validate_float(value) - except TypeError: - is_valid_float = False - if not is_valid_float: - try: - cls._validate_int(value) - except TypeError: - is_valid_int = False - if is_valid_float or is_valid_int: - return value - raise TypeError("is not a valid number. Integer or float types are expected") - - @classmethod - def _validate_int(cls, value): - return strict_int_validator(value) - - @classmethod - def _validate_float(cls, value): - strict_float_validator(value) - return cls._validate_nan(value) - - @staticmethod - def _validate_nan(v): - if isnan(v): - raise TypeError("NaN is not a valid float") - return v - - -class AxisPoint(BaseModel): - x: StrictNumber - y: StrictNumber - - -class BaseAttribute(BaseModel): - id: Optional[StrictInt] - group_id: Optional[StrictInt] = Field(alias="groupId") - name: Optional[NotEmptyStr] - group_name: Optional[NotEmptyStr] = Field(alias="groupName") - - -class Tag(BaseModel): - __root__: NotEmptyStr - - -class BboxPoints(BaseModel): - x1: StrictNumber - x2: StrictNumber - y1: StrictNumber - y2: StrictNumber - - -class TimedBaseModel(BaseModel): - created_at: Optional[constr(regex=DATE_REGEX)] = Field(None, alias="createdAt") - updated_at: Optional[constr(regex=DATE_REGEX)] = Field(None, alias="updatedAt") - - @validator("created_at", "updated_at", pre=True, always=True) - def validate_created_at(cls, value): - if value: - try: - if value is not None: - constr(regex=DATE_REGEX, strict=True).validate(value) - except (TypeError, StrRegexError): - raise TypeError(DATE_TIME_FORMAT_ERROR_MESSAGE) - return value - else: - return datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" - - -class UserAction(BaseModel): - email: EmailStr - role: BaseImageRoleEnum - - -class TrackableModel(BaseModel): - created_by: Optional[UserAction] = Field(None, alias="createdBy") - updated_by: Optional[UserAction] = Field(None, alias="updatedBy") - creation_type: Optional[CreationTypeEnum] = Field( - CreationTypeEnum.PRE_ANNOTATION.value, alias="creationType" - ) - - @validator("creation_type", always=True) - def clean_creation_type(cls, _): - return CreationTypeEnum.PRE_ANNOTATION.value - - -class LastUserAction(BaseModel): - email: EmailStr - timestamp: StrictInt - - -class BaseInstance(TrackableModel, TimedBaseModel): - class_id: Optional[StrictInt] = Field(None, alias="classId") - class_name: Optional[NotEmptyStr] = Field(None, alias="className") - - -class BaseInstanceTagAttribute(BaseAttribute): - name: NotEmptyStr - group_name: NotEmptyStr = Field(alias="groupName") - - -class BaseInstanceTag(BaseInstance): - type: ClassTypeEnum - probability: Optional[StrictInt] = Field(100) - attributes: Optional[List[BaseInstanceTagAttribute]] = Field(list()) - class_name: NotEmptyStr = Field(alias="className") - - -class BaseMetadata(BaseModel): - name: NotEmptyStr - url: Optional[StrictStr] - status: Optional[AnnotationStatusEnum] - annotator_email: Optional[EmailStr] = Field(None, alias="annotatorEmail") - qa_email: Optional[EmailStr] = Field(None, alias="qaEmail") - last_action: Optional[LastUserAction] = Field(None, alias="lastAction") - project_id: Optional[StrictInt] = Field(None, alias="projectId") - - -class BaseImageMetadata(BaseMetadata): - width: Optional[StrictInt] - height: Optional[StrictInt] - pinned: Optional[StrictBool] - - -class Correspondence(BaseModel): - text: NotEmptyStr - email: EmailStr - - -class Comment(TimedBaseModel, TrackableModel): - x: Union[StrictInt, StrictFloat] - y: Union[StrictInt, StrictFloat] - resolved: Optional[StrictBool] = Field(False) - correspondence: conlist(Correspondence, min_items=1) - - -class BaseImageAnnotationInstance(BaseInstance): - visible: Optional[StrictBool] - locked: Optional[StrictBool] - probability: Optional[StrictInt] = Field(100) - attributes: Optional[List[BaseAttribute]] = Field(list()) - error: Optional[StrictBool] - - class Config: - error_msg_templates = { - "value_error.missing": "field required for annotation", - } - - -class StringA(BaseModel): - string: StrictStr - - -class PointLabels(BaseModel): - __root__: Dict[constr(regex=r"^[0-9]+$", min_length=1, strict=True), StrictStr] - - @classmethod - def __get_validators__(cls): - yield cls.validate_type - yield cls.validate_value - - @validator("__root__", pre=True) - def validate_value(cls, values): - result = {} - errors = [] - validate_key, validate_value = None, None - for key, value in values.items(): - try: - validate_key = constr(regex=r"^[0-9]+$", min_length=1, strict=True).validate(key) - except ValueError: - errors.append( - ErrorWrapper( - ValueError(POINT_LABEL_KEY_FORMAT_ERROR_MESSAGE), str(key) - ) - ) - try: - validate_value = strict_str_validator(value) - except ValueError: - errors.append( - ErrorWrapper( - ValueError(POINT_LABEL_VALUE_FORMAT_ERROR_MESSAGE), str(key) - ) - ) - if validate_key and validate_value: - result.update({validate_key: validate_value}) - if errors: - raise ValidationError(errors, cls) - return result - - @classmethod - def validate_type(cls, values): - if not issubclass(type(values), dict): - raise TypeError(INVALID_DICT_MESSAGE) - return values - - -class BaseVectorInstance(BaseImageAnnotationInstance): - type: VectorAnnotationTypeEnum - point_labels: Optional[PointLabels] = Field(alias="pointLabels") - tracking_id: Optional[str] = Field(alias="trackingId") - group_id: Optional[int] = Field(alias="groupId") - - -class BaseDocumentInstance(BaseInstance): - type: DocumentAnnotationTypeEnum - - -class HexColor(BaseModel): - __root__: ColorType - - @validator("__root__") - def validate_color(cls, v): - return '#{:02X}{:02X}{:02X}'.format(*Color(v).as_rgb_tuple()) diff --git a/src/superannotate_schemas/schemas/classes.py b/src/superannotate_schemas/schemas/classes.py deleted file mode 100644 index 9360be7..0000000 --- a/src/superannotate_schemas/schemas/classes.py +++ /dev/null @@ -1,52 +0,0 @@ -from typing import Optional -from typing import List - -from superannotate_schemas.schemas.base import BaseModel -from superannotate_schemas.schemas.base import TimedBaseModel -from superannotate_schemas.schemas.base import StrictInt -from superannotate_schemas.schemas.base import StrictStr -from superannotate_schemas.schemas.base import HexColor -from superannotate_schemas.schemas.enums import ClassTypeEnum - - -class Attribute(TimedBaseModel): - id: Optional[StrictInt] - group_id: Optional[StrictInt] - project_id: Optional[StrictInt] - name: StrictStr - count: Optional[StrictInt] - - def __hash__(self): - return hash(f"{self.id}{self.group_id}{self.name}") - - -class AttributeGroup(TimedBaseModel): - id: Optional[StrictInt] - class_id: Optional[StrictInt] - name: StrictStr - is_multiselect: Optional[bool] = False - attributes: List[Attribute] - - def __hash__(self): - return hash(f"{self.id}{self.class_id}{self.name}") - - -class AnnotationClass(TimedBaseModel): - id: Optional[StrictInt] - project_id: Optional[StrictInt] - type: ClassTypeEnum = ClassTypeEnum.OBJECT - name: StrictStr - color: HexColor - count: Optional[StrictInt] - attribute_groups: List[AttributeGroup] - - def __hash__(self): - return hash(f"{self.id}{self.type}{self.name}") - - class Config: - validate_assignment = True - use_enum_values = True - - -class AnnotationClasses(BaseModel): - __root__: List[AnnotationClass] diff --git a/src/superannotate_schemas/schemas/constances.py b/src/superannotate_schemas/schemas/constances.py deleted file mode 100644 index 56063f3..0000000 --- a/src/superannotate_schemas/schemas/constances.py +++ /dev/null @@ -1,5 +0,0 @@ -DATE_REGEX = r"\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d{3})Z" -DATE_TIME_FORMAT_ERROR_MESSAGE = "does not match expected format YYYY-MM-DDTHH:MM:SS.fffZ" -POINT_LABEL_VALUE_FORMAT_ERROR_MESSAGE = "str type expected" -POINT_LABEL_KEY_FORMAT_ERROR_MESSAGE = "does not match expected format ^[0-9]+$" -INVALID_DICT_MESSAGE = "value is not a valid dict" diff --git a/src/superannotate_schemas/schemas/custom_types.py b/src/superannotate_schemas/schemas/custom_types.py deleted file mode 100644 index 36524ca..0000000 --- a/src/superannotate_schemas/schemas/custom_types.py +++ /dev/null @@ -1,109 +0,0 @@ -from typing import Any -from typing import TypeVar - -from superannotate_schemas.schemas.base import BaseModel -from pydantic import ValidationError -from pydantic.error_wrappers import ErrorWrapper - - -class CustomFieldMeta(type): - def __getitem__(self, key_value_type: Any): - return type('CustomFieldValue', (self,), {'key_value_type': key_value_type}) - - -T = TypeVar("T") - - -# -# class Strict(Generic[T]): -# __type_like__: T -# -# @staticmethod -# def _display_type(v: Any) -> str: -# try: -# return v.__name__ -# except AttributeError: -# return str(v).replace("typing.", "") -# -# @classmethod -# def __class_getitem__(cls, type_like: T) -> T: -# new_cls = new_class( -# f"Strict[{cls._display_type(type_like)}]", -# (cls,), -# {}, -# lambda ns: ns.update({"__type_like__": type_like}), -# ) -# return cast(T, new_cls) -# -# @classmethod -# def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]: -# yield cls.validate -# -# @classmethod -# def validate(cls, value: Any) -> T: -# if not isinstancex(value, cls.__type_like__): -# raise TypeError(f"{value!r} is not of valid type") -# return value - -class StrictType(BaseModel): - class Config: - key_field = "type" - - -class StrictDict(metaclass=CustomFieldMeta): - key_value_type: Any = None - - @classmethod - def __get_validators__(cls): - yield cls.validate - - @classmethod - def validate(cls, v): - - if not isinstance(v, dict): - raise TypeError("type dict expected") - - key_type, value_type = cls.key_value_type - errors = [] - - for key in v: - try: - key_type.validate(key) - except TypeError as e: - errors.append(ErrorWrapper(exc=e, loc="")) - - try: - value_type.validate((v[key])) - except TypeError as e: - errors.append(ErrorWrapper(exc=e, loc="")) - - if errors: - raise ValidationError(errors=errors, model=cls) - return v - - -class BaseDiscriminatedInstances(BaseModel): - __root__: Any - - class Config: - DISCRIMINATOR = "type" - - @classmethod - def __get_validators__(cls): - yield cls.return_instance - - @classmethod - def return_instance(cls, values): - try: - discriminator = values[cls.Config.DISCRIMINATOR] - except KeyError: - raise TypeError( - f"Missing required 'type' field for instance: {values}" - ) - try: - for instance in cls.__fields__["__root__"].type_.__dict__["__args__"]: - if discriminator in instance.__fields__.get(cls.Config.DISCRIMINATOR).type_.__dict__["__args__"]: - return instance(**values) - raise TypeError(f"Incorrect type: {discriminator}") - except KeyError: - raise TypeError(f"Incorrect type: {discriminator}") diff --git a/src/superannotate_schemas/schemas/draft3.json b/src/superannotate_schemas/schemas/draft3.json new file mode 100644 index 0000000..f8a09c5 --- /dev/null +++ b/src/superannotate_schemas/schemas/draft3.json @@ -0,0 +1,199 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "dependencies": { + "exclusiveMaximum": "maximum", + "exclusiveMinimum": "minimum" + }, + "id": "http://json-schema.org/draft-03/schema#", + "properties": { + "$ref": { + "format": "uri", + "type": "string" + }, + "$schema": { + "format": "uri", + "type": "string" + }, + "additionalItems": { + "default": {}, + "type": [ + { + "$ref": "#" + }, + "boolean" + ] + }, + "additionalProperties": { + "default": {}, + "type": [ + { + "$ref": "#" + }, + "boolean" + ] + }, + "default": { + "type": "any" + }, + "dependencies": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": [ + "string", + "array", + { + "$ref": "#" + } + ] + }, + "default": {}, + "type": [ + "string", + "array", + "object" + ] + }, + "description": { + "type": "string" + }, + "disallow": { + "items": { + "type": [ + "string", + { + "$ref": "#" + } + ] + }, + "type": [ + "string", + "array" + ], + "uniqueItems": true + }, + "divisibleBy": { + "default": 1, + "exclusiveMinimum": true, + "minimum": 0, + "type": "number" + }, + "enum": { + "type": "array" + }, + "exclusiveMaximum": { + "default": false, + "type": "boolean" + }, + "exclusiveMinimum": { + "default": false, + "type": "boolean" + }, + "extends": { + "default": {}, + "items": { + "$ref": "#" + }, + "type": [ + { + "$ref": "#" + }, + "array" + ] + }, + "format": { + "type": "string" + }, + "id": { + "format": "uri", + "type": "string" + }, + "items": { + "default": {}, + "items": { + "$ref": "#" + }, + "type": [ + { + "$ref": "#" + }, + "array" + ] + }, + "maxDecimal": { + "minimum": 0, + "type": "number" + }, + "maxItems": { + "minimum": 0, + "type": "integer" + }, + "maxLength": { + "type": "integer" + }, + "maximum": { + "type": "number" + }, + "minItems": { + "default": 0, + "minimum": 0, + "type": "integer" + }, + "minLength": { + "default": 0, + "minimum": 0, + "type": "integer" + }, + "minimum": { + "type": "number" + }, + "pattern": { + "format": "regex", + "type": "string" + }, + "patternProperties": { + "additionalProperties": { + "$ref": "#" + }, + "default": {}, + "type": "object" + }, + "properties": { + "additionalProperties": { + "$ref": "#", + "type": "object" + }, + "default": {}, + "type": "object" + }, + "required": { + "default": false, + "type": "boolean" + }, + "title": { + "type": "string" + }, + "type": { + "default": "any", + "items": { + "type": [ + "string", + { + "$ref": "#" + } + ] + }, + "type": [ + "string", + "array" + ], + "uniqueItems": true + }, + "uniqueItems": { + "default": false, + "type": "boolean" + } + }, + "type": "object" +} diff --git a/src/superannotate_schemas/schemas/draft4.json b/src/superannotate_schemas/schemas/draft4.json new file mode 100644 index 0000000..9b666cf --- /dev/null +++ b/src/superannotate_schemas/schemas/draft4.json @@ -0,0 +1,222 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "default": {}, + "definitions": { + "positiveInteger": { + "minimum": 0, + "type": "integer" + }, + "positiveIntegerDefault0": { + "allOf": [ + { + "$ref": "#/definitions/positiveInteger" + }, + { + "default": 0 + } + ] + }, + "schemaArray": { + "items": { + "$ref": "#" + }, + "minItems": 1, + "type": "array" + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + } + }, + "dependencies": { + "exclusiveMaximum": [ + "maximum" + ], + "exclusiveMinimum": [ + "minimum" + ] + }, + "description": "Core schema meta-schema", + "id": "http://json-schema.org/draft-04/schema#", + "properties": { + "$schema": { + "format": "uri", + "type": "string" + }, + "additionalItems": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#" + } + ], + "default": {} + }, + "additionalProperties": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#" + } + ], + "default": {} + }, + "allOf": { + "$ref": "#/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/schemaArray" + }, + "default": {}, + "definitions": { + "additionalProperties": { + "$ref": "#" + }, + "default": {}, + "type": "object" + }, + "dependencies": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/stringArray" + } + ] + }, + "type": "object" + }, + "description": { + "type": "string" + }, + "enum": { + "type": "array" + }, + "exclusiveMaximum": { + "default": false, + "type": "boolean" + }, + "exclusiveMinimum": { + "default": false, + "type": "boolean" + }, + "format": { + "type": "string" + }, + "id": { + "format": "uri", + "type": "string" + }, + "items": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/schemaArray" + } + ], + "default": {} + }, + "maxItems": { + "$ref": "#/definitions/positiveInteger" + }, + "maxLength": { + "$ref": "#/definitions/positiveInteger" + }, + "maxProperties": { + "$ref": "#/definitions/positiveInteger" + }, + "maximum": { + "type": "number" + }, + "minItems": { + "$ref": "#/definitions/positiveIntegerDefault0" + }, + "minLength": { + "$ref": "#/definitions/positiveIntegerDefault0" + }, + "minProperties": { + "$ref": "#/definitions/positiveIntegerDefault0" + }, + "minimum": { + "type": "number" + }, + "multipleOf": { + "exclusiveMinimum": true, + "minimum": 0, + "type": "number" + }, + "not": { + "$ref": "#" + }, + "oneOf": { + "$ref": "#/definitions/schemaArray" + }, + "pattern": { + "format": "regex", + "type": "string" + }, + "patternProperties": { + "additionalProperties": { + "$ref": "#" + }, + "default": {}, + "type": "object" + }, + "properties": { + "additionalProperties": { + "$ref": "#" + }, + "default": {}, + "type": "object" + }, + "required": { + "$ref": "#/definitions/stringArray" + }, + "title": { + "type": "string" + }, + "type": { + "anyOf": [ + { + "$ref": "#/definitions/simpleTypes" + }, + { + "items": { + "$ref": "#/definitions/simpleTypes" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + } + ] + }, + "uniqueItems": { + "default": false, + "type": "boolean" + } + }, + "type": "object" +} diff --git a/src/superannotate_schemas/schemas/draft6.json b/src/superannotate_schemas/schemas/draft6.json new file mode 100644 index 0000000..a0d2bf7 --- /dev/null +++ b/src/superannotate_schemas/schemas/draft6.json @@ -0,0 +1,153 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://json-schema.org/draft-06/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "examples": { + "type": "array", + "items": {} + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": {}, + "enum": { + "type": "array" + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": {} +} diff --git a/src/superannotate_schemas/schemas/draft7.json b/src/superannotate_schemas/schemas/draft7.json new file mode 100644 index 0000000..746cde9 --- /dev/null +++ b/src/superannotate_schemas/schemas/draft7.json @@ -0,0 +1,166 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://json-schema.org/draft-07/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": true + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": true, + "enum": { + "type": "array", + "items": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "contentMediaType": { "type": "string" }, + "contentEncoding": { "type": "string" }, + "if": {"$ref": "#"}, + "then": {"$ref": "#"}, + "else": {"$ref": "#"}, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": true +} diff --git a/src/superannotate_schemas/schemas/enums.py b/src/superannotate_schemas/schemas/enums.py deleted file mode 100644 index 24ac0fc..0000000 --- a/src/superannotate_schemas/schemas/enums.py +++ /dev/null @@ -1,51 +0,0 @@ -from enum import Enum - - -class VectorAnnotationTypeEnum(str, Enum): - BBOX = "bbox" - ELLIPSE = "ellipse" - TEMPLATE = "template" - CUBOID = "cuboid" - POLYLINE = "polyline" - POLYGON = "polygon" - POINT = "point" - RBBOX = "rbbox" - TAG = "tag" - - -class DocumentAnnotationTypeEnum(str, Enum): - ENTITY = "entity" - TAG = "tag" - - -class CreationTypeEnum(str, Enum): - MANUAL = "Manual" - PREDICTION = "Prediction" - PRE_ANNOTATION = "Preannotation" - - -class AnnotationStatusEnum(str, Enum): - NOT_STARTED = "NotStarted" - IN_PROGRESS = "InProgress" - QUALITY_CHECK = "QualityCheck" - RETURNED = "Returned" - COMPLETED = "Completed" - SKIPPED = "Skipped" - - -class BaseRoleEnum(str, Enum): - ADMIN = "Admin" - ANNOTATOR = "Annotator" - QA = "QA" - - -class BaseImageRoleEnum(str, Enum): - CUSTOMER = "Customer" - ADMIN = "Admin" - ANNOTATOR = "Annotator" - QA = "QA" - - -class ClassTypeEnum(str, Enum): - OBJECT = "object" - TAG = "tag" diff --git a/src/superannotate_schemas/schemas/external/__init__.py b/src/superannotate_schemas/schemas/external/__init__.py deleted file mode 100644 index 4e9e0f1..0000000 --- a/src/superannotate_schemas/schemas/external/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from superannotate_schemas.schemas.external.video import VideoAnnotation -from superannotate_schemas.schemas.external.document import DocumentAnnotation -from superannotate_schemas.schemas.external.pixel import PixelAnnotation -from superannotate_schemas.schemas.external.vector import VectorAnnotation - -__all__ = [ - "DocumentAnnotation", - "VideoAnnotation", - "PixelAnnotation", - "VectorAnnotation" -] diff --git a/src/superannotate_schemas/schemas/external/document.py b/src/superannotate_schemas/schemas/external/document.py deleted file mode 100644 index 23109d4..0000000 --- a/src/superannotate_schemas/schemas/external/document.py +++ /dev/null @@ -1,83 +0,0 @@ -from typing import List -from typing import Optional -from typing import Union - -from pydantic import Field -from pydantic import StrictStr -from pydantic import ValidationError -from pydantic.error_wrappers import ErrorWrapper - -from superannotate_schemas.schemas.base import BaseAttribute -from superannotate_schemas.schemas.base import BaseDocumentInstance -from superannotate_schemas.schemas.base import BaseMetadata as Metadata -from superannotate_schemas.schemas.base import BaseModel -from superannotate_schemas.schemas.base import INVALID_DICT_MESSAGE -from superannotate_schemas.schemas.base import NotEmptyStr -from superannotate_schemas.schemas.base import Tag -from superannotate_schemas.schemas.enums import DocumentAnnotationTypeEnum - - -class Attribute(BaseAttribute): - name: NotEmptyStr - group_name: NotEmptyStr = Field(alias="groupName") - - -class EntityInstance(BaseDocumentInstance): - start: int - end: int - attributes: Optional[List[Attribute]] = Field(list()) - - -class TagInstance(BaseDocumentInstance): - attributes: Optional[List[Attribute]] = Field(list()) - class_name: NotEmptyStr = Field(alias="className") - - -class DocumentInstance(BaseDocumentInstance): - pass - - -ANNOTATION_TYPES = { - DocumentAnnotationTypeEnum.ENTITY: EntityInstance, - DocumentAnnotationTypeEnum.TAG: TagInstance, -} - - -class AnnotationInstance(BaseModel): - __root__: Union[TagInstance, EntityInstance] - - @classmethod - def __get_validators__(cls): - yield cls.return_action - - @classmethod - def return_action(cls, values): - try: - try: - instance_type = values["type"] - except KeyError: - raise ValidationError( - [ErrorWrapper(ValueError("field required"), "type")], cls - ) - return ANNOTATION_TYPES[instance_type](**values) - except KeyError: - raise ValidationError( - [ - ErrorWrapper( - ValueError( - f"invalid type, valid types are {', '.join(ANNOTATION_TYPES.keys())}" - ), - "type", - ) - ], - cls, - ) - except TypeError as e: - raise TypeError(INVALID_DICT_MESSAGE) from e - - -class DocumentAnnotation(BaseModel): - metadata: Metadata - instances: Optional[List[AnnotationInstance]] = Field(list()) - tags: Optional[List[Tag]] = Field(list()) - free_text: Optional[StrictStr] = Field(None, alias="freeText") diff --git a/src/superannotate_schemas/schemas/external/pixel.py b/src/superannotate_schemas/schemas/external/pixel.py deleted file mode 100644 index 7d0ef06..0000000 --- a/src/superannotate_schemas/schemas/external/pixel.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import List -from typing import Optional - -from superannotate_schemas.schemas.base import BaseImageAnnotationInstance -from superannotate_schemas.schemas.base import BaseAttribute -from superannotate_schemas.schemas.base import BaseImageMetadata -from superannotate_schemas.schemas.base import NotEmptyStr -from superannotate_schemas.schemas.base import HexColor -from superannotate_schemas.schemas.base import Tag -from superannotate_schemas.schemas.base import Comment - -from superannotate_schemas.schemas.base import BaseModel -from pydantic import Field - - -class Attribute(BaseAttribute): - name: NotEmptyStr - group_name: NotEmptyStr = Field(alias="groupName") - - -class MetaData(BaseImageMetadata): - is_segmented: Optional[bool] = Field(alias="isSegmented") - is_predicted: Optional[bool] = Field(alias="isPredicted") - - -class AnnotationPart(BaseModel): - color: HexColor - - -class AnnotationInstance(BaseImageAnnotationInstance): - parts: List[AnnotationPart] - attributes: Optional[List[Attribute]] = Field(list()) - - -class PixelAnnotation(BaseModel): - metadata: MetaData - instances: List[AnnotationInstance] - tags: Optional[List[Tag]] = Field(list()) - comments: Optional[List[Comment]] = Field(list()) diff --git a/src/superannotate_schemas/schemas/external/vector.py b/src/superannotate_schemas/schemas/external/vector.py deleted file mode 100644 index 7efd2d9..0000000 --- a/src/superannotate_schemas/schemas/external/vector.py +++ /dev/null @@ -1,167 +0,0 @@ -from typing import List -from typing import Optional -from typing import Union - -from pydantic import Field -from pydantic import StrictInt -from pydantic import StrictStr -from pydantic import ValidationError -from pydantic import conlist -from pydantic.error_wrappers import ErrorWrapper - -from superannotate_schemas.schemas.base import AxisPoint -from superannotate_schemas.schemas.base import BaseAttribute -from superannotate_schemas.schemas.base import BaseImageMetadata -from superannotate_schemas.schemas.base import BaseInstanceTag -from superannotate_schemas.schemas.base import BaseModel -from superannotate_schemas.schemas.base import BaseVectorInstance -from superannotate_schemas.schemas.base import BboxPoints -from superannotate_schemas.schemas.base import Comment -from superannotate_schemas.schemas.base import INVALID_DICT_MESSAGE -from superannotate_schemas.schemas.base import NotEmptyStr -from superannotate_schemas.schemas.base import StrictNumber -from superannotate_schemas.schemas.base import StrictPointNumber -from superannotate_schemas.schemas.base import Tag -from superannotate_schemas.schemas.enums import VectorAnnotationTypeEnum - - -class InstanceTag(BaseInstanceTag): - pass - - -class Attribute(BaseAttribute): - name: NotEmptyStr - group_name: NotEmptyStr = Field(alias="groupName") - - -class VectorInstance(BaseVectorInstance): - attributes: Optional[List[Attribute]] = Field(list()) - - -class Metadata(BaseImageMetadata): - is_predicted: Optional[bool] = Field(alias="isPredicted") - - -class Point(VectorInstance, AxisPoint): - pass - - -class PolyLine(VectorInstance): - points: List[StrictPointNumber] - - -class Polygon(VectorInstance): - points: conlist(StrictPointNumber, min_items=3) - exclude: Optional[List[List[StrictPointNumber]]] = [] - - -class Bbox(VectorInstance): - points: BboxPoints - - -class RotatedBoxPoints(BaseModel): - x1: StrictNumber - y1: StrictNumber - x2: StrictNumber - y2: StrictNumber - x3: StrictNumber - y3: StrictNumber - x4: StrictNumber - y4: StrictNumber - - -class RotatedBox(VectorInstance): - points: RotatedBoxPoints - - -class Ellipse(VectorInstance): - cx: StrictNumber - cy: StrictNumber - rx: StrictNumber - ry: StrictNumber - angle: StrictNumber - - -class TemplatePoint(BaseModel): - id: StrictInt - x: StrictNumber - y: StrictNumber - - -class TemplateConnection(BaseModel): - id: StrictInt - from_connection: StrictInt = Field(alias="from") - to_connection: StrictInt = Field(alias="to") - - -class Template(VectorInstance): - points: conlist(TemplatePoint, min_items=1) - connections: List[TemplateConnection] - template_id: Optional[StrictInt] = Field(None, alias="templateId") - template_name: StrictStr = Field(alias="templateName") - - -class CuboidPoint(BaseModel): - f1: AxisPoint - f2: AxisPoint - r1: AxisPoint - r2: AxisPoint - - -class Cuboid(VectorInstance): - points: CuboidPoint - - -ANNOTATION_TYPES = { - VectorAnnotationTypeEnum.BBOX: Bbox, - VectorAnnotationTypeEnum.TEMPLATE: Template, - VectorAnnotationTypeEnum.CUBOID: Cuboid, - VectorAnnotationTypeEnum.POLYGON: Polygon, - VectorAnnotationTypeEnum.POINT: Point, - VectorAnnotationTypeEnum.POLYLINE: PolyLine, - VectorAnnotationTypeEnum.ELLIPSE: Ellipse, - VectorAnnotationTypeEnum.RBBOX: RotatedBox, - VectorAnnotationTypeEnum.TAG: InstanceTag, -} - - -class AnnotationInstance(BaseModel): - __root__: Union[ - Template, Cuboid, Point, PolyLine, Polygon, Bbox, Ellipse, RotatedBox, InstanceTag - ] - - @classmethod - def __get_validators__(cls): - yield cls.return_action - - @classmethod - def return_action(cls, values): - try: - try: - instance_type = values["type"] - except KeyError: - raise ValidationError( - [ErrorWrapper(ValueError("field required"), "type")], cls - ) - return ANNOTATION_TYPES[instance_type](**values) - except KeyError: - raise ValidationError( - [ - ErrorWrapper( - ValueError( - f"invalid type, valid types are {', '.join(ANNOTATION_TYPES.keys())}" - ), - "type", - ) - ], - cls, - ) - except TypeError as e: - raise TypeError(INVALID_DICT_MESSAGE) from e - - -class VectorAnnotation(BaseModel): - metadata: Metadata - comments: Optional[List[Comment]] = Field(list()) - tags: Optional[List[Tag]] = Field(list()) - instances: Optional[List[AnnotationInstance]] = Field(list()) diff --git a/src/superannotate_schemas/schemas/external/video.py b/src/superannotate_schemas/schemas/external/video.py deleted file mode 100644 index 1b45a0d..0000000 --- a/src/superannotate_schemas/schemas/external/video.py +++ /dev/null @@ -1,222 +0,0 @@ -from enum import Enum -from typing import Dict -from typing import List -from typing import Optional -from typing import Union - -from pydantic import Field -from pydantic import StrictBool -from pydantic import StrictInt -from pydantic import StrictStr -from pydantic import ValidationError -from pydantic import conlist -from pydantic.error_wrappers import ErrorWrapper - -from superannotate_schemas.schemas.base import BaseAttribute -from superannotate_schemas.schemas.base import BaseInstance -from superannotate_schemas.schemas.base import BaseMetadata -from superannotate_schemas.schemas.base import BaseModel -from superannotate_schemas.schemas.base import BboxPoints -from superannotate_schemas.schemas.base import INVALID_DICT_MESSAGE -from superannotate_schemas.schemas.base import NotEmptyStr -from superannotate_schemas.schemas.base import PointLabels -from superannotate_schemas.schemas.base import StrictNumber -from superannotate_schemas.schemas.base import StrictPointNumber -from superannotate_schemas.schemas.base import Tag -from superannotate_schemas.schemas.enums import AnnotationStatusEnum - - -class Attribute(BaseAttribute): - name: NotEmptyStr - group_name: NotEmptyStr = Field(alias="groupName") - - -class VideoType(str, Enum): - EVENT = "event" - BBOX = "bbox" - POINT = "point" - POLYGON = "polygon" - POLYLINE = "polyline" - - -class MetaData(BaseMetadata): - width: Optional[StrictInt] - height: Optional[StrictInt] - status: Optional[AnnotationStatusEnum] - duration: Optional[StrictInt] - error: Optional[StrictBool] - - -class BaseTimeStamp(BaseModel): - timestamp: StrictInt - attributes: Optional[List[Attribute]] - - -class BboxTimeStamp(BaseTimeStamp): - points: BboxPoints - - -class PointTimeStamp(BaseTimeStamp): - x: StrictNumber - y: StrictNumber - - -class PolylineTimestamp(BaseTimeStamp): - points: conlist(StrictPointNumber, min_items=4) - - -class PolygonTimestamp(BaseTimeStamp): - points: conlist(StrictPointNumber, min_items=6) - - -class EventTimeStamp(BaseTimeStamp): - pass - - -class InstanceMetadata(BaseInstance): - type: VideoType - start: StrictInt - end: StrictInt - - class Config: - fields = {"creation_type": {"exclude": True}} - - -class BBoxInstanceMetadata(InstanceMetadata): - type: StrictStr = Field(VideoType.BBOX, const=True) - point_labels: Optional[PointLabels] = Field(alias="pointLabels") - - -class PolygonInstanceMetadata(InstanceMetadata): - type: StrictStr = Field(VideoType.POLYGON, const=True) - point_labels: Optional[PointLabels] = Field(alias="pointLabels") - - -class PolylineInstanceMetadata(InstanceMetadata): - type: StrictStr = Field(VideoType.POLYLINE, const=True) - point_labels: Optional[PointLabels] = Field(alias="pointLabels") - - -class PointInstanceMetadata(InstanceMetadata): - type: StrictStr = Field(VideoType.POINT, const=True) - point_labels: Optional[PointLabels] = Field(alias="pointLabels") - - -class EventInstanceMetadata(InstanceMetadata): - type: StrictStr = Field(VideoType.EVENT, const=True) - - -class BaseVideoInstance(BaseModel): - metadata: InstanceMetadata - id: Optional[str] - type: VideoType - locked: Optional[bool] - timeline: Dict[float, BaseTimeStamp] - - -class BaseParameter(BaseModel): - start: StrictInt - end: StrictInt - - -class BboxParameter(BaseParameter): - timestamps: conlist(BboxTimeStamp, min_items=2) - - -class PolygonParameter(BaseParameter): - timestamps: conlist(PolygonTimestamp, min_items=2) - - -class PolylineParameter(BaseParameter): - timestamps: conlist(PolylineTimestamp, min_items=2) - - -class PointParameter(BaseParameter): - timestamps: conlist(PointTimeStamp, min_items=2) - - -class EventParameter(BaseParameter): - timestamps: conlist(EventTimeStamp, min_items=2) - - -class BboxInstance(BaseModel): - meta: BBoxInstanceMetadata - parameters: conlist(BboxParameter, min_items=1) - - -class PointInstance(BaseModel): - meta: PointInstanceMetadata - parameters: conlist(PointParameter, min_items=1) - - -class PolygonInstance(BaseModel): - meta: PolygonInstanceMetadata - parameters: conlist(PolygonParameter, min_items=1) - - -class PolylineInstance(BaseModel): - meta: PolylineInstanceMetadata - parameters: conlist(PolylineParameter, min_items=1) - - -class EventInstance(BaseModel): - meta: EventInstanceMetadata - parameters: conlist(EventParameter, min_items=1) - - -ANNOTATION_TYPES = { - VideoType.BBOX: BboxInstance, - VideoType.EVENT: EventInstance, - VideoType.POINT: PointInstance, - VideoType.POLYGON: PolygonInstance, - VideoType.POLYLINE: PolylineInstance -} - - -class AnnotationInstance(BaseModel): - __root__: Union[ - BboxInstance, EventInstance, PointInstance, PolylineInstance, PolygonInstance - ] - - @classmethod - def __get_validators__(cls): - yield cls.return_action - - @classmethod - def return_action(cls, values): - - try: - meta = values.get("meta") - if not meta: - raise ValidationError( - [ErrorWrapper(ValueError("field required"), "meta")], cls - ) - try: - instance_type = meta["type"] - except KeyError: - raise ValidationError( - [ErrorWrapper(ValueError("field required"), "meta.type")], cls - ) - return ANNOTATION_TYPES[instance_type](**values) - except KeyError: - raise ValidationError( - [ - ErrorWrapper( - ValueError( - f"invalid type, valid types are {', '.join(ANNOTATION_TYPES.keys())}" - ), - "meta.type", - ) - ], - cls, - ) - except TypeError as e: - raise ValidationError( - [ErrorWrapper(ValueError(INVALID_DICT_MESSAGE), "meta")], cls - ) - - -class VideoAnnotation(BaseModel): - metadata: MetaData - instances: Optional[List[AnnotationInstance]] = Field(list()) - tags: Optional[List[Tag]] = Field(list()) diff --git a/src/superannotate_schemas/schemas/internal/__init__.py b/src/superannotate_schemas/schemas/internal/__init__.py deleted file mode 100644 index d3e692c..0000000 --- a/src/superannotate_schemas/schemas/internal/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from superannotate_schemas.schemas.internal.pixel import PixelAnnotation -from superannotate_schemas.schemas.internal.vector import VectorAnnotation -from superannotate_schemas.schemas.internal.video import VideoAnnotation -from superannotate_schemas.schemas.internal.document import DocumentAnnotation - - -__all__ = [ - "DocumentAnnotation", - "VideoAnnotation", - "PixelAnnotation", - "VectorAnnotation" -] \ No newline at end of file diff --git a/src/superannotate_schemas/schemas/internal/document.py b/src/superannotate_schemas/schemas/internal/document.py deleted file mode 100644 index 14d9775..0000000 --- a/src/superannotate_schemas/schemas/internal/document.py +++ /dev/null @@ -1,83 +0,0 @@ -from typing import List -from typing import Optional -from typing import Union - -from superannotate_schemas.schemas.base import BaseAttribute -from superannotate_schemas.schemas.enums import DocumentAnnotationTypeEnum -from superannotate_schemas.schemas.base import BaseDocumentInstance -from superannotate_schemas.schemas.base import INVALID_DICT_MESSAGE -from superannotate_schemas.schemas.base import BaseMetadata as Metadata -from superannotate_schemas.schemas.base import Tag -from superannotate_schemas.schemas.base import StrictInt - -from superannotate_schemas.schemas.base import BaseModel - -from pydantic import Field -from pydantic import StrictStr -from pydantic.error_wrappers import ErrorWrapper -from pydantic import ValidationError - - -class Attribute(BaseAttribute): - id: StrictInt - group_id: StrictInt = Field(None, alias="groupId") - - -class EntityInstance(BaseDocumentInstance): - start: int - end: int - attributes: Optional[List[Attribute]] = Field(list()) - - -class TagInstance(BaseDocumentInstance): - attributes: Optional[List[Attribute]] = Field(list()) - - -class DocumentInstance(BaseDocumentInstance): - pass - - -ANNOTATION_TYPES = { - DocumentAnnotationTypeEnum.ENTITY: EntityInstance, - DocumentAnnotationTypeEnum.TAG: TagInstance, -} - - -class AnnotationInstance(BaseModel): - __root__: Union[TagInstance, EntityInstance] - - @classmethod - def __get_validators__(cls): - yield cls.return_action - - @classmethod - def return_action(cls, values): - try: - try: - instance_type = values["type"] - except KeyError: - raise ValidationError( - [ErrorWrapper(ValueError("field required"), "type")], cls - ) - return ANNOTATION_TYPES[instance_type](**values) - except KeyError: - raise ValidationError( - [ - ErrorWrapper( - ValueError( - f"invalid type, valid types are {', '.join(ANNOTATION_TYPES.keys())}" - ), - "type", - ) - ], - cls, - ) - except TypeError as e: - raise TypeError(INVALID_DICT_MESSAGE) from e - - -class DocumentAnnotation(BaseModel): - metadata: Metadata - instances: Optional[List[AnnotationInstance]] = Field(list()) - tags: Optional[List[Tag]] = Field(list()) - free_text: Optional[StrictStr] = Field(None, alias="freeText") diff --git a/src/superannotate_schemas/schemas/internal/pixel.py b/src/superannotate_schemas/schemas/internal/pixel.py deleted file mode 100644 index 0b4d981..0000000 --- a/src/superannotate_schemas/schemas/internal/pixel.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import List -from typing import Optional - -from superannotate_schemas.schemas.base import BaseImageMetadata as Metadata -from superannotate_schemas.schemas.base import HexColor -from superannotate_schemas.schemas.base import BaseAttribute -from superannotate_schemas.schemas.base import BaseImageAnnotationInstance -from superannotate_schemas.schemas.base import Tag -from superannotate_schemas.schemas.base import Comment -from superannotate_schemas.schemas.base import BaseModel - -from pydantic import StrictInt -from pydantic import Field - - -class Attribute(BaseAttribute): - id: StrictInt - group_id: StrictInt = Field(alias="groupId") - - -class AnnotationPart(BaseModel): - color: HexColor - - -class AnnotationInstance(BaseImageAnnotationInstance): - parts: List[AnnotationPart] - class_id: StrictInt = Field(alias="classId") - attributes: Optional[List[Attribute]] = Field(list()) - - -class PixelAnnotation(BaseModel): - metadata: Metadata - instances: List[AnnotationInstance] - tags: Optional[List[Tag]] = Field(list()) - comments: Optional[List[Comment]] = Field(list()) diff --git a/src/superannotate_schemas/schemas/internal/vector.py b/src/superannotate_schemas/schemas/internal/vector.py deleted file mode 100644 index c81d75c..0000000 --- a/src/superannotate_schemas/schemas/internal/vector.py +++ /dev/null @@ -1,205 +0,0 @@ -from typing import List -from typing import Any -from typing import Optional -from typing import Union - -from superannotate_schemas.schemas.base import BaseVectorInstance -from superannotate_schemas.schemas.base import BboxPoints -from superannotate_schemas.schemas.base import BaseAttribute -from superannotate_schemas.schemas.base import Comment -from superannotate_schemas.schemas.base import BaseImageMetadata as Metadata -from superannotate_schemas.schemas.base import Tag -from superannotate_schemas.schemas.base import AxisPoint -from superannotate_schemas.schemas.base import VectorAnnotationTypeEnum -from superannotate_schemas.schemas.base import BaseInstanceTag -from superannotate_schemas.schemas.base import StrictNumber -from superannotate_schemas.schemas.base import INVALID_DICT_MESSAGE -from superannotate_schemas.schemas.base import BaseModel -from superannotate_schemas.schemas.base import NotEmptyStr - -from pydantic import StrictInt -from superannotate_schemas.schemas.base import StrictFloat -from pydantic import conlist -from pydantic import Field -from pydantic import validate_model -from pydantic import ValidationError -from pydantic import validator -from pydantic.error_wrappers import ErrorWrapper - - -class InstanceTag(BaseInstanceTag): - class_id: StrictInt - class_name: NotEmptyStr = Field(None, alias="className") - - -class Attribute(BaseAttribute): - id: StrictInt - group_id: StrictInt = Field(alias="groupId") - - -class VectorInstance(BaseVectorInstance): - attributes: Optional[List[Attribute]] = Field(list()) - - -class Point(VectorInstance, AxisPoint): - pass - - -class PolyLine(VectorInstance): - points: List[Union[StrictFloat, StrictInt]] - - -class Polygon(VectorInstance): - points: conlist(Union[StrictFloat, StrictInt], min_items=3) - exclude: Optional[List[List[Union[StrictFloat, StrictInt]]]] = [] - - -class Bbox(VectorInstance): - points: BboxPoints - - -class RotatedBoxPoints(BaseModel): - x1: StrictNumber - y1: StrictNumber - x2: StrictNumber - y2: StrictNumber - x3: StrictNumber - y3: StrictNumber - x4: StrictNumber - y4: StrictNumber - - -class RotatedBox(VectorInstance): - points: RotatedBoxPoints - - -class Ellipse(VectorInstance): - cx: StrictNumber - cy: StrictNumber - rx: StrictNumber - ry: StrictNumber - angle: StrictNumber - - -class TemplatePoint(BaseModel): - id: StrictInt - x: StrictNumber - y: StrictNumber - - -class TemplateConnection(BaseModel): - id: StrictInt - from_connection: StrictInt = Field(alias="from") - to_connection: StrictInt = Field(alias="to") - - -class Template(VectorInstance): - points: conlist(TemplatePoint, min_items=1) - connections: List[TemplateConnection] - template_id: StrictInt = Field(alias="templateId") - - -class CuboidPoint(BaseModel): - f1: AxisPoint - f2: AxisPoint - r1: AxisPoint - r2: AxisPoint - - -class Cuboid(VectorInstance): - points: CuboidPoint - - -ANNOTATION_TYPES = { - VectorAnnotationTypeEnum.BBOX: Bbox, - VectorAnnotationTypeEnum.TEMPLATE: Template, - VectorAnnotationTypeEnum.CUBOID: Cuboid, - VectorAnnotationTypeEnum.POLYGON: Polygon, - VectorAnnotationTypeEnum.POINT: Point, - VectorAnnotationTypeEnum.POLYLINE: PolyLine, - VectorAnnotationTypeEnum.ELLIPSE: Ellipse, - VectorAnnotationTypeEnum.RBBOX: RotatedBox, - VectorAnnotationTypeEnum.TAG: InstanceTag, -} - - -class AnnotationInstance(BaseModel): - __root__: Union[ - Template, Cuboid, Point, PolyLine, Polygon, Bbox, Ellipse, RotatedBox, InstanceTag - ] - - @classmethod - def __get_validators__(cls): - yield cls.return_action - - @classmethod - def return_action(cls, values): - try: - try: - instance_type = values["type"] - except KeyError: - raise ValidationError( - [ErrorWrapper(ValueError("field required"), "type")], cls - ) - return ANNOTATION_TYPES[instance_type](**values) - except KeyError: - raise ValidationError( - [ - ErrorWrapper( - ValueError( - f"invalid type, valid types are {', '.join(ANNOTATION_TYPES.keys())}" - ), - "type", - ) - ], - cls, - ) - except TypeError as e: - raise TypeError(INVALID_DICT_MESSAGE) from e - - -class VectorAnnotation(BaseModel): - metadata: Metadata - comments: Optional[List[Comment]] = Field(list()) - tags: Optional[List[Tag]] = Field(list()) - instances: Optional[ - List[ - Union[Template, Cuboid, Point, PolyLine, Polygon, Bbox, Ellipse, RotatedBox] - ] - ] = Field(list()) - - @validator("instances", pre=True, each_item=True) - def check_instances(cls, instance): - annotation_type = instance.get("type") - if not annotation_type: - raise ValidationError( - [ - ErrorWrapper( - ValueError( - f"type field required" - ), - "type", - ) - ], - model=BaseModel - ) - model_type = ANNOTATION_TYPES.get(annotation_type) - if not model_type: - raise ValidationError( - [ - ErrorWrapper( - ValueError( - f"invalid value" - ), - "type", - ) - ], - model=BaseModel - ) - result = validate_model(ANNOTATION_TYPES[annotation_type], instance) - if result[2]: - raise ValidationError( - result[2].raw_errors, model=ANNOTATION_TYPES[annotation_type] - ) - return instance - diff --git a/src/superannotate_schemas/schemas/internal/video.py b/src/superannotate_schemas/schemas/internal/video.py deleted file mode 100644 index 0435b81..0000000 --- a/src/superannotate_schemas/schemas/internal/video.py +++ /dev/null @@ -1,70 +0,0 @@ -from enum import Enum -from typing import Dict -from typing import List -from typing import Optional -from typing import Union - -from superannotate_schemas.schemas.base import BaseAttribute -from superannotate_schemas.schemas.base import BaseInstance -from superannotate_schemas.schemas.base import BboxPoints -from superannotate_schemas.schemas.base import BaseMetadata -from superannotate_schemas.schemas.base import PointLabels -from superannotate_schemas.schemas.base import Tag -from superannotate_schemas.schemas.base import AnnotationStatusEnum - -from superannotate_schemas.schemas.base import BaseModel -from superannotate_schemas.schemas.base import StrictFloat -from pydantic import constr -from pydantic import Field -from pydantic import StrictBool -from pydantic import StrictStr -from pydantic import StrictInt - - -class VideoType(str, Enum): - EVENT = "event" - BBOX = "bbox" - - -class Attribute(BaseAttribute): - id: StrictInt - group_id: StrictInt = Field(alias="groupId") - - -class MetaData(BaseMetadata): - width: Optional[StrictInt] - height: Optional[StrictInt] - status: Optional[AnnotationStatusEnum] - duration: Optional[StrictInt] - error: Optional[StrictBool] - - -class BaseTimeStamp(BaseModel): - active: Optional[bool] - attributes: Optional[Dict[constr(regex=r"^[-|+]$"), List[Attribute]]] # noqa: F722 - - -class BboxTimeStamp(BaseTimeStamp): - points: Optional[BboxPoints] - - -class BaseVideoInstance(BaseInstance): - id: Optional[StrictStr] - type: VideoType - locked: Optional[StrictBool] - timeline: Dict[StrictFloat, BaseTimeStamp] - - -class BboxInstance(BaseVideoInstance): - point_labels: Optional[PointLabels] = Field(alias="pointLabels") - timeline: Dict[StrictFloat, BboxTimeStamp] - - -class EventInstance(BaseVideoInstance): - pass - - -class VideoAnnotation(BaseModel): - metadata: MetaData - instances: Optional[List[Union[EventInstance, BboxInstance]]] = Field(list()) - tags: Optional[List[Tag]] = Field(list()) diff --git a/src/superannotate_schemas/schemas/utils.py b/src/superannotate_schemas/schemas/utils.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/superannotate_schemas/bin/__init__.py b/src/superannotate_schemas/tests/__init__.py similarity index 100% rename from src/superannotate_schemas/bin/__init__.py rename to src/superannotate_schemas/tests/__init__.py diff --git a/src/superannotate_schemas/tests/_helpers.py b/src/superannotate_schemas/tests/_helpers.py new file mode 100644 index 0000000..70f291f --- /dev/null +++ b/src/superannotate_schemas/tests/_helpers.py @@ -0,0 +1,5 @@ +def bug(issue=None): + message = "A known bug." + if issue is not None: + message += " See issue #{issue}.".format(issue=issue) + return message diff --git a/src/superannotate_schemas/tests/_suite.py b/src/superannotate_schemas/tests/_suite.py new file mode 100644 index 0000000..5adbb94 --- /dev/null +++ b/src/superannotate_schemas/tests/_suite.py @@ -0,0 +1,239 @@ +""" +Python representations of the JSON Schema Test Suite tests. +""" + +from functools import partial +import json +import os +import re +import subprocess +import sys +import unittest + +from twisted.python.filepath import FilePath +import attr + +from superannotate_schemas.compat import PY3 +from superannotate_schemas.validators import validators +import superannotate_schemas + + +def _find_suite(): + root = os.environ.get("JSON_SCHEMA_TEST_SUITE") + if root is not None: + return FilePath(root) + + root = FilePath(superannotate_schemas.__file__).parent().sibling("json") + if not root.isdir(): # pragma: no cover + raise ValueError( + ( + "Can't find the JSON-Schema-Test-Suite directory. " + "Set the 'JSON_SCHEMA_TEST_SUITE' environment " + "variable or run the tests from alongside a checkout " + "of the suite." + ), + ) + return root + + +@attr.s(hash=True) +class Suite(object): + + _root = attr.ib(default=attr.Factory(_find_suite)) + + def _remotes(self): + superannotate_schemas_suite = self._root.descendant(["bin", "superannotate_schemas_suite"]) + remotes = subprocess.check_output( + [sys.executable, superannotate_schemas_suite.path, "remotes"], + ) + return { + "http://localhost:1234/" + name: schema + for name, schema in json.loads(remotes.decode("utf-8")).items() + } + + def benchmark(self, runner): # pragma: no cover + for name in validators: + self.version(name=name).benchmark(runner=runner) + + def version(self, name): + return Version( + name=name, + path=self._root.descendant(["tests", name]), + remotes=self._remotes(), + ) + + +@attr.s(hash=True) +class Version(object): + + _path = attr.ib() + _remotes = attr.ib() + + name = attr.ib() + + def benchmark(self, runner, **kwargs): # pragma: no cover + for suite in self.tests(): + for test in suite: + runner.bench_func( + test.fully_qualified_name, + partial(test.validate_ignoring_errors, **kwargs), + ) + + def tests(self): + return ( + test + for child in self._path.globChildren("*.json") + for test in self._tests_in( + subject=child.basename()[:-5], + path=child, + ) + ) + + def format_tests(self): + path = self._path.descendant(["optional", "format"]) + return ( + test + for child in path.globChildren("*.json") + for test in self._tests_in( + subject=child.basename()[:-5], + path=child, + ) + ) + + def tests_of(self, name): + return self._tests_in( + subject=name, + path=self._path.child(name + ".json"), + ) + + def optional_tests_of(self, name): + return self._tests_in( + subject=name, + path=self._path.descendant(["optional", name + ".json"]), + ) + + def to_unittest_testcase(self, *suites, **kwargs): + name = kwargs.pop("name", "Test" + self.name.title()) + methods = { + test.method_name: test.to_unittest_method(**kwargs) + for suite in suites + for tests in suite + for test in tests + } + cls = type(name, (unittest.TestCase,), methods) + + try: + cls.__module__ = _someone_save_us_the_module_of_the_caller() + except Exception: # pragma: no cover + # We're doing crazy things, so if they go wrong, like a function + # behaving differently on some other interpreter, just make them + # not happen. + pass + + return cls + + def _tests_in(self, subject, path): + for each in json.loads(path.getContent().decode("utf-8")): + yield ( + _Test( + version=self, + subject=subject, + case_description=each["description"], + schema=each["schema"], + remotes=self._remotes, + **test + ) for test in each["tests"] + ) + + +@attr.s(hash=True, repr=False) +class _Test(object): + + version = attr.ib() + + subject = attr.ib() + case_description = attr.ib() + description = attr.ib() + + data = attr.ib() + schema = attr.ib(repr=False) + + valid = attr.ib() + + _remotes = attr.ib() + + def __repr__(self): # pragma: no cover + return "".format(self.fully_qualified_name) + + @property + def fully_qualified_name(self): # pragma: no cover + return " > ".join( + [ + self.version.name, + self.subject, + self.case_description, + self.description, + ] + ) + + @property + def method_name(self): + delimiters = r"[\W\- ]+" + name = "test_%s_%s_%s" % ( + re.sub(delimiters, "_", self.subject), + re.sub(delimiters, "_", self.case_description), + re.sub(delimiters, "_", self.description), + ) + + if not PY3: # pragma: no cover + name = name.encode("utf-8") + return name + + def to_unittest_method(self, skip=lambda test: None, **kwargs): + if self.valid: + def fn(this): + self.validate(**kwargs) + else: + def fn(this): + with this.assertRaises(superannotate_schemas.ValidationError): + self.validate(**kwargs) + + fn.__name__ = self.method_name + reason = skip(self) + return unittest.skipIf(reason is not None, reason)(fn) + + def validate(self, Validator, **kwargs): + resolver = superannotate_schemas.RefResolver.from_schema( + schema=self.schema, + store=self._remotes, + id_of=Validator.ID_OF, + ) + superannotate_schemas.validate( + instance=self.data, + schema=self.schema, + cls=Validator, + resolver=resolver, + **kwargs + ) + + def validate_ignoring_errors(self, Validator): # pragma: no cover + try: + self.validate(Validator=Validator) + except superannotate_schemas.ValidationError: + pass + + +def _someone_save_us_the_module_of_the_caller(): + """ + The FQON of the module 2nd stack frames up from here. + + This is intended to allow us to dynamicallly return test case classes that + are indistinguishable from being defined in the module that wants them. + + Otherwise, trial will mis-print the FQON, and copy pasting it won't re-run + the class that really is running. + + Save us all, this is all so so so so so terrible. + """ + + return sys._getframe(2).f_globals["__name__"] diff --git a/src/superannotate_schemas/schemas/__init__.py b/src/superannotate_schemas/tests/_trial_temp/_trial_marker old mode 100644 new mode 100755 similarity index 100% rename from src/superannotate_schemas/schemas/__init__.py rename to src/superannotate_schemas/tests/_trial_temp/_trial_marker diff --git a/src/superannotate_schemas/tests/test_cli.py b/src/superannotate_schemas/tests/test_cli.py new file mode 100644 index 0000000..69fe9ff --- /dev/null +++ b/src/superannotate_schemas/tests/test_cli.py @@ -0,0 +1,151 @@ +from unittest import TestCase +import json +import subprocess +import sys + +from superannotate_schemas import Draft4Validator, ValidationError, cli, __version__ +from superannotate_schemas.compat import NativeIO +from superannotate_schemas.exceptions import SchemaError + + +def fake_validator(*errors): + errors = list(reversed(errors)) + + class FakeValidator(object): + def __init__(self, *args, **kwargs): + pass + + def iter_errors(self, instance): + if errors: + return errors.pop() + return [] + + def check_schema(self, schema): + pass + + return FakeValidator + + +class TestParser(TestCase): + + FakeValidator = fake_validator() + instance_file = "foo.json" + schema_file = "schema.json" + + def setUp(self): + cli.open = self.fake_open + self.addCleanup(delattr, cli, "open") + + def fake_open(self, path): + if path == self.instance_file: + contents = "" + elif path == self.schema_file: + contents = {} + else: # pragma: no cover + self.fail("What is {!r}".format(path)) + return NativeIO(json.dumps(contents)) + + def test_find_validator_by_fully_qualified_object_name(self): + arguments = cli.parse_args( + [ + "--validator", + "jsonschema.tests.test_cli.TestParser.FakeValidator", + "--instance", self.instance_file, + self.schema_file, + ] + ) + self.assertIs(arguments["validator"], self.FakeValidator) + + def test_find_validator_in_jsonschema(self): + arguments = cli.parse_args( + [ + "--validator", "Draft4Validator", + "--instance", self.instance_file, + self.schema_file, + ] + ) + self.assertIs(arguments["validator"], Draft4Validator) + + +class TestCLI(TestCase): + def test_draft3_schema_draft4_validator(self): + stdout, stderr = NativeIO(), NativeIO() + with self.assertRaises(SchemaError): + cli.run( + { + "validator": Draft4Validator, + "schema": { + "anyOf": [ + {"minimum": 20}, + {"type": "string"}, + {"required": True}, + ], + }, + "instances": [1], + "error_format": "{error.message}", + }, + stdout=stdout, + stderr=stderr, + ) + + def test_successful_validation(self): + stdout, stderr = NativeIO(), NativeIO() + exit_code = cli.run( + { + "validator": fake_validator(), + "schema": {}, + "instances": [1], + "error_format": "{error.message}", + }, + stdout=stdout, + stderr=stderr, + ) + self.assertFalse(stdout.getvalue()) + self.assertFalse(stderr.getvalue()) + self.assertEqual(exit_code, 0) + + def test_unsuccessful_validation(self): + error = ValidationError("I am an error!", instance=1) + stdout, stderr = NativeIO(), NativeIO() + exit_code = cli.run( + { + "validator": fake_validator([error]), + "schema": {}, + "instances": [1], + "error_format": "{error.instance} - {error.message}", + }, + stdout=stdout, + stderr=stderr, + ) + self.assertFalse(stdout.getvalue()) + self.assertEqual(stderr.getvalue(), "1 - I am an error!") + self.assertEqual(exit_code, 1) + + def test_unsuccessful_validation_multiple_instances(self): + first_errors = [ + ValidationError("9", instance=1), + ValidationError("8", instance=1), + ] + second_errors = [ValidationError("7", instance=2)] + stdout, stderr = NativeIO(), NativeIO() + exit_code = cli.run( + { + "validator": fake_validator(first_errors, second_errors), + "schema": {}, + "instances": [1, 2], + "error_format": "{error.instance} - {error.message}\t", + }, + stdout=stdout, + stderr=stderr, + ) + self.assertFalse(stdout.getvalue()) + self.assertEqual(stderr.getvalue(), "1 - 9\t1 - 8\t2 - 7\t") + self.assertEqual(exit_code, 1) + + def test_version(self): + version = subprocess.check_output( + [sys.executable, "-m", "jsonschema", "--version"], + stderr=subprocess.STDOUT, + ) + version = version.decode("utf-8").strip() + self.assertEqual(version, __version__) diff --git a/src/superannotate_schemas/tests/test_exceptions.py b/src/superannotate_schemas/tests/test_exceptions.py new file mode 100644 index 0000000..b555826 --- /dev/null +++ b/src/superannotate_schemas/tests/test_exceptions.py @@ -0,0 +1,462 @@ +from unittest import TestCase +import textwrap + +from superannotate_schemas import Draft4Validator, exceptions +from superannotate_schemas.compat import PY3 + + +class TestBestMatch(TestCase): + def best_match(self, errors): + errors = list(errors) + best = exceptions.best_match(errors) + reversed_best = exceptions.best_match(reversed(errors)) + msg = "Didn't return a consistent best match!\nGot: {0}\n\nThen: {1}" + self.assertEqual( + best._contents(), reversed_best._contents(), + msg=msg.format(best, reversed_best), + ) + return best + + def test_shallower_errors_are_better_matches(self): + validator = Draft4Validator( + { + "properties": { + "foo": { + "minProperties": 2, + "properties": {"bar": {"type": "object"}}, + }, + }, + }, + ) + best = self.best_match(validator.iter_errors({"foo": {"bar": []}})) + self.assertEqual(best.validator, "minProperties") + + def test_oneOf_and_anyOf_are_weak_matches(self): + """ + A property you *must* match is probably better than one you have to + match a part of. + """ + + validator = Draft4Validator( + { + "minProperties": 2, + "anyOf": [{"type": "string"}, {"type": "number"}], + "oneOf": [{"type": "string"}, {"type": "number"}], + } + ) + best = self.best_match(validator.iter_errors({})) + self.assertEqual(best.validator, "minProperties") + + def test_if_the_most_relevant_error_is_anyOf_it_is_traversed(self): + """ + If the most relevant error is an anyOf, then we traverse its context + and select the otherwise *least* relevant error, since in this case + that means the most specific, deep, error inside the instance. + + I.e. since only one of the schemas must match, we look for the most + relevant one. + """ + + validator = Draft4Validator( + { + "properties": { + "foo": { + "anyOf": [ + {"type": "string"}, + {"properties": {"bar": {"type": "array"}}}, + ], + }, + }, + }, + ) + best = self.best_match(validator.iter_errors({"foo": {"bar": 12}})) + self.assertEqual(best.validator_value, "array") + + def test_if_the_most_relevant_error_is_oneOf_it_is_traversed(self): + """ + If the most relevant error is an oneOf, then we traverse its context + and select the otherwise *least* relevant error, since in this case + that means the most specific, deep, error inside the instance. + + I.e. since only one of the schemas must match, we look for the most + relevant one. + """ + + validator = Draft4Validator( + { + "properties": { + "foo": { + "oneOf": [ + {"type": "string"}, + {"properties": {"bar": {"type": "array"}}}, + ], + }, + }, + }, + ) + best = self.best_match(validator.iter_errors({"foo": {"bar": 12}})) + self.assertEqual(best.validator_value, "array") + + def test_if_the_most_relevant_error_is_allOf_it_is_traversed(self): + """ + Now, if the error is allOf, we traverse but select the *most* relevant + error from the context, because all schemas here must match anyways. + """ + + validator = Draft4Validator( + { + "properties": { + "foo": { + "allOf": [ + {"type": "string"}, + {"properties": {"bar": {"type": "array"}}}, + ], + }, + }, + }, + ) + best = self.best_match(validator.iter_errors({"foo": {"bar": 12}})) + self.assertEqual(best.validator_value, "string") + + def test_nested_context_for_oneOf(self): + validator = Draft4Validator( + { + "properties": { + "foo": { + "oneOf": [ + {"type": "string"}, + { + "oneOf": [ + {"type": "string"}, + { + "properties": { + "bar": {"type": "array"}, + }, + }, + ], + }, + ], + }, + }, + }, + ) + best = self.best_match(validator.iter_errors({"foo": {"bar": 12}})) + self.assertEqual(best.validator_value, "array") + + def test_one_error(self): + validator = Draft4Validator({"minProperties": 2}) + error, = validator.iter_errors({}) + self.assertEqual( + exceptions.best_match(validator.iter_errors({})).validator, + "minProperties", + ) + + def test_no_errors(self): + validator = Draft4Validator({}) + self.assertIsNone(exceptions.best_match(validator.iter_errors({}))) + + +class TestByRelevance(TestCase): + def test_short_paths_are_better_matches(self): + shallow = exceptions.ValidationError("Oh no!", path=["baz"]) + deep = exceptions.ValidationError("Oh yes!", path=["foo", "bar"]) + match = max([shallow, deep], key=exceptions.relevance) + self.assertIs(match, shallow) + + match = max([deep, shallow], key=exceptions.relevance) + self.assertIs(match, shallow) + + def test_global_errors_are_even_better_matches(self): + shallow = exceptions.ValidationError("Oh no!", path=[]) + deep = exceptions.ValidationError("Oh yes!", path=["foo"]) + + errors = sorted([shallow, deep], key=exceptions.relevance) + self.assertEqual( + [list(error.path) for error in errors], + [["foo"], []], + ) + + errors = sorted([deep, shallow], key=exceptions.relevance) + self.assertEqual( + [list(error.path) for error in errors], + [["foo"], []], + ) + + def test_weak_validators_are_lower_priority(self): + weak = exceptions.ValidationError("Oh no!", path=[], validator="a") + normal = exceptions.ValidationError("Oh yes!", path=[], validator="b") + + best_match = exceptions.by_relevance(weak="a") + + match = max([weak, normal], key=best_match) + self.assertIs(match, normal) + + match = max([normal, weak], key=best_match) + self.assertIs(match, normal) + + def test_strong_validators_are_higher_priority(self): + weak = exceptions.ValidationError("Oh no!", path=[], validator="a") + normal = exceptions.ValidationError("Oh yes!", path=[], validator="b") + strong = exceptions.ValidationError("Oh fine!", path=[], validator="c") + + best_match = exceptions.by_relevance(weak="a", strong="c") + + match = max([weak, normal, strong], key=best_match) + self.assertIs(match, strong) + + match = max([strong, normal, weak], key=best_match) + self.assertIs(match, strong) + + +class TestErrorTree(TestCase): + def test_it_knows_how_many_total_errors_it_contains(self): + # FIXME: https://github.com/Julian/jsonschema/issues/442 + errors = [ + exceptions.ValidationError("Something", validator=i) + for i in range(8) + ] + tree = exceptions.ErrorTree(errors) + self.assertEqual(tree.total_errors, 8) + + def test_it_contains_an_item_if_the_item_had_an_error(self): + errors = [exceptions.ValidationError("a message", path=["bar"])] + tree = exceptions.ErrorTree(errors) + self.assertIn("bar", tree) + + def test_it_does_not_contain_an_item_if_the_item_had_no_error(self): + errors = [exceptions.ValidationError("a message", path=["bar"])] + tree = exceptions.ErrorTree(errors) + self.assertNotIn("foo", tree) + + def test_validators_that_failed_appear_in_errors_dict(self): + error = exceptions.ValidationError("a message", validator="foo") + tree = exceptions.ErrorTree([error]) + self.assertEqual(tree.errors, {"foo": error}) + + def test_it_creates_a_child_tree_for_each_nested_path(self): + errors = [ + exceptions.ValidationError("a bar message", path=["bar"]), + exceptions.ValidationError("a bar -> 0 message", path=["bar", 0]), + ] + tree = exceptions.ErrorTree(errors) + self.assertIn(0, tree["bar"]) + self.assertNotIn(1, tree["bar"]) + + def test_children_have_their_errors_dicts_built(self): + e1, e2 = ( + exceptions.ValidationError("1", validator="foo", path=["bar", 0]), + exceptions.ValidationError("2", validator="quux", path=["bar", 0]), + ) + tree = exceptions.ErrorTree([e1, e2]) + self.assertEqual(tree["bar"][0].errors, {"foo": e1, "quux": e2}) + + def test_multiple_errors_with_instance(self): + e1, e2 = ( + exceptions.ValidationError( + "1", + validator="foo", + path=["bar", "bar2"], + instance="i1"), + exceptions.ValidationError( + "2", + validator="quux", + path=["foobar", 2], + instance="i2"), + ) + exceptions.ErrorTree([e1, e2]) + + def test_it_does_not_contain_subtrees_that_are_not_in_the_instance(self): + error = exceptions.ValidationError("123", validator="foo", instance=[]) + tree = exceptions.ErrorTree([error]) + + with self.assertRaises(IndexError): + tree[0] + + def test_if_its_in_the_tree_anyhow_it_does_not_raise_an_error(self): + """ + If a validator is dumb (like :validator:`required` in draft 3) and + refers to a path that isn't in the instance, the tree still properly + returns a subtree for that path. + """ + + error = exceptions.ValidationError( + "a message", validator="foo", instance={}, path=["foo"], + ) + tree = exceptions.ErrorTree([error]) + self.assertIsInstance(tree["foo"], exceptions.ErrorTree) + + +class TestErrorInitReprStr(TestCase): + def make_error(self, **kwargs): + defaults = dict( + message=u"hello", + validator=u"type", + validator_value=u"string", + instance=5, + schema={u"type": u"string"}, + ) + defaults.update(kwargs) + return exceptions.ValidationError(**defaults) + + def assertShows(self, expected, **kwargs): + if PY3: # pragma: no cover + expected = expected.replace("u'", "'") + expected = textwrap.dedent(expected).rstrip("\n") + + error = self.make_error(**kwargs) + message_line, _, rest = str(error).partition("\n") + self.assertEqual(message_line, error.message) + self.assertEqual(rest, expected) + + def test_it_calls_super_and_sets_args(self): + error = self.make_error() + self.assertGreater(len(error.args), 1) + + def test_repr(self): + self.assertEqual( + repr(exceptions.ValidationError(message="Hello!")), + "" % "Hello!", + ) + + def test_unset_error(self): + error = exceptions.ValidationError("message") + self.assertEqual(str(error), "message") + + kwargs = { + "validator": "type", + "validator_value": "string", + "instance": 5, + "schema": {"type": "string"}, + } + # Just the message should show if any of the attributes are unset + for attr in kwargs: + k = dict(kwargs) + del k[attr] + error = exceptions.ValidationError("message", **k) + self.assertEqual(str(error), "message") + + def test_empty_paths(self): + self.assertShows( + """ + Failed validating u'type' in schema: + {u'type': u'string'} + + On instance: + 5 + """, + path=[], + schema_path=[], + ) + + def test_one_item_paths(self): + self.assertShows( + """ + Failed validating u'type' in schema: + {u'type': u'string'} + + On instance[0]: + 5 + """, + path=[0], + schema_path=["items"], + ) + + def test_multiple_item_paths(self): + self.assertShows( + """ + Failed validating u'type' in schema[u'items'][0]: + {u'type': u'string'} + + On instance[0][u'a']: + 5 + """, + path=[0, u"a"], + schema_path=[u"items", 0, 1], + ) + + def test_uses_pprint(self): + self.assertShows( + """ + Failed validating u'maxLength' in schema: + {0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + 10: 10, + 11: 11, + 12: 12, + 13: 13, + 14: 14, + 15: 15, + 16: 16, + 17: 17, + 18: 18, + 19: 19} + + On instance: + [0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24] + """, + instance=list(range(25)), + schema=dict(zip(range(20), range(20))), + validator=u"maxLength", + ) + + def test_str_works_with_instances_having_overriden_eq_operator(self): + """ + Check for https://github.com/Julian/jsonschema/issues/164 which + rendered exceptions unusable when a `ValidationError` involved + instances with an `__eq__` method that returned truthy values. + """ + + class DontEQMeBro(object): + def __eq__(this, other): # pragma: no cover + self.fail("Don't!") + + def __ne__(this, other): # pragma: no cover + self.fail("Don't!") + + instance = DontEQMeBro() + error = exceptions.ValidationError( + "a message", + validator="foo", + instance=instance, + validator_value="some", + schema="schema", + ) + self.assertIn(repr(instance), str(error)) + + +class TestHashable(TestCase): + def test_hashable(self): + set([exceptions.ValidationError("")]) + set([exceptions.SchemaError("")]) diff --git a/src/superannotate_schemas/tests/test_format.py b/src/superannotate_schemas/tests/test_format.py new file mode 100644 index 0000000..eb98d6b --- /dev/null +++ b/src/superannotate_schemas/tests/test_format.py @@ -0,0 +1,88 @@ +""" +Tests for the parts of jsonschema related to the :validator:`format` property. +""" + +from unittest import TestCase +from superannotate_schemas import FormatError, ValidationError, FormatChecker +from superannotate_schemas.validators import Draft4Validator + + +BOOM = ValueError("Boom!") +BANG = ZeroDivisionError("Bang!") + + +def boom(thing): + if thing == "bang": + raise BANG + raise BOOM + + +class TestFormatChecker(TestCase): + def test_it_can_validate_no_formats(self): + checker = FormatChecker(formats=()) + self.assertFalse(checker.checkers) + + def test_it_raises_a_key_error_for_unknown_formats(self): + with self.assertRaises(KeyError): + FormatChecker(formats=["o noes"]) + + def test_it_can_register_cls_checkers(self): + original = dict(FormatChecker.checkers) + self.addCleanup(FormatChecker.checkers.pop, "boom") + FormatChecker.cls_checks("boom")(boom) + self.assertEqual( + FormatChecker.checkers, + dict(original, boom=(boom, ())), + ) + + def test_it_can_register_checkers(self): + checker = FormatChecker() + checker.checks("boom")(boom) + self.assertEqual( + checker.checkers, + dict(FormatChecker.checkers, boom=(boom, ())) + ) + + def test_it_catches_registered_errors(self): + checker = FormatChecker() + checker.checks("boom", raises=type(BOOM))(boom) + + with self.assertRaises(FormatError) as cm: + checker.check(instance=12, format="boom") + + self.assertIs(cm.exception.cause, BOOM) + self.assertIs(cm.exception.__cause__, BOOM) + + # Unregistered errors should not be caught + with self.assertRaises(type(BANG)): + checker.check(instance="bang", format="boom") + + def test_format_error_causes_become_validation_error_causes(self): + checker = FormatChecker() + checker.checks("boom", raises=ValueError)(boom) + validator = Draft4Validator({"format": "boom"}, format_checker=checker) + + with self.assertRaises(ValidationError) as cm: + validator.validate("BOOM") + + self.assertIs(cm.exception.cause, BOOM) + self.assertIs(cm.exception.__cause__, BOOM) + + def test_format_checkers_come_with_defaults(self): + # This is bad :/ but relied upon. + # The docs for quite awhile recommended people do things like + # validate(..., format_checker=FormatChecker()) + # We should change that, but we can't without deprecation... + checker = FormatChecker() + with self.assertRaises(FormatError): + checker.check(instance="not-an-ipv4", format="ipv4") + + def test_repr(self): + checker = FormatChecker(formats=()) + checker.checks("foo")(lambda thing: True) + checker.checks("bar")(lambda thing: True) + checker.checks("baz")(lambda thing: True) + self.assertEqual( + repr(checker), + "", + ) diff --git a/src/superannotate_schemas/tests/test_jsonschema_test_suite.py b/src/superannotate_schemas/tests/test_jsonschema_test_suite.py new file mode 100644 index 0000000..5a395f5 --- /dev/null +++ b/src/superannotate_schemas/tests/test_jsonschema_test_suite.py @@ -0,0 +1,277 @@ +""" +Test runner for the JSON Schema official test suite + +Tests comprehensive correctness of each draft's validator. + +See https://github.com/json-schema-org/JSON-Schema-Test-Suite for details. +""" + +import sys +import warnings + +from superannotate_schemas import ( + Draft3Validator, + Draft4Validator, + Draft6Validator, + Draft7Validator, + draft3_format_checker, + draft4_format_checker, + draft6_format_checker, + draft7_format_checker, +) +from superannotate_schemas.tests._helpers import bug +from superannotate_schemas.tests._suite import Suite +from superannotate_schemas.validators import _DEPRECATED_DEFAULT_TYPES, create + + +SUITE = Suite() +DRAFT3 = SUITE.version(name="draft3") +DRAFT4 = SUITE.version(name="draft4") +DRAFT6 = SUITE.version(name="draft6") +DRAFT7 = SUITE.version(name="draft7") + + +def skip(message, **kwargs): + def skipper(test): + if all(value == getattr(test, attr) for attr, value in kwargs.items()): + return message + return skipper + + +def missing_format(checker): + def missing_format(test): + schema = test.schema + if schema is True or schema is False or "format" not in schema: + return + + if schema["format"] not in checker.checkers: + return "Format checker {0!r} not found.".format(schema["format"]) + return missing_format + + +is_narrow_build = sys.maxunicode == 2 ** 16 - 1 +if is_narrow_build: # pragma: no cover + message = "Not running surrogate Unicode case, this Python is narrow." + + def narrow_unicode_build(test): # pragma: no cover + return skip( + message=message, + description="one supplementary Unicode code point is not long enough", + )(test) or skip( + message=message, + description="two supplementary Unicode code points is long enough", + )(test) +else: + def narrow_unicode_build(test): # pragma: no cover + return + + +TestDraft3 = DRAFT3.to_unittest_testcase( + DRAFT3.tests(), + DRAFT3.optional_tests_of(name="bignum"), + DRAFT3.optional_tests_of(name="format"), + DRAFT3.optional_tests_of(name="zeroTerminatedFloats"), + Validator=Draft3Validator, + format_checker=draft3_format_checker, + skip=lambda test: ( + narrow_unicode_build(test) + or missing_format(draft3_format_checker)(test) + or skip( + message="Upstream bug in strict_rfc3339", + subject="format", + description="case-insensitive T and Z", + )(test) + ), +) + + +TestDraft4 = DRAFT4.to_unittest_testcase( + DRAFT4.tests(), + DRAFT4.optional_tests_of(name="bignum"), + DRAFT4.optional_tests_of(name="format"), + DRAFT4.optional_tests_of(name="zeroTerminatedFloats"), + Validator=Draft4Validator, + format_checker=draft4_format_checker, + skip=lambda test: ( + narrow_unicode_build(test) + or missing_format(draft4_format_checker)(test) + or skip( + message=bug(), + subject="ref", + case_description="Recursive references between schemas", + )(test) + or skip( + message=bug(371), + subject="ref", + case_description="Location-independent identifier", + )(test) + or skip( + message=bug(371), + subject="ref", + case_description=( + "Location-independent identifier with absolute URI" + ), + )(test) + or skip( + message=bug(371), + subject="ref", + case_description=( + "Location-independent identifier with base URI change in subschema" + ), + )(test) + or skip( + message=bug(), + subject="refRemote", + case_description="base URI change - change folder in subschema", + )(test) + or skip( + message="Upstream bug in strict_rfc3339", + subject="format", + description="case-insensitive T and Z", + )(test) + ), +) + + +TestDraft6 = DRAFT6.to_unittest_testcase( + DRAFT6.tests(), + DRAFT6.optional_tests_of(name="bignum"), + DRAFT6.optional_tests_of(name="format"), + DRAFT6.optional_tests_of(name="zeroTerminatedFloats"), + Validator=Draft6Validator, + format_checker=draft6_format_checker, + skip=lambda test: ( + narrow_unicode_build(test) + or missing_format(draft6_format_checker)(test) + or skip( + message=bug(), + subject="ref", + case_description="Recursive references between schemas", + )(test) + or skip( + message=bug(371), + subject="ref", + case_description="Location-independent identifier", + )(test) + or skip( + message=bug(371), + subject="ref", + case_description=( + "Location-independent identifier with absolute URI" + ), + )(test) + or skip( + message=bug(371), + subject="ref", + case_description=( + "Location-independent identifier with base URI change in subschema" + ), + )(test) + or skip( + message=bug(), + subject="refRemote", + case_description="base URI change - change folder in subschema", + )(test) + or skip( + message="Upstream bug in strict_rfc3339", + subject="format", + description="case-insensitive T and Z", + )(test) + ), +) + + +TestDraft7 = DRAFT7.to_unittest_testcase( + DRAFT7.tests(), + DRAFT7.format_tests(), + DRAFT7.optional_tests_of(name="bignum"), + DRAFT7.optional_tests_of(name="content"), + DRAFT7.optional_tests_of(name="zeroTerminatedFloats"), + Validator=Draft7Validator, + format_checker=draft7_format_checker, + skip=lambda test: ( + narrow_unicode_build(test) + or missing_format(draft7_format_checker)(test) + or skip( + message=bug(), + subject="ref", + case_description="Recursive references between schemas", + )(test) + or skip( + message=bug(371), + subject="ref", + case_description="Location-independent identifier", + )(test) + or skip( + message=bug(371), + subject="ref", + case_description=( + "Location-independent identifier with absolute URI" + ), + )(test) + or skip( + message=bug(371), + subject="ref", + case_description=( + "Location-independent identifier with base URI change in subschema" + ), + )(test) + or skip( + message=bug(), + subject="refRemote", + case_description="base URI change - change folder in subschema", + )(test) + or skip( + message="Upstream bug in strict_rfc3339", + subject="date-time", + description="case-insensitive T and Z", + )(test) + or skip( + message=bug(593), + subject="content", + case_description=( + "validation of string-encoded content based on media type" + ), + )(test) + or skip( + message=bug(593), + subject="content", + case_description="validation of binary string-encoding", + )(test) + or skip( + message=bug(593), + subject="content", + case_description=( + "validation of binary-encoded media type documents" + ), + )(test) + ), +) + + +with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + + TestDraft3LegacyTypeCheck = DRAFT3.to_unittest_testcase( + # Interestingly the any part couldn't really be done w/the old API. + ( + (test for test in each if test.schema != {"type": "any"}) + for each in DRAFT3.tests_of(name="type") + ), + name="TestDraft3LegacyTypeCheck", + Validator=create( + meta_schema=Draft3Validator.META_SCHEMA, + validators=Draft3Validator.VALIDATORS, + default_types=_DEPRECATED_DEFAULT_TYPES, + ), + ) + + TestDraft4LegacyTypeCheck = DRAFT4.to_unittest_testcase( + DRAFT4.tests_of(name="type"), + name="TestDraft4LegacyTypeCheck", + Validator=create( + meta_schema=Draft4Validator.META_SCHEMA, + validators=Draft4Validator.VALIDATORS, + default_types=_DEPRECATED_DEFAULT_TYPES, + ), + ) diff --git a/src/superannotate_schemas/tests/test_types.py b/src/superannotate_schemas/tests/test_types.py new file mode 100644 index 0000000..a8e1e5a --- /dev/null +++ b/src/superannotate_schemas/tests/test_types.py @@ -0,0 +1,190 @@ +""" +Tests on the new type interface. The actual correctness of the type checking +is handled in test_jsonschema_test_suite; these tests check that TypeChecker +functions correctly and can facilitate extensions to type checking +""" +from collections import namedtuple +from unittest import TestCase + +from superannotate_schemas import ValidationError, _validators +from superannotate_schemas._types import TypeChecker +from superannotate_schemas.exceptions import UndefinedTypeCheck +from superannotate_schemas.validators import Draft4Validator, extend + + +def equals_2(checker, instance): + return instance == 2 + + +def is_namedtuple(instance): + return isinstance(instance, tuple) and getattr(instance, "_fields", None) + + +def is_object_or_named_tuple(checker, instance): + if Draft4Validator.TYPE_CHECKER.is_type(instance, "object"): + return True + return is_namedtuple(instance) + + +def coerce_named_tuple(fn): + def coerced(validator, value, instance, schema): + if is_namedtuple(instance): + instance = instance._asdict() + return fn(validator, value, instance, schema) + return coerced + + +required = coerce_named_tuple(_validators.required) +properties = coerce_named_tuple(_validators.properties) + + +class TestTypeChecker(TestCase): + def test_is_type(self): + checker = TypeChecker({"two": equals_2}) + self.assertEqual( + ( + checker.is_type(instance=2, type="two"), + checker.is_type(instance="bar", type="two"), + ), + (True, False), + ) + + def test_is_unknown_type(self): + with self.assertRaises(UndefinedTypeCheck) as context: + TypeChecker().is_type(4, "foobar") + self.assertIn("foobar", str(context.exception)) + + def test_checks_can_be_added_at_init(self): + checker = TypeChecker({"two": equals_2}) + self.assertEqual(checker, TypeChecker().redefine("two", equals_2)) + + def test_redefine_existing_type(self): + self.assertEqual( + TypeChecker().redefine("two", object()).redefine("two", equals_2), + TypeChecker().redefine("two", equals_2), + ) + + def test_remove(self): + self.assertEqual( + TypeChecker({"two": equals_2}).remove("two"), + TypeChecker(), + ) + + def test_remove_unknown_type(self): + with self.assertRaises(UndefinedTypeCheck) as context: + TypeChecker().remove("foobar") + self.assertIn("foobar", str(context.exception)) + + def test_redefine_many(self): + self.assertEqual( + TypeChecker().redefine_many({"foo": int, "bar": str}), + TypeChecker().redefine("foo", int).redefine("bar", str), + ) + + def test_remove_multiple(self): + self.assertEqual( + TypeChecker({"foo": int, "bar": str}).remove("foo", "bar"), + TypeChecker(), + ) + + def test_type_check_can_raise_key_error(self): + """ + Make sure no one writes: + + try: + self._type_checkers[type](...) + except KeyError: + + ignoring the fact that the function itself can raise that. + """ + + error = KeyError("Stuff") + + def raises_keyerror(checker, instance): + raise error + + with self.assertRaises(KeyError) as context: + TypeChecker({"foo": raises_keyerror}).is_type(4, "foo") + + self.assertIs(context.exception, error) + + +class TestCustomTypes(TestCase): + def test_simple_type_can_be_extended(self): + def int_or_str_int(checker, instance): + if not isinstance(instance, (int, str)): + return False + try: + int(instance) + except ValueError: + return False + return True + + CustomValidator = extend( + Draft4Validator, + type_checker=Draft4Validator.TYPE_CHECKER.redefine( + "integer", int_or_str_int, + ), + ) + validator = CustomValidator({"type": "integer"}) + + validator.validate(4) + validator.validate("4") + + with self.assertRaises(ValidationError): + validator.validate(4.4) + + def test_object_can_be_extended(self): + schema = {"type": "object"} + + Point = namedtuple("Point", ["x", "y"]) + + type_checker = Draft4Validator.TYPE_CHECKER.redefine( + u"object", is_object_or_named_tuple, + ) + + CustomValidator = extend(Draft4Validator, type_checker=type_checker) + validator = CustomValidator(schema) + + validator.validate(Point(x=4, y=5)) + + def test_object_extensions_require_custom_validators(self): + schema = {"type": "object", "required": ["x"]} + + type_checker = Draft4Validator.TYPE_CHECKER.redefine( + u"object", is_object_or_named_tuple, + ) + + CustomValidator = extend(Draft4Validator, type_checker=type_checker) + validator = CustomValidator(schema) + + Point = namedtuple("Point", ["x", "y"]) + # Cannot handle required + with self.assertRaises(ValidationError): + validator.validate(Point(x=4, y=5)) + + def test_object_extensions_can_handle_custom_validators(self): + schema = { + "type": "object", + "required": ["x"], + "properties": {"x": {"type": "integer"}}, + } + + type_checker = Draft4Validator.TYPE_CHECKER.redefine( + u"object", is_object_or_named_tuple, + ) + + CustomValidator = extend( + Draft4Validator, + type_checker=type_checker, + validators={"required": required, "properties": properties}, + ) + + validator = CustomValidator(schema) + + Point = namedtuple("Point", ["x", "y"]) + # Can now process required and properties + validator.validate(Point(x=4, y=5)) + + with self.assertRaises(ValidationError): + validator.validate(Point(x="not an integer", y=5)) diff --git a/src/superannotate_schemas/tests/test_validators.py b/src/superannotate_schemas/tests/test_validators.py new file mode 100644 index 0000000..9e42b86 --- /dev/null +++ b/src/superannotate_schemas/tests/test_validators.py @@ -0,0 +1,1762 @@ +from collections import deque +from contextlib import contextmanager +from decimal import Decimal +from io import BytesIO +from unittest import TestCase +import json +import os +import sys +import tempfile +import unittest + +from twisted.trial.unittest import SynchronousTestCase +import attr + +from superannotate_schemas import FormatChecker, TypeChecker, exceptions, validators +from superannotate_schemas.compat import PY3, pathname2url +from superannotate_schemas.tests._helpers import bug + + +def startswith(validator, startswith, instance, schema): + if not instance.startswith(startswith): + yield exceptions.ValidationError(u"Whoops!") + + +class TestCreateAndExtend(SynchronousTestCase): + def setUp(self): + self.addCleanup( + self.assertEqual, + validators.meta_schemas, + dict(validators.meta_schemas), + ) + + self.meta_schema = {u"$id": "some://meta/schema"} + self.validators = {u"startswith": startswith} + self.type_checker = TypeChecker() + self.Validator = validators.create( + meta_schema=self.meta_schema, + validators=self.validators, + type_checker=self.type_checker, + ) + + def test_attrs(self): + self.assertEqual( + ( + self.Validator.VALIDATORS, + self.Validator.META_SCHEMA, + self.Validator.TYPE_CHECKER, + ), ( + self.validators, + self.meta_schema, + self.type_checker, + ), + ) + + def test_init(self): + schema = {u"startswith": u"foo"} + self.assertEqual(self.Validator(schema).schema, schema) + + def test_iter_errors(self): + schema = {u"startswith": u"hel"} + iter_errors = self.Validator(schema).iter_errors + + errors = list(iter_errors(u"hello")) + self.assertEqual(errors, []) + + expected_error = exceptions.ValidationError( + u"Whoops!", + instance=u"goodbye", + schema=schema, + validator=u"startswith", + validator_value=u"hel", + schema_path=deque([u"startswith"]), + ) + + errors = list(iter_errors(u"goodbye")) + self.assertEqual(len(errors), 1) + self.assertEqual(errors[0]._contents(), expected_error._contents()) + + def test_if_a_version_is_provided_it_is_registered(self): + Validator = validators.create( + meta_schema={u"$id": "something"}, + version="my version", + ) + self.addCleanup(validators.meta_schemas.pop, "something") + self.assertEqual(Validator.__name__, "MyVersionValidator") + + def test_if_a_version_is_not_provided_it_is_not_registered(self): + original = dict(validators.meta_schemas) + validators.create(meta_schema={u"id": "id"}) + self.assertEqual(validators.meta_schemas, original) + + def test_validates_registers_meta_schema_id(self): + meta_schema_key = "meta schema id" + my_meta_schema = {u"id": meta_schema_key} + + validators.create( + meta_schema=my_meta_schema, + version="my version", + id_of=lambda s: s.get("id", ""), + ) + self.addCleanup(validators.meta_schemas.pop, meta_schema_key) + + self.assertIn(meta_schema_key, validators.meta_schemas) + + def test_validates_registers_meta_schema_draft6_id(self): + meta_schema_key = "meta schema $id" + my_meta_schema = {u"$id": meta_schema_key} + + validators.create( + meta_schema=my_meta_schema, + version="my version", + ) + self.addCleanup(validators.meta_schemas.pop, meta_schema_key) + + self.assertIn(meta_schema_key, validators.meta_schemas) + + def test_create_default_types(self): + Validator = validators.create(meta_schema={}, validators=()) + self.assertTrue( + all( + Validator({}).is_type(instance=instance, type=type) + for type, instance in [ + (u"array", []), + (u"boolean", True), + (u"integer", 12), + (u"null", None), + (u"number", 12.0), + (u"object", {}), + (u"string", u"foo"), + ] + ), + ) + + def test_extend(self): + original = dict(self.Validator.VALIDATORS) + new = object() + + Extended = validators.extend( + self.Validator, + validators={u"new": new}, + ) + self.assertEqual( + ( + Extended.VALIDATORS, + Extended.META_SCHEMA, + Extended.TYPE_CHECKER, + self.Validator.VALIDATORS, + ), ( + dict(original, new=new), + self.Validator.META_SCHEMA, + self.Validator.TYPE_CHECKER, + original, + ), + ) + + def test_extend_idof(self): + """ + Extending a validator preserves its notion of schema IDs. + """ + def id_of(schema): + return schema.get(u"__test__", self.Validator.ID_OF(schema)) + correct_id = "the://correct/id/" + meta_schema = { + u"$id": "the://wrong/id/", + u"__test__": correct_id, + } + Original = validators.create( + meta_schema=meta_schema, + validators=self.validators, + type_checker=self.type_checker, + id_of=id_of, + ) + self.assertEqual(Original.ID_OF(Original.META_SCHEMA), correct_id) + + Derived = validators.extend(Original) + self.assertEqual(Derived.ID_OF(Derived.META_SCHEMA), correct_id) + + +class TestLegacyTypeChecking(SynchronousTestCase): + def test_create_default_types(self): + Validator = validators.create(meta_schema={}, validators=()) + self.assertEqual( + set(Validator.DEFAULT_TYPES), { + u"array", + u"boolean", + u"integer", + u"null", + u"number", + u"object", u"string", + }, + ) + self.flushWarnings() + + def test_extend(self): + Validator = validators.create(meta_schema={}, validators=()) + original = dict(Validator.VALIDATORS) + new = object() + + Extended = validators.extend( + Validator, + validators={u"new": new}, + ) + self.assertEqual( + ( + Extended.VALIDATORS, + Extended.META_SCHEMA, + Extended.TYPE_CHECKER, + Validator.VALIDATORS, + + Extended.DEFAULT_TYPES, + Extended({}).DEFAULT_TYPES, + self.flushWarnings()[0]["message"], + ), ( + dict(original, new=new), + Validator.META_SCHEMA, + Validator.TYPE_CHECKER, + original, + + Validator.DEFAULT_TYPES, + Validator.DEFAULT_TYPES, + self.flushWarnings()[0]["message"], + ), + ) + + def test_types_redefines_the_validators_type_checker(self): + schema = {"type": "string"} + self.assertFalse(validators.Draft7Validator(schema).is_valid(12)) + + validator = validators.Draft7Validator( + schema, + types={"string": (str, int)}, + ) + self.assertTrue(validator.is_valid(12)) + self.flushWarnings() + + def test_providing_default_types_warns(self): + self.assertWarns( + category=DeprecationWarning, + message=( + "The default_types argument is deprecated. " + "Use the type_checker argument instead." + ), + # https://tm.tl/9363 :'( + filename=sys.modules[self.assertWarns.__module__].__file__, + + f=validators.create, + meta_schema={}, + validators={}, + default_types={"foo": object}, + ) + + def test_cannot_ask_for_default_types_with_non_default_type_checker(self): + """ + We raise an error when you ask a validator with non-default + type checker for its DEFAULT_TYPES. + + The type checker argument is new, so no one but this library + itself should be trying to use it, and doing so while then + asking for DEFAULT_TYPES makes no sense (not to mention is + deprecated), since type checkers are not strictly about Python + type. + """ + Validator = validators.create( + meta_schema={}, + validators={}, + type_checker=TypeChecker(), + ) + with self.assertRaises(validators._DontDoThat) as e: + Validator.DEFAULT_TYPES + + self.assertIn( + "DEFAULT_TYPES cannot be used on Validators using TypeCheckers", + str(e.exception), + ) + with self.assertRaises(validators._DontDoThat): + Validator({}).DEFAULT_TYPES + + self.assertFalse(self.flushWarnings()) + + def test_providing_explicit_type_checker_does_not_warn(self): + Validator = validators.create( + meta_schema={}, + validators={}, + type_checker=TypeChecker(), + ) + self.assertFalse(self.flushWarnings()) + + Validator({}) + self.assertFalse(self.flushWarnings()) + + def test_providing_neither_does_not_warn(self): + Validator = validators.create(meta_schema={}, validators={}) + self.assertFalse(self.flushWarnings()) + + Validator({}) + self.assertFalse(self.flushWarnings()) + + def test_providing_default_types_with_type_checker_errors(self): + with self.assertRaises(TypeError) as e: + validators.create( + meta_schema={}, + validators={}, + default_types={"foo": object}, + type_checker=TypeChecker(), + ) + + self.assertIn( + "Do not specify default_types when providing a type checker", + str(e.exception), + ) + self.assertFalse(self.flushWarnings()) + + def test_extending_a_legacy_validator_with_a_type_checker_errors(self): + Validator = validators.create( + meta_schema={}, + validators={}, + default_types={u"array": list} + ) + with self.assertRaises(TypeError) as e: + validators.extend( + Validator, + validators={}, + type_checker=TypeChecker(), + ) + + self.assertIn( + ( + "Cannot extend a validator created with default_types " + "with a type_checker. Update the validator to use a " + "type_checker when created." + ), + str(e.exception), + ) + self.flushWarnings() + + def test_extending_a_legacy_validator_does_not_rewarn(self): + Validator = validators.create(meta_schema={}, default_types={}) + self.assertTrue(self.flushWarnings()) + + validators.extend(Validator) + self.assertFalse(self.flushWarnings()) + + def test_accessing_default_types_warns(self): + Validator = validators.create(meta_schema={}, validators={}) + self.assertFalse(self.flushWarnings()) + + self.assertWarns( + DeprecationWarning, + ( + "The DEFAULT_TYPES attribute is deprecated. " + "See the type checker attached to this validator instead." + ), + # https://tm.tl/9363 :'( + sys.modules[self.assertWarns.__module__].__file__, + + getattr, + Validator, + "DEFAULT_TYPES", + ) + + def test_accessing_default_types_on_the_instance_warns(self): + Validator = validators.create(meta_schema={}, validators={}) + self.assertFalse(self.flushWarnings()) + + self.assertWarns( + DeprecationWarning, + ( + "The DEFAULT_TYPES attribute is deprecated. " + "See the type checker attached to this validator instead." + ), + # https://tm.tl/9363 :'( + sys.modules[self.assertWarns.__module__].__file__, + + getattr, + Validator({}), + "DEFAULT_TYPES", + ) + + def test_providing_types_to_init_warns(self): + Validator = validators.create(meta_schema={}, validators={}) + self.assertFalse(self.flushWarnings()) + + self.assertWarns( + category=DeprecationWarning, + message=( + "The types argument is deprecated. " + "Provide a type_checker to jsonschema.validators.extend " + "instead." + ), + # https://tm.tl/9363 :'( + filename=sys.modules[self.assertWarns.__module__].__file__, + + f=Validator, + schema={}, + types={"bar": object}, + ) + + +class TestIterErrors(TestCase): + def setUp(self): + self.validator = validators.Draft3Validator({}) + + def test_iter_errors(self): + instance = [1, 2] + schema = { + u"disallow": u"array", + u"enum": [["a", "b", "c"], ["d", "e", "f"]], + u"minItems": 3, + } + + got = (e.message for e in self.validator.iter_errors(instance, schema)) + expected = [ + "%r is disallowed for [1, 2]" % (schema["disallow"],), + "[1, 2] is too short", + "[1, 2] is not one of %r" % (schema["enum"],), + ] + self.assertEqual(sorted(got), sorted(expected)) + + def test_iter_errors_multiple_failures_one_validator(self): + instance = {"foo": 2, "bar": [1], "baz": 15, "quux": "spam"} + schema = { + u"properties": { + "foo": {u"type": "string"}, + "bar": {u"minItems": 2}, + "baz": {u"maximum": 10, u"enum": [2, 4, 6, 8]}, + }, + } + + errors = list(self.validator.iter_errors(instance, schema)) + self.assertEqual(len(errors), 4) + + +class TestValidationErrorMessages(TestCase): + def message_for(self, instance, schema, *args, **kwargs): + kwargs.setdefault("cls", validators.Draft3Validator) + with self.assertRaises(exceptions.ValidationError) as e: + validators.validate(instance, schema, *args, **kwargs) + return e.exception.message + + def test_single_type_failure(self): + message = self.message_for(instance=1, schema={u"type": u"string"}) + self.assertEqual(message, "1 is not of type %r" % u"string") + + def test_single_type_list_failure(self): + message = self.message_for(instance=1, schema={u"type": [u"string"]}) + self.assertEqual(message, "1 is not of type %r" % u"string") + + def test_multiple_type_failure(self): + types = u"string", u"object" + message = self.message_for(instance=1, schema={u"type": list(types)}) + self.assertEqual(message, "1 is not of type %r, %r" % types) + + def test_object_without_title_type_failure(self): + type = {u"type": [{u"minimum": 3}]} + message = self.message_for(instance=1, schema={u"type": [type]}) + self.assertEqual(message, "1 is less than the minimum of 3") + + def test_object_with_named_type_failure(self): + schema = {u"type": [{u"name": "Foo", u"minimum": 3}]} + message = self.message_for(instance=1, schema=schema) + self.assertEqual(message, "1 is less than the minimum of 3") + + def test_minimum(self): + message = self.message_for(instance=1, schema={"minimum": 2}) + self.assertEqual(message, "1 is less than the minimum of 2") + + def test_maximum(self): + message = self.message_for(instance=1, schema={"maximum": 0}) + self.assertEqual(message, "1 is greater than the maximum of 0") + + def test_dependencies_single_element(self): + depend, on = "bar", "foo" + schema = {u"dependencies": {depend: on}} + message = self.message_for( + instance={"bar": 2}, + schema=schema, + cls=validators.Draft3Validator, + ) + self.assertEqual(message, "%r is a dependency of %r" % (on, depend)) + + def test_dependencies_list_draft3(self): + depend, on = "bar", "foo" + schema = {u"dependencies": {depend: [on]}} + message = self.message_for( + instance={"bar": 2}, + schema=schema, + cls=validators.Draft3Validator, + ) + self.assertEqual(message, "%r is a dependency of %r" % (on, depend)) + + def test_dependencies_list_draft7(self): + depend, on = "bar", "foo" + schema = {u"dependencies": {depend: [on]}} + message = self.message_for( + instance={"bar": 2}, + schema=schema, + cls=validators.Draft7Validator, + ) + self.assertEqual(message, "%r is a dependency of %r" % (on, depend)) + + def test_additionalItems_single_failure(self): + message = self.message_for( + instance=[2], + schema={u"items": [], u"additionalItems": False}, + ) + self.assertIn("(2 was unexpected)", message) + + def test_additionalItems_multiple_failures(self): + message = self.message_for( + instance=[1, 2, 3], + schema={u"items": [], u"additionalItems": False} + ) + self.assertIn("(1, 2, 3 were unexpected)", message) + + def test_additionalProperties_single_failure(self): + additional = "foo" + schema = {u"additionalProperties": False} + message = self.message_for(instance={additional: 2}, schema=schema) + self.assertIn("(%r was unexpected)" % (additional,), message) + + def test_additionalProperties_multiple_failures(self): + schema = {u"additionalProperties": False} + message = self.message_for( + instance=dict.fromkeys(["foo", "bar"]), + schema=schema, + ) + + self.assertIn(repr("foo"), message) + self.assertIn(repr("bar"), message) + self.assertIn("were unexpected)", message) + + def test_const(self): + schema = {u"const": 12} + message = self.message_for( + instance={"foo": "bar"}, + schema=schema, + cls=validators.Draft6Validator, + ) + self.assertIn("12 was expected", message) + + def test_contains(self): + schema = {u"contains": {u"const": 12}} + message = self.message_for( + instance=[2, {}, []], + schema=schema, + cls=validators.Draft6Validator, + ) + self.assertIn( + "None of [2, {}, []] are valid under the given schema", + message, + ) + + def test_invalid_format_default_message(self): + checker = FormatChecker(formats=()) + checker.checks(u"thing")(lambda value: False) + + schema = {u"format": u"thing"} + message = self.message_for( + instance="bla", + schema=schema, + format_checker=checker, + ) + + self.assertIn(repr("bla"), message) + self.assertIn(repr("thing"), message) + self.assertIn("is not a", message) + + def test_additionalProperties_false_patternProperties(self): + schema = {u"type": u"object", + u"additionalProperties": False, + u"patternProperties": { + u"^abc$": {u"type": u"string"}, + u"^def$": {u"type": u"string"}, + }} + message = self.message_for( + instance={u"zebra": 123}, + schema=schema, + cls=validators.Draft4Validator, + ) + self.assertEqual( + message, + "{} does not match any of the regexes: {}, {}".format( + repr(u"zebra"), repr(u"^abc$"), repr(u"^def$"), + ), + ) + message = self.message_for( + instance={u"zebra": 123, u"fish": 456}, + schema=schema, + cls=validators.Draft4Validator, + ) + self.assertEqual( + message, + "{}, {} do not match any of the regexes: {}, {}".format( + repr(u"fish"), repr(u"zebra"), repr(u"^abc$"), repr(u"^def$") + ), + ) + + def test_False_schema(self): + message = self.message_for( + instance="something", + schema=False, + cls=validators.Draft7Validator, + ) + self.assertIn("False schema does not allow 'something'", message) + + +class TestValidationErrorDetails(TestCase): + # TODO: These really need unit tests for each individual validator, rather + # than just these higher level tests. + def test_anyOf(self): + instance = 5 + schema = { + "anyOf": [ + {"minimum": 20}, + {"type": "string"}, + ], + } + + validator = validators.Draft4Validator(schema) + errors = list(validator.iter_errors(instance)) + self.assertEqual(len(errors), 1) + e = errors[0] + + self.assertEqual(e.validator, "anyOf") + self.assertEqual(e.validator_value, schema["anyOf"]) + self.assertEqual(e.instance, instance) + self.assertEqual(e.schema, schema) + self.assertIsNone(e.parent) + + self.assertEqual(e.path, deque([])) + self.assertEqual(e.relative_path, deque([])) + self.assertEqual(e.absolute_path, deque([])) + + self.assertEqual(e.schema_path, deque(["anyOf"])) + self.assertEqual(e.relative_schema_path, deque(["anyOf"])) + self.assertEqual(e.absolute_schema_path, deque(["anyOf"])) + + self.assertEqual(len(e.context), 2) + + e1, e2 = sorted_errors(e.context) + + self.assertEqual(e1.validator, "minimum") + self.assertEqual(e1.validator_value, schema["anyOf"][0]["minimum"]) + self.assertEqual(e1.instance, instance) + self.assertEqual(e1.schema, schema["anyOf"][0]) + self.assertIs(e1.parent, e) + + self.assertEqual(e1.path, deque([])) + self.assertEqual(e1.absolute_path, deque([])) + self.assertEqual(e1.relative_path, deque([])) + + self.assertEqual(e1.schema_path, deque([0, "minimum"])) + self.assertEqual(e1.relative_schema_path, deque([0, "minimum"])) + self.assertEqual( + e1.absolute_schema_path, deque(["anyOf", 0, "minimum"]), + ) + + self.assertFalse(e1.context) + + self.assertEqual(e2.validator, "type") + self.assertEqual(e2.validator_value, schema["anyOf"][1]["type"]) + self.assertEqual(e2.instance, instance) + self.assertEqual(e2.schema, schema["anyOf"][1]) + self.assertIs(e2.parent, e) + + self.assertEqual(e2.path, deque([])) + self.assertEqual(e2.relative_path, deque([])) + self.assertEqual(e2.absolute_path, deque([])) + + self.assertEqual(e2.schema_path, deque([1, "type"])) + self.assertEqual(e2.relative_schema_path, deque([1, "type"])) + self.assertEqual(e2.absolute_schema_path, deque(["anyOf", 1, "type"])) + + self.assertEqual(len(e2.context), 0) + + def test_type(self): + instance = {"foo": 1} + schema = { + "type": [ + {"type": "integer"}, + { + "type": "object", + "properties": {"foo": {"enum": [2]}}, + }, + ], + } + + validator = validators.Draft3Validator(schema) + errors = list(validator.iter_errors(instance)) + self.assertEqual(len(errors), 1) + e = errors[0] + + self.assertEqual(e.validator, "type") + self.assertEqual(e.validator_value, schema["type"]) + self.assertEqual(e.instance, instance) + self.assertEqual(e.schema, schema) + self.assertIsNone(e.parent) + + self.assertEqual(e.path, deque([])) + self.assertEqual(e.relative_path, deque([])) + self.assertEqual(e.absolute_path, deque([])) + + self.assertEqual(e.schema_path, deque(["type"])) + self.assertEqual(e.relative_schema_path, deque(["type"])) + self.assertEqual(e.absolute_schema_path, deque(["type"])) + + self.assertEqual(len(e.context), 2) + + e1, e2 = sorted_errors(e.context) + + self.assertEqual(e1.validator, "type") + self.assertEqual(e1.validator_value, schema["type"][0]["type"]) + self.assertEqual(e1.instance, instance) + self.assertEqual(e1.schema, schema["type"][0]) + self.assertIs(e1.parent, e) + + self.assertEqual(e1.path, deque([])) + self.assertEqual(e1.relative_path, deque([])) + self.assertEqual(e1.absolute_path, deque([])) + + self.assertEqual(e1.schema_path, deque([0, "type"])) + self.assertEqual(e1.relative_schema_path, deque([0, "type"])) + self.assertEqual(e1.absolute_schema_path, deque(["type", 0, "type"])) + + self.assertFalse(e1.context) + + self.assertEqual(e2.validator, "enum") + self.assertEqual(e2.validator_value, [2]) + self.assertEqual(e2.instance, 1) + self.assertEqual(e2.schema, {u"enum": [2]}) + self.assertIs(e2.parent, e) + + self.assertEqual(e2.path, deque(["foo"])) + self.assertEqual(e2.relative_path, deque(["foo"])) + self.assertEqual(e2.absolute_path, deque(["foo"])) + + self.assertEqual( + e2.schema_path, deque([1, "properties", "foo", "enum"]), + ) + self.assertEqual( + e2.relative_schema_path, deque([1, "properties", "foo", "enum"]), + ) + self.assertEqual( + e2.absolute_schema_path, + deque(["type", 1, "properties", "foo", "enum"]), + ) + + self.assertFalse(e2.context) + + def test_single_nesting(self): + instance = {"foo": 2, "bar": [1], "baz": 15, "quux": "spam"} + schema = { + "properties": { + "foo": {"type": "string"}, + "bar": {"minItems": 2}, + "baz": {"maximum": 10, "enum": [2, 4, 6, 8]}, + }, + } + + validator = validators.Draft3Validator(schema) + errors = validator.iter_errors(instance) + e1, e2, e3, e4 = sorted_errors(errors) + + self.assertEqual(e1.path, deque(["bar"])) + self.assertEqual(e2.path, deque(["baz"])) + self.assertEqual(e3.path, deque(["baz"])) + self.assertEqual(e4.path, deque(["foo"])) + + self.assertEqual(e1.relative_path, deque(["bar"])) + self.assertEqual(e2.relative_path, deque(["baz"])) + self.assertEqual(e3.relative_path, deque(["baz"])) + self.assertEqual(e4.relative_path, deque(["foo"])) + + self.assertEqual(e1.absolute_path, deque(["bar"])) + self.assertEqual(e2.absolute_path, deque(["baz"])) + self.assertEqual(e3.absolute_path, deque(["baz"])) + self.assertEqual(e4.absolute_path, deque(["foo"])) + + self.assertEqual(e1.validator, "minItems") + self.assertEqual(e2.validator, "enum") + self.assertEqual(e3.validator, "maximum") + self.assertEqual(e4.validator, "type") + + def test_multiple_nesting(self): + instance = [1, {"foo": 2, "bar": {"baz": [1]}}, "quux"] + schema = { + "type": "string", + "items": { + "type": ["string", "object"], + "properties": { + "foo": {"enum": [1, 3]}, + "bar": { + "type": "array", + "properties": { + "bar": {"required": True}, + "baz": {"minItems": 2}, + }, + }, + }, + }, + } + + validator = validators.Draft3Validator(schema) + errors = validator.iter_errors(instance) + e1, e2, e3, e4, e5, e6 = sorted_errors(errors) + + self.assertEqual(e1.path, deque([])) + self.assertEqual(e2.path, deque([0])) + self.assertEqual(e3.path, deque([1, "bar"])) + self.assertEqual(e4.path, deque([1, "bar", "bar"])) + self.assertEqual(e5.path, deque([1, "bar", "baz"])) + self.assertEqual(e6.path, deque([1, "foo"])) + + self.assertEqual(e1.schema_path, deque(["type"])) + self.assertEqual(e2.schema_path, deque(["items", "type"])) + self.assertEqual( + list(e3.schema_path), ["items", "properties", "bar", "type"], + ) + self.assertEqual( + list(e4.schema_path), + ["items", "properties", "bar", "properties", "bar", "required"], + ) + self.assertEqual( + list(e5.schema_path), + ["items", "properties", "bar", "properties", "baz", "minItems"] + ) + self.assertEqual( + list(e6.schema_path), ["items", "properties", "foo", "enum"], + ) + + self.assertEqual(e1.validator, "type") + self.assertEqual(e2.validator, "type") + self.assertEqual(e3.validator, "type") + self.assertEqual(e4.validator, "required") + self.assertEqual(e5.validator, "minItems") + self.assertEqual(e6.validator, "enum") + + def test_recursive(self): + schema = { + "definitions": { + "node": { + "anyOf": [{ + "type": "object", + "required": ["name", "children"], + "properties": { + "name": { + "type": "string", + }, + "children": { + "type": "object", + "patternProperties": { + "^.*$": { + "$ref": "#/definitions/node", + }, + }, + }, + }, + }], + }, + }, + "type": "object", + "required": ["root"], + "properties": {"root": {"$ref": "#/definitions/node"}}, + } + + instance = { + "root": { + "name": "root", + "children": { + "a": { + "name": "a", + "children": { + "ab": { + "name": "ab", + # missing "children" + }, + }, + }, + }, + }, + } + validator = validators.Draft4Validator(schema) + + e, = validator.iter_errors(instance) + self.assertEqual(e.absolute_path, deque(["root"])) + self.assertEqual( + e.absolute_schema_path, deque(["properties", "root", "anyOf"]), + ) + + e1, = e.context + self.assertEqual(e1.absolute_path, deque(["root", "children", "a"])) + self.assertEqual( + e1.absolute_schema_path, deque( + [ + "properties", + "root", + "anyOf", + 0, + "properties", + "children", + "patternProperties", + "^.*$", + "anyOf", + ], + ), + ) + + e2, = e1.context + self.assertEqual( + e2.absolute_path, deque( + ["root", "children", "a", "children", "ab"], + ), + ) + self.assertEqual( + e2.absolute_schema_path, deque( + [ + "properties", + "root", + "anyOf", + 0, + "properties", + "children", + "patternProperties", + "^.*$", + "anyOf", + 0, + "properties", + "children", + "patternProperties", + "^.*$", + "anyOf", + ], + ), + ) + + def test_additionalProperties(self): + instance = {"bar": "bar", "foo": 2} + schema = {"additionalProperties": {"type": "integer", "minimum": 5}} + + validator = validators.Draft3Validator(schema) + errors = validator.iter_errors(instance) + e1, e2 = sorted_errors(errors) + + self.assertEqual(e1.path, deque(["bar"])) + self.assertEqual(e2.path, deque(["foo"])) + + self.assertEqual(e1.validator, "type") + self.assertEqual(e2.validator, "minimum") + + def test_patternProperties(self): + instance = {"bar": 1, "foo": 2} + schema = { + "patternProperties": { + "bar": {"type": "string"}, + "foo": {"minimum": 5}, + }, + } + + validator = validators.Draft3Validator(schema) + errors = validator.iter_errors(instance) + e1, e2 = sorted_errors(errors) + + self.assertEqual(e1.path, deque(["bar"])) + self.assertEqual(e2.path, deque(["foo"])) + + self.assertEqual(e1.validator, "type") + self.assertEqual(e2.validator, "minimum") + + def test_additionalItems(self): + instance = ["foo", 1] + schema = { + "items": [], + "additionalItems": {"type": "integer", "minimum": 5}, + } + + validator = validators.Draft3Validator(schema) + errors = validator.iter_errors(instance) + e1, e2 = sorted_errors(errors) + + self.assertEqual(e1.path, deque([0])) + self.assertEqual(e2.path, deque([1])) + + self.assertEqual(e1.validator, "type") + self.assertEqual(e2.validator, "minimum") + + def test_additionalItems_with_items(self): + instance = ["foo", "bar", 1] + schema = { + "items": [{}], + "additionalItems": {"type": "integer", "minimum": 5}, + } + + validator = validators.Draft3Validator(schema) + errors = validator.iter_errors(instance) + e1, e2 = sorted_errors(errors) + + self.assertEqual(e1.path, deque([1])) + self.assertEqual(e2.path, deque([2])) + + self.assertEqual(e1.validator, "type") + self.assertEqual(e2.validator, "minimum") + + def test_propertyNames(self): + instance = {"foo": 12} + schema = {"propertyNames": {"not": {"const": "foo"}}} + + validator = validators.Draft7Validator(schema) + error, = validator.iter_errors(instance) + + self.assertEqual(error.validator, "not") + self.assertEqual( + error.message, + "%r is not allowed for %r" % ({"const": "foo"}, "foo"), + ) + self.assertEqual(error.path, deque([])) + self.assertEqual(error.schema_path, deque(["propertyNames", "not"])) + + def test_if_then(self): + schema = { + "if": {"const": 12}, + "then": {"const": 13}, + } + + validator = validators.Draft7Validator(schema) + error, = validator.iter_errors(12) + + self.assertEqual(error.validator, "const") + self.assertEqual(error.message, "13 was expected") + self.assertEqual(error.path, deque([])) + self.assertEqual(error.schema_path, deque(["if", "then", "const"])) + + def test_if_else(self): + schema = { + "if": {"const": 12}, + "else": {"const": 13}, + } + + validator = validators.Draft7Validator(schema) + error, = validator.iter_errors(15) + + self.assertEqual(error.validator, "const") + self.assertEqual(error.message, "13 was expected") + self.assertEqual(error.path, deque([])) + self.assertEqual(error.schema_path, deque(["if", "else", "const"])) + + def test_boolean_schema_False(self): + validator = validators.Draft7Validator(False) + error, = validator.iter_errors(12) + + self.assertEqual( + ( + error.message, + error.validator, + error.validator_value, + error.instance, + error.schema, + error.schema_path, + ), + ( + "False schema does not allow 12", + None, + None, + 12, + False, + deque([]), + ), + ) + + def test_ref(self): + ref, schema = "someRef", {"additionalProperties": {"type": "integer"}} + validator = validators.Draft7Validator( + {"$ref": ref}, + resolver=validators.RefResolver("", {}, store={ref: schema}), + ) + error, = validator.iter_errors({"foo": "notAnInteger"}) + + self.assertEqual( + ( + error.message, + error.validator, + error.validator_value, + error.instance, + error.absolute_path, + error.schema, + error.schema_path, + ), + ( + "'notAnInteger' is not of type 'integer'", + "type", + "integer", + "notAnInteger", + deque(["foo"]), + {"type": "integer"}, + deque(["additionalProperties", "type"]), + ), + ) + + +class MetaSchemaTestsMixin(object): + # TODO: These all belong upstream + def test_invalid_properties(self): + with self.assertRaises(exceptions.SchemaError): + self.Validator.check_schema({"properties": {"test": object()}}) + + def test_minItems_invalid_string(self): + with self.assertRaises(exceptions.SchemaError): + # needs to be an integer + self.Validator.check_schema({"minItems": "1"}) + + def test_enum_allows_empty_arrays(self): + """ + Technically, all the spec says is they SHOULD have elements, not MUST. + + See https://github.com/Julian/jsonschema/issues/529. + """ + self.Validator.check_schema({"enum": []}) + + def test_enum_allows_non_unique_items(self): + """ + Technically, all the spec says is they SHOULD be unique, not MUST. + + See https://github.com/Julian/jsonschema/issues/529. + """ + self.Validator.check_schema({"enum": [12, 12]}) + + +class ValidatorTestMixin(MetaSchemaTestsMixin, object): + def test_valid_instances_are_valid(self): + schema, instance = self.valid + self.assertTrue(self.Validator(schema).is_valid(instance)) + + def test_invalid_instances_are_not_valid(self): + schema, instance = self.invalid + self.assertFalse(self.Validator(schema).is_valid(instance)) + + def test_non_existent_properties_are_ignored(self): + self.Validator({object(): object()}).validate(instance=object()) + + def test_it_creates_a_ref_resolver_if_not_provided(self): + self.assertIsInstance( + self.Validator({}).resolver, + validators.RefResolver, + ) + + def test_it_delegates_to_a_ref_resolver(self): + ref, schema = "someCoolRef", {"type": "integer"} + resolver = validators.RefResolver("", {}, store={ref: schema}) + validator = self.Validator({"$ref": ref}, resolver=resolver) + + with self.assertRaises(exceptions.ValidationError): + validator.validate(None) + + def test_it_delegates_to_a_legacy_ref_resolver(self): + """ + Legacy RefResolvers support only the context manager form of + resolution. + """ + + class LegacyRefResolver(object): + @contextmanager + def resolving(this, ref): + self.assertEqual(ref, "the ref") + yield {"type": "integer"} + + resolver = LegacyRefResolver() + schema = {"$ref": "the ref"} + + with self.assertRaises(exceptions.ValidationError): + self.Validator(schema, resolver=resolver).validate(None) + + def test_is_type_is_true_for_valid_type(self): + self.assertTrue(self.Validator({}).is_type("foo", "string")) + + def test_is_type_is_false_for_invalid_type(self): + self.assertFalse(self.Validator({}).is_type("foo", "array")) + + def test_is_type_evades_bool_inheriting_from_int(self): + self.assertFalse(self.Validator({}).is_type(True, "integer")) + self.assertFalse(self.Validator({}).is_type(True, "number")) + + @unittest.skipIf(PY3, "In Python 3 json.load always produces unicode") + def test_string_a_bytestring_is_a_string(self): + self.Validator({"type": "string"}).validate(b"foo") + + def test_patterns_can_be_native_strings(self): + """ + See https://github.com/Julian/jsonschema/issues/611. + """ + self.Validator({"pattern": "foo"}).validate("foo") + + def test_it_can_validate_with_decimals(self): + schema = {"items": {"type": "number"}} + Validator = validators.extend( + self.Validator, + type_checker=self.Validator.TYPE_CHECKER.redefine( + "number", + lambda checker, thing: isinstance( + thing, (int, float, Decimal), + ) and not isinstance(thing, bool), + ) + ) + + validator = Validator(schema) + validator.validate([1, 1.1, Decimal(1) / Decimal(8)]) + + invalid = ["foo", {}, [], True, None] + self.assertEqual( + [error.instance for error in validator.iter_errors(invalid)], + invalid, + ) + + def test_it_returns_true_for_formats_it_does_not_know_about(self): + validator = self.Validator( + {"format": "carrot"}, format_checker=FormatChecker(), + ) + validator.validate("bugs") + + def test_it_does_not_validate_formats_by_default(self): + validator = self.Validator({}) + self.assertIsNone(validator.format_checker) + + def test_it_validates_formats_if_a_checker_is_provided(self): + checker = FormatChecker() + bad = ValueError("Bad!") + + @checker.checks("foo", raises=ValueError) + def check(value): + if value == "good": + return True + elif value == "bad": + raise bad + else: # pragma: no cover + self.fail("What is {}? [Baby Don't Hurt Me]".format(value)) + + validator = self.Validator( + {"format": "foo"}, format_checker=checker, + ) + + validator.validate("good") + with self.assertRaises(exceptions.ValidationError) as cm: + validator.validate("bad") + + # Make sure original cause is attached + self.assertIs(cm.exception.cause, bad) + + def test_non_string_custom_type(self): + non_string_type = object() + schema = {"type": [non_string_type]} + Crazy = validators.extend( + self.Validator, + type_checker=self.Validator.TYPE_CHECKER.redefine( + non_string_type, + lambda checker, thing: isinstance(thing, int), + ) + ) + Crazy(schema).validate(15) + + def test_it_properly_formats_tuples_in_errors(self): + """ + A tuple instance properly formats validation errors for uniqueItems. + + See https://github.com/Julian/jsonschema/pull/224 + """ + TupleValidator = validators.extend( + self.Validator, + type_checker=self.Validator.TYPE_CHECKER.redefine( + "array", + lambda checker, thing: isinstance(thing, tuple), + ) + ) + with self.assertRaises(exceptions.ValidationError) as e: + TupleValidator({"uniqueItems": True}).validate((1, 1)) + self.assertIn("(1, 1) has non-unique elements", str(e.exception)) + + +class AntiDraft6LeakMixin(object): + """ + Make sure functionality from draft 6 doesn't leak backwards in time. + """ + + def test_True_is_not_a_schema(self): + with self.assertRaises(exceptions.SchemaError) as e: + self.Validator.check_schema(True) + self.assertIn("True is not of type", str(e.exception)) + + def test_False_is_not_a_schema(self): + with self.assertRaises(exceptions.SchemaError) as e: + self.Validator.check_schema(False) + self.assertIn("False is not of type", str(e.exception)) + + @unittest.skip(bug(523)) + def test_True_is_not_a_schema_even_if_you_forget_to_check(self): + resolver = validators.RefResolver("", {}) + with self.assertRaises(Exception) as e: + self.Validator(True, resolver=resolver).validate(12) + self.assertNotIsInstance(e.exception, exceptions.ValidationError) + + @unittest.skip(bug(523)) + def test_False_is_not_a_schema_even_if_you_forget_to_check(self): + resolver = validators.RefResolver("", {}) + with self.assertRaises(Exception) as e: + self.Validator(False, resolver=resolver).validate(12) + self.assertNotIsInstance(e.exception, exceptions.ValidationError) + + +class TestDraft3Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase): + Validator = validators.Draft3Validator + valid = {}, {} + invalid = {"type": "integer"}, "foo" + + def test_any_type_is_valid_for_type_any(self): + validator = self.Validator({"type": "any"}) + validator.validate(object()) + + def test_any_type_is_redefinable(self): + """ + Sigh, because why not. + """ + Crazy = validators.extend( + self.Validator, + type_checker=self.Validator.TYPE_CHECKER.redefine( + "any", lambda checker, thing: isinstance(thing, int), + ) + ) + validator = Crazy({"type": "any"}) + validator.validate(12) + with self.assertRaises(exceptions.ValidationError): + validator.validate("foo") + + def test_is_type_is_true_for_any_type(self): + self.assertTrue(self.Validator({}).is_valid(object(), {"type": "any"})) + + def test_is_type_does_not_evade_bool_if_it_is_being_tested(self): + self.assertTrue(self.Validator({}).is_type(True, "boolean")) + self.assertTrue(self.Validator({}).is_valid(True, {"type": "any"})) + + +class TestDraft4Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase): + Validator = validators.Draft4Validator + valid = {}, {} + invalid = {"type": "integer"}, "foo" + + +class TestDraft6Validator(ValidatorTestMixin, TestCase): + Validator = validators.Draft6Validator + valid = {}, {} + invalid = {"type": "integer"}, "foo" + + +class TestDraft7Validator(ValidatorTestMixin, TestCase): + Validator = validators.Draft7Validator + valid = {}, {} + invalid = {"type": "integer"}, "foo" + + +class TestValidatorFor(SynchronousTestCase): + def test_draft_3(self): + schema = {"$schema": "http://json-schema.org/draft-03/schema"} + self.assertIs( + validators.validator_for(schema), + validators.Draft3Validator, + ) + + schema = {"$schema": "http://json-schema.org/draft-03/schema#"} + self.assertIs( + validators.validator_for(schema), + validators.Draft3Validator, + ) + + def test_draft_4(self): + schema = {"$schema": "http://json-schema.org/draft-04/schema"} + self.assertIs( + validators.validator_for(schema), + validators.Draft4Validator, + ) + + schema = {"$schema": "http://json-schema.org/draft-04/schema#"} + self.assertIs( + validators.validator_for(schema), + validators.Draft4Validator, + ) + + def test_draft_6(self): + schema = {"$schema": "http://json-schema.org/draft-06/schema"} + self.assertIs( + validators.validator_for(schema), + validators.Draft6Validator, + ) + + schema = {"$schema": "http://json-schema.org/draft-06/schema#"} + self.assertIs( + validators.validator_for(schema), + validators.Draft6Validator, + ) + + def test_draft_7(self): + schema = {"$schema": "http://json-schema.org/draft-07/schema"} + self.assertIs( + validators.validator_for(schema), + validators.Draft7Validator, + ) + + schema = {"$schema": "http://json-schema.org/draft-07/schema#"} + self.assertIs( + validators.validator_for(schema), + validators.Draft7Validator, + ) + + def test_True(self): + self.assertIs( + validators.validator_for(True), + validators._LATEST_VERSION, + ) + + def test_False(self): + self.assertIs( + validators.validator_for(False), + validators._LATEST_VERSION, + ) + + def test_custom_validator(self): + Validator = validators.create( + meta_schema={"id": "meta schema id"}, + version="12", + id_of=lambda s: s.get("id", ""), + ) + schema = {"$schema": "meta schema id"} + self.assertIs( + validators.validator_for(schema), + Validator, + ) + + def test_custom_validator_draft6(self): + Validator = validators.create( + meta_schema={"$id": "meta schema $id"}, + version="13", + ) + schema = {"$schema": "meta schema $id"} + self.assertIs( + validators.validator_for(schema), + Validator, + ) + + def test_validator_for_jsonschema_default(self): + self.assertIs(validators.validator_for({}), validators._LATEST_VERSION) + + def test_validator_for_custom_default(self): + self.assertIs(validators.validator_for({}, default=None), None) + + def test_warns_if_meta_schema_specified_was_not_found(self): + self.assertWarns( + category=DeprecationWarning, + message=( + "The metaschema specified by $schema was not found. " + "Using the latest draft to validate, but this will raise " + "an error in the future." + ), + # https://tm.tl/9363 :'( + filename=sys.modules[self.assertWarns.__module__].__file__, + + f=validators.validator_for, + schema={u"$schema": "unknownSchema"}, + default={}, + ) + + def test_does_not_warn_if_meta_schema_is_unspecified(self): + validators.validator_for(schema={}, default={}), + self.assertFalse(self.flushWarnings()) + + +class TestValidate(SynchronousTestCase): + def assertUses(self, schema, Validator): + result = [] + self.patch(Validator, "check_schema", result.append) + validators.validate({}, schema) + self.assertEqual(result, [schema]) + + def test_draft3_validator_is_chosen(self): + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-03/schema#"}, + Validator=validators.Draft3Validator, + ) + # Make sure it works without the empty fragment + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-03/schema"}, + Validator=validators.Draft3Validator, + ) + + def test_draft4_validator_is_chosen(self): + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-04/schema#"}, + Validator=validators.Draft4Validator, + ) + # Make sure it works without the empty fragment + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-04/schema"}, + Validator=validators.Draft4Validator, + ) + + def test_draft6_validator_is_chosen(self): + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-06/schema#"}, + Validator=validators.Draft6Validator, + ) + # Make sure it works without the empty fragment + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-06/schema"}, + Validator=validators.Draft6Validator, + ) + + def test_draft7_validator_is_chosen(self): + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-07/schema#"}, + Validator=validators.Draft7Validator, + ) + # Make sure it works without the empty fragment + self.assertUses( + schema={"$schema": "http://json-schema.org/draft-07/schema"}, + Validator=validators.Draft7Validator, + ) + + def test_draft7_validator_is_the_default(self): + self.assertUses(schema={}, Validator=validators.Draft7Validator) + + def test_validation_error_message(self): + with self.assertRaises(exceptions.ValidationError) as e: + validators.validate(12, {"type": "string"}) + self.assertRegexpMatches( + str(e.exception), + "(?s)Failed validating u?'.*' in schema.*On instance", + ) + + def test_schema_error_message(self): + with self.assertRaises(exceptions.SchemaError) as e: + validators.validate(12, {"type": 12}) + self.assertRegexpMatches( + str(e.exception), + "(?s)Failed validating u?'.*' in metaschema.*On schema", + ) + + def test_it_uses_best_match(self): + # This is a schema that best_match will recurse into + schema = {"oneOf": [{"type": "string"}, {"type": "array"}]} + with self.assertRaises(exceptions.ValidationError) as e: + validators.validate(12, schema) + self.assertIn("12 is not of type", str(e.exception)) + + +class TestRefResolver(SynchronousTestCase): + + base_uri = "" + stored_uri = "foo://stored" + stored_schema = {"stored": "schema"} + + def setUp(self): + self.referrer = {} + self.store = {self.stored_uri: self.stored_schema} + self.resolver = validators.RefResolver( + self.base_uri, self.referrer, self.store, + ) + + def test_it_does_not_retrieve_schema_urls_from_the_network(self): + ref = validators.Draft3Validator.META_SCHEMA["id"] + self.patch( + self.resolver, + "resolve_remote", + lambda *args, **kwargs: self.fail("Should not have been called!"), + ) + with self.resolver.resolving(ref) as resolved: + pass + self.assertEqual(resolved, validators.Draft3Validator.META_SCHEMA) + + def test_it_resolves_local_refs(self): + ref = "#/properties/foo" + self.referrer["properties"] = {"foo": object()} + with self.resolver.resolving(ref) as resolved: + self.assertEqual(resolved, self.referrer["properties"]["foo"]) + + def test_it_resolves_local_refs_with_id(self): + schema = {"id": "http://bar/schema#", "a": {"foo": "bar"}} + resolver = validators.RefResolver.from_schema( + schema, + id_of=lambda schema: schema.get(u"id", u""), + ) + with resolver.resolving("#/a") as resolved: + self.assertEqual(resolved, schema["a"]) + with resolver.resolving("http://bar/schema#/a") as resolved: + self.assertEqual(resolved, schema["a"]) + + def test_it_retrieves_stored_refs(self): + with self.resolver.resolving(self.stored_uri) as resolved: + self.assertIs(resolved, self.stored_schema) + + self.resolver.store["cached_ref"] = {"foo": 12} + with self.resolver.resolving("cached_ref#/foo") as resolved: + self.assertEqual(resolved, 12) + + def test_it_retrieves_unstored_refs_via_requests(self): + ref = "http://bar#baz" + schema = {"baz": 12} + + if "requests" in sys.modules: + self.addCleanup( + sys.modules.__setitem__, "requests", sys.modules["requests"], + ) + sys.modules["requests"] = ReallyFakeRequests({"http://bar": schema}) + + with self.resolver.resolving(ref) as resolved: + self.assertEqual(resolved, 12) + + def test_it_retrieves_unstored_refs_via_urlopen(self): + ref = "http://bar#baz" + schema = {"baz": 12} + + if "requests" in sys.modules: + self.addCleanup( + sys.modules.__setitem__, "requests", sys.modules["requests"], + ) + sys.modules["requests"] = None + + @contextmanager + def fake_urlopen(url): + self.assertEqual(url, "http://bar") + yield BytesIO(json.dumps(schema).encode("utf8")) + + self.addCleanup(setattr, validators, "urlopen", validators.urlopen) + validators.urlopen = fake_urlopen + + with self.resolver.resolving(ref) as resolved: + pass + self.assertEqual(resolved, 12) + + def test_it_retrieves_local_refs_via_urlopen(self): + with tempfile.NamedTemporaryFile(delete=False, mode="wt") as tempf: + self.addCleanup(os.remove, tempf.name) + json.dump({"foo": "bar"}, tempf) + + ref = "file://{}#foo".format(pathname2url(tempf.name)) + with self.resolver.resolving(ref) as resolved: + self.assertEqual(resolved, "bar") + + def test_it_can_construct_a_base_uri_from_a_schema(self): + schema = {"id": "foo"} + resolver = validators.RefResolver.from_schema( + schema, + id_of=lambda schema: schema.get(u"id", u""), + ) + self.assertEqual(resolver.base_uri, "foo") + self.assertEqual(resolver.resolution_scope, "foo") + with resolver.resolving("") as resolved: + self.assertEqual(resolved, schema) + with resolver.resolving("#") as resolved: + self.assertEqual(resolved, schema) + with resolver.resolving("foo") as resolved: + self.assertEqual(resolved, schema) + with resolver.resolving("foo#") as resolved: + self.assertEqual(resolved, schema) + + def test_it_can_construct_a_base_uri_from_a_schema_without_id(self): + schema = {} + resolver = validators.RefResolver.from_schema(schema) + self.assertEqual(resolver.base_uri, "") + self.assertEqual(resolver.resolution_scope, "") + with resolver.resolving("") as resolved: + self.assertEqual(resolved, schema) + with resolver.resolving("#") as resolved: + self.assertEqual(resolved, schema) + + def test_custom_uri_scheme_handlers(self): + def handler(url): + self.assertEqual(url, ref) + return schema + + schema = {"foo": "bar"} + ref = "foo://bar" + resolver = validators.RefResolver("", {}, handlers={"foo": handler}) + with resolver.resolving(ref) as resolved: + self.assertEqual(resolved, schema) + + def test_cache_remote_on(self): + response = [object()] + + def handler(url): + try: + return response.pop() + except IndexError: # pragma: no cover + self.fail("Response must not have been cached!") + + ref = "foo://bar" + resolver = validators.RefResolver( + "", {}, cache_remote=True, handlers={"foo": handler}, + ) + with resolver.resolving(ref): + pass + with resolver.resolving(ref): + pass + + def test_cache_remote_off(self): + response = [object()] + + def handler(url): + try: + return response.pop() + except IndexError: # pragma: no cover + self.fail("Handler called twice!") + + ref = "foo://bar" + resolver = validators.RefResolver( + "", {}, cache_remote=False, handlers={"foo": handler}, + ) + with resolver.resolving(ref): + pass + + def test_if_you_give_it_junk_you_get_a_resolution_error(self): + error = ValueError("Oh no! What's this?") + + def handler(url): + raise error + + ref = "foo://bar" + resolver = validators.RefResolver("", {}, handlers={"foo": handler}) + with self.assertRaises(exceptions.RefResolutionError) as err: + with resolver.resolving(ref): + self.fail("Shouldn't get this far!") # pragma: no cover + self.assertEqual(err.exception, exceptions.RefResolutionError(error)) + + def test_helpful_error_message_on_failed_pop_scope(self): + resolver = validators.RefResolver("", {}) + resolver.pop_scope() + with self.assertRaises(exceptions.RefResolutionError) as exc: + resolver.pop_scope() + self.assertIn("Failed to pop the scope", str(exc.exception)) + + +def sorted_errors(errors): + def key(error): + return ( + [str(e) for e in error.path], + [str(e) for e in error.schema_path], + ) + return sorted(errors, key=key) + + +@attr.s +class ReallyFakeRequests(object): + + _responses = attr.ib() + + def get(self, url): + response = self._responses.get(url) + if url is None: # pragma: no cover + raise ValueError("Unknown URL: " + repr(url)) + return _ReallyFakeJSONResponse(json.dumps(response)) + + +@attr.s +class _ReallyFakeJSONResponse(object): + + _response = attr.ib() + + def json(self): + return json.loads(self._response) diff --git a/src/superannotate_schemas/utils.py b/src/superannotate_schemas/utils.py deleted file mode 100644 index 4ea7cc8..0000000 --- a/src/superannotate_schemas/utils.py +++ /dev/null @@ -1,45 +0,0 @@ -import os -from collections import defaultdict - -from pydantic import ValidationError - - -def get_tabulation_size() -> int: - try: - return int(os.get_terminal_size().columns / 2) - except OSError: - return 48 - - -def wrap_error(e: ValidationError) -> str: - tabulation = get_tabulation_size() - error_messages = defaultdict(list) - for error in e.errors(): - errors_list = list(error["loc"]) - if "__root__" in errors_list: - errors_list.remove("__root__") - errors_list[1::] = [ - f"[{i}]" if isinstance(i, int) else f".{i}" for i in errors_list[1::] - ] - error_messages["".join(errors_list)].append(error["msg"]) - texts = [] - for field, text in error_messages.items(): - texts.append( - "{} {}{}".format( - field, - " " * (tabulation - len(field)), - f"\n {' ' * tabulation}".join(text), - ) - ) - return "\n".join(texts) - - -def uniquify(path): - filename, extension = os.path.splitext(path) - counter = 1 - - while os.path.exists(path): - path = filename + " (" + str(counter) + ")" + extension - counter += 1 - - return path \ No newline at end of file diff --git a/src/superannotate_schemas/validators.py b/src/superannotate_schemas/validators.py index a093e03..b0d2a04 100644 --- a/src/superannotate_schemas/validators.py +++ b/src/superannotate_schemas/validators.py @@ -1,104 +1,970 @@ -import copy +""" +Creation and extension of validators, with implementations for existing drafts. +""" +from __future__ import division -from abc import ABCMeta -from abc import abstractmethod -from typing import Any -from typing import Type +from warnings import warn +import contextlib +import json +import numbers -from superannotate_schemas.utils import wrap_error -from superannotate_schemas.schemas.external import PixelAnnotation as ExternalPixelAnnotation -from superannotate_schemas.schemas.external import VectorAnnotation as ExternalVectorAnnotation -from superannotate_schemas.schemas.external import VideoAnnotation as ExternalVideoAnnotation -from superannotate_schemas.schemas.external import DocumentAnnotation as ExternalDocumentAnnotation +from six import add_metaclass -from superannotate_schemas.schemas.internal import PixelAnnotation as InternalPixelAnnotation -from superannotate_schemas.schemas.internal import VectorAnnotation as InternalVectorAnnotation -from superannotate_schemas.schemas.internal import VideoAnnotation as InternalVideoAnnotation -from superannotate_schemas.schemas.internal import DocumentAnnotation as InternalDocumentAnnotation +from superannotate_schemas import ( + _legacy_validators, + _types, + _utils, + _validators, + exceptions, +) +from superannotate_schemas.compat import ( + Sequence, + int_types, + iteritems, + lru_cache, + str_types, + unquote, + urldefrag, + urljoin, + urlopen, + urlsplit, +) +# Sigh. https://gitlab.com/pycqa/flake8/issues/280 +# https://github.com/pyga/ebb-lint/issues/7 +# Imported for backwards compatibility. +from superannotate_schemas.exceptions import ErrorTree +ErrorTree -from superannotate_schemas.schemas.base import BaseModel -from pydantic import Extra -from pydantic import ValidationError +class _DontDoThat(Exception): + """ + Raised when a Validators with non-default type checker is misused. -class BaseValidator(metaclass=ABCMeta): - MODEL: Type[BaseModel] + Asking one for DEFAULT_TYPES doesn't make sense, since type checkers + exist for the unrepresentable cases where DEFAULT_TYPES can't + represent the type relationship. + """ - def __init__(self, data: Any, allow_extra: bool = True): - self.data = data - self._validation_output = None - self._extra = Extra.allow if allow_extra else Extra.forbid + def __str__(self): + return "DEFAULT_TYPES cannot be used on Validators using TypeCheckers" - @classmethod - def validate(cls, data: Any, extra=True): - return cls.MODEL(**data) - def _validate(self): - model = copy.deepcopy(self.MODEL) - model.Config.extra = self._extra - self.data = model(**self.data).dict(by_alias=True, exclude_none=True) +validators = {} +meta_schemas = _utils.URIDict() - @abstractmethod - def is_valid(self) -> bool: - raise NotImplementedError - @abstractmethod - def generate_report(self) -> str: - raise NotImplementedError +def _generate_legacy_type_checks(types=()): + """ + Generate newer-style type checks out of JSON-type-name-to-type mappings. + Arguments: -class BaseSchemaValidator(BaseValidator): - MODEL = BaseModel() + types (dict): - def is_valid(self) -> bool: - try: - self._validate() - except ValidationError as e: - self._validation_output = e - return not bool(self._validation_output) + A mapping of type names to their Python types - def generate_report(self) -> str: - return wrap_error(self._validation_output) + Returns: + A dictionary of definitions to pass to `TypeChecker` + """ + types = dict(types) -class ValidatorFactory: - @staticmethod - def _get_default_schema(): - return type('CopyOfB', BaseSchemaValidator.__bases__, dict(BaseSchemaValidator.__dict__)) + def gen_type_check(pytypes): + pytypes = _utils.flatten(pytypes) - @staticmethod - def get_validator(model: Any): - schema = ValidatorFactory._get_default_schema() - schema.MODEL = model - return schema + def type_check(checker, instance): + if isinstance(instance, bool): + if bool not in pytypes: + return False + return isinstance(instance, pytypes) - def __class_getitem__(cls, item): - return cls.get_validator(item) + return type_check + definitions = {} + for typename, pytypes in iteritems(types): + definitions[typename] = gen_type_check(pytypes) -class AnnotationValidators: - VALIDATORS = { - "pixel": ( - ValidatorFactory.get_validator(ExternalPixelAnnotation), - ValidatorFactory.get_validator(InternalPixelAnnotation) - ), - "vector": ( - ValidatorFactory.get_validator(ExternalVectorAnnotation), - ValidatorFactory.get_validator(InternalVectorAnnotation) - ), - "video": ( - ValidatorFactory.get_validator(ExternalVideoAnnotation), - ValidatorFactory.get_validator(InternalVideoAnnotation) + return definitions + + +_DEPRECATED_DEFAULT_TYPES = { + u"array": list, + u"boolean": bool, + u"integer": int_types, + u"null": type(None), + u"number": numbers.Number, + u"object": dict, + u"string": str_types, +} +_TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES = _types.TypeChecker( + type_checkers=_generate_legacy_type_checks(_DEPRECATED_DEFAULT_TYPES), +) + + +def validates(version): + """ + Register the decorated validator for a ``version`` of the specification. + + Registered validators and their meta schemas will be considered when + parsing ``$schema`` properties' URIs. + + Arguments: + + version (str): + + An identifier to use as the version's name + + Returns: + + collections.Callable: + + a class decorator to decorate the validator with the version + """ + + def _validates(cls): + validators[version] = cls + meta_schema_id = cls.ID_OF(cls.META_SCHEMA) + if meta_schema_id: + meta_schemas[meta_schema_id] = cls + return cls + return _validates + + +def _DEFAULT_TYPES(self): + if self._CREATED_WITH_DEFAULT_TYPES is None: + raise _DontDoThat() + + warn( + ( + "The DEFAULT_TYPES attribute is deprecated. " + "See the type checker attached to this validator instead." ), - "document": ( - ValidatorFactory.get_validator(ExternalDocumentAnnotation), - ValidatorFactory.get_validator(InternalDocumentAnnotation) + DeprecationWarning, + stacklevel=2, + ) + return self._DEFAULT_TYPES + + +class _DefaultTypesDeprecatingMetaClass(type): + DEFAULT_TYPES = property(_DEFAULT_TYPES) + + +def _id_of(schema): + if schema is True or schema is False: + return u"" + return schema.get(u"$id", u"") + + +def create( + meta_schema, + validators=(), + version=None, + default_types=None, + type_checker=None, + id_of=_id_of, +): + """ + Create a new validator class. + + Arguments: + + meta_schema (collections.Mapping): + + the meta schema for the new validator class + + validators (collections.Mapping): + + a mapping from names to callables, where each callable will + validate the schema property with the given name. + + Each callable should take 4 arguments: + + 1. a validator instance, + 2. the value of the property being validated within the + instance + 3. the instance + 4. the schema + + version (str): + + an identifier for the version that this validator class will + validate. If provided, the returned validator class will + have its ``__name__`` set to include the version, and also + will have `jsonschema.validators.validates` automatically + called for the given version. + + type_checker (jsonschema.TypeChecker): + + a type checker, used when applying the :validator:`type` validator. + + If unprovided, a `jsonschema.TypeChecker` will be created + with a set of default types typical of JSON Schema drafts. + + default_types (collections.Mapping): + + .. deprecated:: 3.0.0 + + Please use the type_checker argument instead. + + If set, it provides mappings of JSON types to Python types + that will be converted to functions and redefined in this + object's `jsonschema.TypeChecker`. + + id_of (collections.Callable): + + A function that given a schema, returns its ID. + + Returns: + + a new `jsonschema.IValidator` class + """ + + if default_types is not None: + if type_checker is not None: + raise TypeError( + "Do not specify default_types when providing a type checker.", + ) + _created_with_default_types = True + warn( + ( + "The default_types argument is deprecated. " + "Use the type_checker argument instead." + ), + DeprecationWarning, + stacklevel=2, + ) + type_checker = _types.TypeChecker( + type_checkers=_generate_legacy_type_checks(default_types), + ) + else: + default_types = _DEPRECATED_DEFAULT_TYPES + if type_checker is None: + _created_with_default_types = False + type_checker = _TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES + elif type_checker is _TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES: + _created_with_default_types = False + else: + _created_with_default_types = None + + @add_metaclass(_DefaultTypesDeprecatingMetaClass) + class Validator(object): + + VALIDATORS = dict(validators) + META_SCHEMA = dict(meta_schema) + TYPE_CHECKER = type_checker + ID_OF = staticmethod(id_of) + + DEFAULT_TYPES = property(_DEFAULT_TYPES) + _DEFAULT_TYPES = dict(default_types) + _CREATED_WITH_DEFAULT_TYPES = _created_with_default_types + + def __init__( + self, + schema, + types=(), + resolver=None, + format_checker=None, + ): + if types: + warn( + ( + "The types argument is deprecated. Provide " + "a type_checker to jsonschema.validators.extend " + "instead." + ), + DeprecationWarning, + stacklevel=2, + ) + + self.TYPE_CHECKER = self.TYPE_CHECKER.redefine_many( + _generate_legacy_type_checks(types), + ) + + if resolver is None: + resolver = RefResolver.from_schema(schema, id_of=id_of) + + self.resolver = resolver + self.format_checker = format_checker + self.schema = schema + + @classmethod + def check_schema(cls, schema): + for error in cls(cls.META_SCHEMA).iter_errors(schema): + raise exceptions.SchemaError.create_from(error) + + def iter_errors(self, instance, _schema=None): + if _schema is None: + _schema = self.schema + + if _schema is True: + return + elif _schema is False: + yield exceptions.ValidationError( + "False schema does not allow %r" % (instance,), + validator=None, + validator_value=None, + instance=instance, + schema=_schema, + ) + return + + scope = id_of(_schema) + if scope: + self.resolver.push_scope(scope) + try: + ref = _schema.get(u"$ref") + if ref is not None: + validators = [(u"$ref", ref)] + else: + validators = iteritems(_schema) + + for k, v in validators: + validator = self.VALIDATORS.get(k) + if validator is None: + continue + + errors = validator(self, v, instance, _schema) or () + for error in errors: + # set details if not already set by the called fn + error._set( + validator=k, + validator_value=v, + instance=instance, + schema=_schema, + ) + if k != u"$ref": + error.schema_path.appendleft(k) + yield error + finally: + if scope: + self.resolver.pop_scope() + + def descend(self, instance, schema, path=None, schema_path=None): + for error in self.iter_errors(instance, schema): + if path is not None: + error.path.appendleft(path) + if schema_path is not None: + error.schema_path.appendleft(schema_path) + yield error + + def validate(self, *args, **kwargs): + for error in self.iter_errors(*args, **kwargs): + raise error + + def is_type(self, instance, type): + try: + return self.TYPE_CHECKER.is_type(instance, type) + except exceptions.UndefinedTypeCheck: + raise exceptions.UnknownType(type, instance, self.schema) + + def is_valid(self, instance, _schema=None): + error = next(self.iter_errors(instance, _schema), None) + return error is None + + if version is not None: + Validator = validates(version)(Validator) + Validator.__name__ = version.title().replace(" ", "") + "Validator" + + return Validator + + +def extend(validator, validators=(), version=None, type_checker=None): + """ + Create a new validator class by extending an existing one. + + Arguments: + + validator (jsonschema.IValidator): + + an existing validator class + + validators (collections.Mapping): + + a mapping of new validator callables to extend with, whose + structure is as in `create`. + + .. note:: + + Any validator callables with the same name as an + existing one will (silently) replace the old validator + callable entirely, effectively overriding any validation + done in the "parent" validator class. + + If you wish to instead extend the behavior of a parent's + validator callable, delegate and call it directly in + the new validator function by retrieving it using + ``OldValidator.VALIDATORS["validator_name"]``. + + version (str): + + a version for the new validator class + + type_checker (jsonschema.TypeChecker): + + a type checker, used when applying the :validator:`type` validator. + + If unprovided, the type checker of the extended + `jsonschema.IValidator` will be carried along.` + + Returns: + + a new `jsonschema.IValidator` class extending the one provided + + .. note:: Meta Schemas + + The new validator class will have its parent's meta schema. + + If you wish to change or extend the meta schema in the new + validator class, modify ``META_SCHEMA`` directly on the returned + class. Note that no implicit copying is done, so a copy should + likely be made before modifying it, in order to not affect the + old validator. + """ + + all_validators = dict(validator.VALIDATORS) + all_validators.update(validators) + + if type_checker is None: + type_checker = validator.TYPE_CHECKER + elif validator._CREATED_WITH_DEFAULT_TYPES: + raise TypeError( + "Cannot extend a validator created with default_types " + "with a type_checker. Update the validator to use a " + "type_checker when created." + ) + return create( + meta_schema=validator.META_SCHEMA, + validators=all_validators, + version=version, + type_checker=type_checker, + id_of=validator.ID_OF, + ) + + +Draft3Validator = create( + meta_schema=_utils.load_schema("draft3"), + validators={ + u"$ref": _validators.ref, + u"additionalItems": _validators.additionalItems, + u"additionalProperties": _validators.additionalProperties, + u"dependencies": _legacy_validators.dependencies_draft3, + u"disallow": _legacy_validators.disallow_draft3, + u"divisibleBy": _validators.multipleOf, + u"enum": _validators.enum, + u"extends": _legacy_validators.extends_draft3, + u"format": _validators.format, + u"items": _legacy_validators.items_draft3_draft4, + u"maxItems": _validators.maxItems, + u"maxLength": _validators.maxLength, + u"maximum": _legacy_validators.maximum_draft3_draft4, + u"minItems": _validators.minItems, + u"minLength": _validators.minLength, + u"minimum": _legacy_validators.minimum_draft3_draft4, + u"pattern": _validators.pattern, + u"patternProperties": _validators.patternProperties, + u"properties": _legacy_validators.properties_draft3, + u"type": _legacy_validators.type_draft3, + u"uniqueItems": _validators.uniqueItems, + }, + type_checker=_types.draft3_type_checker, + version="draft3", + id_of=lambda schema: schema.get(u"id", ""), +) + +Draft4Validator = create( + meta_schema=_utils.load_schema("draft4"), + validators={ + u"$ref": _validators.ref, + u"additionalItems": _validators.additionalItems, + u"additionalProperties": _validators.additionalProperties, + u"allOf": _validators.allOf, + u"anyOf": _validators.anyOf, + u"dependencies": _validators.dependencies, + u"enum": _validators.enum, + u"format": _validators.format, + u"items": _legacy_validators.items_draft3_draft4, + u"maxItems": _validators.maxItems, + u"maxLength": _validators.maxLength, + u"maxProperties": _validators.maxProperties, + u"maximum": _legacy_validators.maximum_draft3_draft4, + u"minItems": _validators.minItems, + u"minLength": _validators.minLength, + u"minProperties": _validators.minProperties, + u"minimum": _legacy_validators.minimum_draft3_draft4, + u"multipleOf": _validators.multipleOf, + u"not": _validators.not_, + u"oneOf": _validators.oneOf, + u"pattern": _validators.pattern, + u"patternProperties": _validators.patternProperties, + u"properties": _validators.properties, + u"required": _validators.required, + u"type": _validators.type, + u"uniqueItems": _validators.uniqueItems, + }, + type_checker=_types.draft4_type_checker, + version="draft4", + id_of=lambda schema: schema.get(u"id", ""), +) + +Draft6Validator = create( + meta_schema=_utils.load_schema("draft6"), + validators={ + u"$ref": _validators.ref, + u"additionalItems": _validators.additionalItems, + u"additionalProperties": _validators.additionalProperties, + u"allOf": _validators.allOf, + u"anyOf": _validators.anyOf, + u"const": _validators.const, + u"contains": _validators.contains, + u"dependencies": _validators.dependencies, + u"enum": _validators.enum, + u"exclusiveMaximum": _validators.exclusiveMaximum, + u"exclusiveMinimum": _validators.exclusiveMinimum, + u"format": _validators.format, + u"items": _validators.items, + u"maxItems": _validators.maxItems, + u"maxLength": _validators.maxLength, + u"maxProperties": _validators.maxProperties, + u"maximum": _validators.maximum, + u"minItems": _validators.minItems, + u"minLength": _validators.minLength, + u"minProperties": _validators.minProperties, + u"minimum": _validators.minimum, + u"multipleOf": _validators.multipleOf, + u"not": _validators.not_, + u"oneOf": _validators.oneOf, + u"pattern": _validators.pattern, + u"patternProperties": _validators.patternProperties, + u"properties": _validators.properties, + u"propertyNames": _validators.propertyNames, + u"required": _validators.required, + u"type": _validators.type, + u"uniqueItems": _validators.uniqueItems, + }, + type_checker=_types.draft6_type_checker, + version="draft6", +) + +Draft7Validator = create( + meta_schema=_utils.load_schema("draft7"), + validators={ + u"$ref": _validators.ref, + u"additionalItems": _validators.additionalItems, + u"additionalProperties": _validators.additionalProperties, + u"allOf": _validators.allOf, + u"anyOf": _validators.anyOf, + u"const": _validators.const, + u"contains": _validators.contains, + u"dependencies": _validators.dependencies, + u"enum": _validators.enum, + u"exclusiveMaximum": _validators.exclusiveMaximum, + u"exclusiveMinimum": _validators.exclusiveMinimum, + u"format": _validators.format, + u"if": _validators.if_, + u"items": _validators.items, + u"maxItems": _validators.maxItems, + u"maxLength": _validators.maxLength, + u"maxProperties": _validators.maxProperties, + u"maximum": _validators.maximum, + u"minItems": _validators.minItems, + u"minLength": _validators.minLength, + u"minProperties": _validators.minProperties, + u"minimum": _validators.minimum, + u"multipleOf": _validators.multipleOf, + u"oneOf": _validators.oneOf, + u"not": _validators.not_, + u"pattern": _validators.pattern, + u"patternProperties": _validators.patternProperties, + u"properties": _validators.properties, + u"propertyNames": _validators.propertyNames, + u"required": _validators.required, + u"type": _validators.type, + u"uniqueItems": _validators.uniqueItems, + }, + type_checker=_types.draft7_type_checker, + version="draft7", +) + +_LATEST_VERSION = Draft7Validator + + +class RefResolver(object): + """ + Resolve JSON References. + + Arguments: + + base_uri (str): + + The URI of the referring document + + referrer: + + The actual referring document + + store (dict): + + A mapping from URIs to documents to cache + + cache_remote (bool): + + Whether remote refs should be cached after first resolution + + handlers (dict): + + A mapping from URI schemes to functions that should be used + to retrieve them + + urljoin_cache (:func:`functools.lru_cache`): + + A cache that will be used for caching the results of joining + the resolution scope to subscopes. + + remote_cache (:func:`functools.lru_cache`): + + A cache that will be used for caching the results of + resolved remote URLs. + + Attributes: + + cache_remote (bool): + + Whether remote refs should be cached after first resolution + """ + + def __init__( + self, + base_uri, + referrer, + store=(), + cache_remote=True, + handlers=(), + urljoin_cache=None, + remote_cache=None, + ): + if urljoin_cache is None: + urljoin_cache = lru_cache(1024)(urljoin) + if remote_cache is None: + remote_cache = lru_cache(1024)(self.resolve_from_url) + + self.referrer = referrer + self.cache_remote = cache_remote + self.handlers = dict(handlers) + + self._scopes_stack = [base_uri] + self.store = _utils.URIDict( + (id, validator.META_SCHEMA) + for id, validator in iteritems(meta_schemas) ) - } + self.store.update(store) + self.store[base_uri] = referrer + + self._urljoin_cache = urljoin_cache + self._remote_cache = remote_cache @classmethod - def get_validator(cls, project_type: str, internal=False): - idx = 1 if internal else 0 - return cls.VALIDATORS[project_type.lower()][idx] + def from_schema(cls, schema, id_of=_id_of, *args, **kwargs): + """ + Construct a resolver from a JSON schema object. + + Arguments: + + schema: + + the referring schema + + Returns: + + `RefResolver` + """ + + return cls(base_uri=id_of(schema), referrer=schema, *args, **kwargs) + + def push_scope(self, scope): + """ + Enter a given sub-scope. + + Treats further dereferences as being performed underneath the + given scope. + """ + self._scopes_stack.append( + self._urljoin_cache(self.resolution_scope, scope), + ) + + def pop_scope(self): + """ + Exit the most recent entered scope. + + Treats further dereferences as being performed underneath the + original scope. + + Don't call this method more times than `push_scope` has been + called. + """ + try: + self._scopes_stack.pop() + except IndexError: + raise exceptions.RefResolutionError( + "Failed to pop the scope from an empty stack. " + "`pop_scope()` should only be called once for every " + "`push_scope()`" + ) + + @property + def resolution_scope(self): + """ + Retrieve the current resolution scope. + """ + return self._scopes_stack[-1] + + @property + def base_uri(self): + """ + Retrieve the current base URI, not including any fragment. + """ + uri, _ = urldefrag(self.resolution_scope) + return uri + + @contextlib.contextmanager + def in_scope(self, scope): + """ + Temporarily enter the given scope for the duration of the context. + """ + self.push_scope(scope) + try: + yield + finally: + self.pop_scope() + + @contextlib.contextmanager + def resolving(self, ref): + """ + Resolve the given ``ref`` and enter its resolution scope. + + Exits the scope on exit of this context manager. + + Arguments: + + ref (str): + + The reference to resolve + """ + + url, resolved = self.resolve(ref) + self.push_scope(url) + try: + yield resolved + finally: + self.pop_scope() + + def resolve(self, ref): + """ + Resolve the given reference. + """ + url = self._urljoin_cache(self.resolution_scope, ref) + return url, self._remote_cache(url) + + def resolve_from_url(self, url): + """ + Resolve the given remote URL. + """ + url, fragment = urldefrag(url) + try: + document = self.store[url] + except KeyError: + try: + document = self.resolve_remote(url) + except Exception as exc: + raise exceptions.RefResolutionError(exc) + + return self.resolve_fragment(document, fragment) + + def resolve_fragment(self, document, fragment): + """ + Resolve a ``fragment`` within the referenced ``document``. + + Arguments: + + document: + + The referent document + fragment (str): + + a URI fragment to resolve within it + """ + + fragment = fragment.lstrip(u"/") + parts = unquote(fragment).split(u"/") if fragment else [] + + for part in parts: + part = part.replace(u"~1", u"/").replace(u"~0", u"~") + + if isinstance(document, Sequence): + # Array indexes should be turned into integers + try: + part = int(part) + except ValueError: + pass + try: + document = document[part] + except (TypeError, LookupError): + raise exceptions.RefResolutionError( + "Unresolvable JSON pointer: %r" % fragment + ) + + return document + + def resolve_remote(self, uri): + """ + Resolve a remote ``uri``. + + If called directly, does not check the store first, but after + retrieving the document at the specified URI it will be saved in + the store if :attr:`cache_remote` is True. + + .. note:: + + If the requests_ library is present, ``jsonschema`` will use it to + request the remote ``uri``, so that the correct encoding is + detected and used. + + If it isn't, or if the scheme of the ``uri`` is not ``http`` or + ``https``, UTF-8 is assumed. + + Arguments: + + uri (str): + + The URI to resolve + + Returns: + + The retrieved document + + .. _requests: https://pypi.org/project/requests/ + """ + try: + import requests + except ImportError: + requests = None + + scheme = urlsplit(uri).scheme + + if scheme in self.handlers: + result = self.handlers[scheme](uri) + elif scheme in [u"http", u"https"] and requests: + # Requests has support for detecting the correct encoding of + # json over http + result = requests.get(uri).json() + else: + # Otherwise, pass off to urllib and assume utf-8 + with urlopen(uri) as url: + result = json.loads(url.read().decode("utf-8")) + + if self.cache_remote: + self.store[uri] = result + return result + + +def validate(instance, schema, cls=None, *args, **kwargs): + """ + Validate an instance under the given schema. + + >>> validate([2, 3, 4], {"maxItems": 2}) + Traceback (most recent call last): + ... + ValidationError: [2, 3, 4] is too long + + :func:`validate` will first verify that the provided schema is + itself valid, since not doing so can lead to less obvious error + messages and fail in less obvious or consistent ways. + + If you know you have a valid schema already, especially if you + intend to validate multiple instances with the same schema, you + likely would prefer using the `IValidator.validate` method directly + on a specific validator (e.g. ``Draft7Validator.validate``). + + + Arguments: + + instance: + + The instance to validate + + schema: + + The schema to validate with + + cls (IValidator): + + The class that will be used to validate the instance. + + If the ``cls`` argument is not provided, two things will happen + in accordance with the specification. First, if the schema has a + :validator:`$schema` property containing a known meta-schema [#]_ + then the proper validator will be used. The specification recommends + that all schemas contain :validator:`$schema` properties for this + reason. If no :validator:`$schema` property is found, the default + validator class is the latest released draft. + + Any other provided positional and keyword arguments will be passed + on when instantiating the ``cls``. + + Raises: + + `jsonschema.exceptions.ValidationError` if the instance + is invalid + + `jsonschema.exceptions.SchemaError` if the schema itself + is invalid + + .. rubric:: Footnotes + .. [#] known by a validator registered with + `jsonschema.validators.validates` + """ + if cls is None: + cls = validator_for(schema) + + cls.check_schema(schema) + validator = cls(schema, *args, **kwargs) + error = exceptions.best_match(validator.iter_errors(instance)) + if error is not None: + raise error + + +def validator_for(schema, default=_LATEST_VERSION): + """ + Retrieve the validator class appropriate for validating the given schema. + + Uses the :validator:`$schema` property that should be present in the + given schema to look up the appropriate validator class. + + Arguments: + + schema (collections.Mapping or bool): + + the schema to look at + + default: + + the default to return if the appropriate validator class + cannot be determined. + + If unprovided, the default is to return the latest supported + draft. + """ + if schema is True or schema is False or u"$schema" not in schema: + return default + if schema[u"$schema"] not in meta_schemas: + warn( + ( + "The metaschema specified by $schema was not found. " + "Using the latest draft to validate, but this will raise " + "an error in the future." + ), + DeprecationWarning, + stacklevel=2, + ) + return meta_schemas.get(schema[u"$schema"], _LATEST_VERSION) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index ac63d6f..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -import sys -from pathlib import Path - - -LIB_PATH = Path(__file__).parent.parent / "src" / "superannotate_schemas" -sys.path.insert(0, str(LIB_PATH)) diff --git a/tests/data_set/sample_project_vector/classes/classes.json b/tests/data_set/sample_project_vector/classes/classes.json deleted file mode 100644 index 810a5a4..0000000 --- a/tests/data_set/sample_project_vector/classes/classes.json +++ /dev/null @@ -1,157 +0,0 @@ -[ - { - "id": 55917, - "project_id": 11979, - "name": "Personal vehicle", - "color": "#ecb65f", - "count": 25, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:48:19.000Z", - "attribute_groups": [ - { - "id": 17245, - "class_id": 55917, - "name": "Num doors", - "is_multiselect": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z", - "attributes": [ - { - "id": 62792, - "group_id": 17245, - "project_id": 11979, - "name": "2", - "count": 1, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:46:28.000Z" - }, - { - "id": 62793, - "group_id": 17245, - "project_id": 11979, - "name": "4", - "count": 1, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z" - } - ] - } - ] - }, - { - "id": 55918, - "project_id": 11979, - "name": "Large vehicle", - "color": "#737b28", - "count": 1, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:48:19.000Z", - "attribute_groups": [ - { - "id": 17246, - "class_id": 55918, - "name": "swedish", - "is_multiselect": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z", - "attributes": [ - { - "id": 62794, - "group_id": 17246, - "project_id": 11979, - "name": "yes", - "count": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z" - }, - { - "id": 62795, - "group_id": 17246, - "project_id": 11979, - "name": "no", - "count": 1, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:46:28.000Z" - } - ] - }, - { - "id": 17247, - "class_id": 55918, - "name": "Num doors", - "is_multiselect": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z", - "attributes": [ - { - "id": 62796, - "group_id": 17247, - "project_id": 11979, - "name": "2", - "count": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z" - }, - { - "id": 62797, - "group_id": 17247, - "project_id": 11979, - "name": "4", - "count": 1, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:46:28.000Z" - } - ] - } - ] - }, - { - "id": 55919, - "project_id": 11979, - "name": "Human", - "color": "#e4542b", - "count": 9, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:48:14.000Z", - "attribute_groups": [ - { - "id": 17248, - "class_id": 55919, - "name": "Height", - "is_multiselect": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z", - "attributes": [ - { - "id": 62798, - "group_id": 17248, - "project_id": 11979, - "name": "Tall", - "count": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z" - }, - { - "id": 62799, - "group_id": 17248, - "project_id": 11979, - "name": "Short", - "count": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z" - } - ] - } - ] - }, - { - "id": 55920, - "project_id": 11979, - "name": "Plant", - "color": "#46ccb2", - "count": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z", - "attribute_groups": [] - } -] diff --git a/tests/data_set/sample_project_vector/example_image_1.jpg b/tests/data_set/sample_project_vector/example_image_1.jpg deleted file mode 100644 index 4dee935..0000000 Binary files a/tests/data_set/sample_project_vector/example_image_1.jpg and /dev/null differ diff --git a/tests/data_set/sample_project_vector/example_image_1.jpg___fuse.png b/tests/data_set/sample_project_vector/example_image_1.jpg___fuse.png deleted file mode 100644 index 368a043..0000000 Binary files a/tests/data_set/sample_project_vector/example_image_1.jpg___fuse.png and /dev/null differ diff --git a/tests/data_set/sample_project_vector/example_image_1.jpg___objects.json b/tests/data_set/sample_project_vector/example_image_1.jpg___objects.json deleted file mode 100644 index cb893b5..0000000 --- a/tests/data_set/sample_project_vector/example_image_1.jpg___objects.json +++ /dev/null @@ -1,3099 +0,0 @@ -{ - "metadata": { - "name": "example_image_1.jpg", - "width": 1024, - "height": 683, - "status": "Completed", - "pinned": false, - "isPredicted": null, - "projectId": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "type": "bbox", - "classId": 72274, - "probability": 100, - "points": { - "x1": 437.16, - "x2": 465.23, - "y1": 341.5, - "y2": 357.09 - }, - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [ - { - "id": 117845, - "groupId": 28230, - "name": "2", - "groupName": "Num doors" - } - ], - "trackingId": "aaa97f80c9e54a5f2dc2e920fc92e5033d9af45b", - "error": null, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "bbox", - "classId": 72274, - "probability": 100, - "points": { - "x1": 480.0, - "x2": 490.0, - "y1": 340.0, - "y2": 350.0 - }, - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [ - { - "id": 117845, - "groupId": 28230, - "name": "2", - "groupName": "Num doors" - } - ], - "error": null, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle1" - }, - { - "type": "bbox", - "classId": 72274, - "probability": 100, - "points": { - "x1": 500.0, - "x2": 510.0, - "y1": 340.0, - "y2": 350.0 - }, - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [ - { - "id": 117845, - "groupId": 28230, - "name": "10", - "groupName": "Num doors" - } - ], - "error": null, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "bbox", - "classId": 72274, - "probability": 100, - "points": { - "x1": 520.0, - "x2": 530.0, - "y1": 340.0, - "y2": 350.0 - }, - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [ - { - "id": 117845, - "groupId": 28230, - "name": "4", - "groupName": "Num doors1" - } - ], - "error": null, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "template", - "templateName": "some", - "classId": 72274, - "probability": 100, - "points": [ - { - "id": 1, - "x": 800.8311630011381, - "y": 431.7220764160156 - }, - { - "id": 2, - "x": 834.6965942382812, - "y": 431.8820692877566 - }, - { - "id": 3, - "x": 834.6965942382812, - "y": 480.848388671875 - }, - { - "id": 4, - "x": 801.0125574701838, - "y": 480.848388671875 - }, - { - "id": 5, - "x": 702.6083268971072, - "y": 437.5428573337124 - }, - { - "id": 6, - "x": 702.5221557617188, - "y": 474.8859480851478 - } - ], - "connections": [ - { - "id": 1, - "from": 1, - "to": 2 - }, - { - "id": 2, - "from": 2, - "to": 3 - }, - { - "id": 3, - "from": 3, - "to": 4 - }, - { - "id": 4, - "from": 4, - "to": 1 - }, - { - "id": 5, - "from": 1, - "to": 5 - }, - { - "id": 6, - "from": 5, - "to": 6 - }, - { - "id": 7, - "from": 6, - "to": 4 - } - ], - "groupId": 0, - "pointLabels": { - "4": "top_left", - "5": "bottom_left" - }, - "locked": false, - "visible": false, - "attributes": [ - { - "name": "4", - "groupName": "Num doors", - "groupId": 28230, - "id": 117846 - } - ], - "templateName": "HandPose", - "trackingId": "cbde2787e76c41be77c1079e8d090252ad701ea", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 281.98, - 383.75, - 282.55, - 378.1, - 287.26, - 376.12, - 297.35, - 372.91, - 311.01, - 372.82, - 319.59, - 375.74, - 323.55, - 378.28, - 325.91, - 381.68, - 326.66, - 385.45, - 325.43, - 387.62, - 324.02, - 388.75, - 317.23, - 388.84, - 315.54, - 390.26, - 312.43, - 390.54, - 308.66, - 388.46, - 306.39, - 388.84, - 297.44, - 389.03, - 291.5, - 388.18, - 287.64, - 384.51 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "bf069efee9e65463824466f442a409a137eabaee", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 266.9, - 384.88, - 267.47, - 404.21, - 276.23, - 404.87, - 277.65, - 407.32, - 278.78, - 407.79, - 282.17, - 407.79, - 284.15, - 407.32, - 285.19, - 403.92, - 292.73, - 403.83, - 293.29, - 405.43, - 294.99, - 406.37, - 297.53, - 406.28, - 298.57, - 405.43, - 301.12, - 404.39, - 302.15, - 402.41, - 303.38, - 395.53, - 301.49, - 391.39, - 296.12, - 389.03, - 291.78, - 388.84, - 286.79, - 384.13, - 284.9, - 384.51 - ], - "groupId": 1, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "a520dde722112d1579ff65260166d02ac1c14e2", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 262.94, - 385.54, - 263.88, - 404.68, - 262.47, - 404.96, - 262.19, - 406.66, - 261.34, - 408.07, - 259.74, - 408.54, - 256.53, - 408.64, - 255.59, - 408.16, - 254.84, - 407.13, - 254.08, - 403.92, - 252.76, - 402.79, - 250.69, - 402.32, - 249.75, - 401.19, - 250.5, - 389.03, - 254.18, - 384.51, - 262.56, - 384.32 - ], - "groupId": 1, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "97e8bcc305b69af97b1a51c102c22a03887410bd", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 348.62, - 395.91, - 367.76, - 395.34, - 367, - 384.32, - 364.36, - 378, - 349.09, - 377.81, - 346.55, - 385.54, - 346.55, - 395.82 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "15e2bd2a9bf66c4df00df9fbe6fd6db43abc56", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 325.25, - 402.32, - 321.1, - 410.99, - 321, - 424.47, - 329.21, - 424.75, - 329.49, - 423.06, - 344.57, - 423.15, - 344.85, - 424.85, - 349.94, - 424.38, - 349.09, - 409.2, - 344.57, - 401.47 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "98c3f3a3fdeb809c7a8de125447acce21abda84f", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 114.81, - 432.5, - 149.32, - 430.8, - 169.65, - 442.24, - 187.65, - 446.05, - 192.94, - 453.25, - 192.31, - 462.14, - 189.77, - 467.44, - 183.84, - 470.83, - 177.48, - 472.52, - 169.65, - 480.57, - 163.93, - 481.62, - 160.54, - 477.18, - 159.27, - 472.73, - 159.91, - 468.28, - 159.49, - 458.76, - 156.94, - 450.71, - 136.62, - 437.37, - 119.04, - 436.52 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "de78e5e22b397426228bed63c15ad2f41bfe653", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 120.52, - 437.37, - 135.77, - 437.79, - 156.31, - 450.5, - 158.85, - 459.39, - 159.27, - 468.71, - 158.21, - 474.21, - 152.92, - 480.78, - 147.84, - 483.74, - 142.54, - 484.17, - 139.37, - 482.05, - 140.43, - 477.6, - 144.87, - 475.91, - 146.78, - 471.25, - 144.03, - 457.27 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "7adde574ed24f845d700b0c8371122bfe667751f", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 81.46, - 437.16, - 94.38, - 435.04, - 110.9, - 433.56, - 117.67, - 434.83, - 133.77, - 448.8, - 144.99, - 457.27, - 147.32, - 471.67, - 145.62, - 475.91, - 141.6, - 477.6, - 136.31, - 485.22, - 131.65, - 487.98, - 126.78, - 488.61, - 122.97, - 472.73, - 118.52, - 464.26, - 110.9, - 455.37, - 103.06, - 441.18, - 99.89, - 438.64 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "4a47c34904d5e6bafdab32f9896c4013b1ddd153", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 684.8, - 420.93, - 683.1, - 416.3, - 634.11, - 414.48, - 626.68, - 419.72, - 622.9, - 424.35, - 609.62, - 425.69, - 604.63, - 427.76, - 600.73, - 434.34, - 600.48, - 440.19, - 600.97, - 440.92, - 604.02, - 442.01, - 604.99, - 445.67, - 607.18, - 447.99, - 610.96, - 450.18, - 618.64, - 450.91, - 621.2, - 448.72, - 622.54, - 446.16, - 626.8, - 446.16, - 626.92, - 440.67, - 629.6, - 435.31, - 633.75, - 432.39, - 646.79, - 430.32, - 664.09, - 420.81, - 685.05, - 422.4 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "eb882a5e0012c77a1557c47bf386b21b6f6848", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 674.69, - 421.91, - 664.82, - 421.3, - 646.66, - 430.56, - 634.24, - 432.63, - 629.85, - 435.68, - 627.29, - 440.55, - 627.05, - 444.94, - 628.14, - 447.13, - 628.63, - 447.86, - 631.68, - 448.35, - 633.38, - 451.4, - 634.48, - 452.25, - 634.72, - 446.89, - 636.43, - 437.99, - 645.57, - 434.34, - 656.53, - 431.05 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "56b1a09c67ce96a1719afe9f8c2b50b3dd08cd1c", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 729.77, - 442.26, - 729.89, - 436.04, - 726.11, - 425.93, - 719.9, - 423.86, - 676.27, - 422.93, - 670.06, - 424.22, - 656.78, - 431.41, - 641.67, - 435.68, - 636.92, - 438.12, - 635.09, - 447.25, - 634.97, - 452.86, - 635.7, - 453.71, - 640.33, - 455.17, - 643.25, - 457.86, - 649.59, - 458.22, - 652.27, - 457.86, - 654.95, - 454.32, - 656.29, - 453.47, - 664.45, - 453.96, - 667.62, - 458.71, - 668.72, - 458.95, - 671.64, - 458.95, - 673.96, - 458.34, - 676.52, - 456.76, - 678.35, - 454.32, - 686.75, - 454.93, - 689.92, - 459.56, - 691.51, - 460.78, - 696.87, - 461.27, - 699.67, - 460.29, - 702.84, - 456.51, - 705.27, - 455.91, - 706.86, - 452.37, - 708.69, - 450.79, - 722.21, - 445.18, - 725.87, - 445.43 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "12a358abc908ad69e8b599ab359e12ecfe1047", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 96, - "points": [ - 703, - 462.81, - 703, - 464.81, - 712, - 472.81, - 712, - 474.81, - 724, - 474.81, - 729, - 471.81, - 741.61, - 472.86, - 745.32, - 476.75, - 753.29, - 476.57, - 756.25, - 473.97, - 770, - 473.81, - 780, - 478.81, - 784, - 478.81, - 792, - 474.81, - 802, - 474.81, - 806, - 478.81, - 812, - 479.81, - 817, - 477.81, - 820, - 473.81, - 832.61, - 472.49, - 834, - 468.81, - 833, - 453.81, - 827, - 448.81, - 805, - 437.81, - 783, - 434.81, - 750, - 434.81, - 739, - 437.81, - 726, - 445.81, - 722, - 445.81, - 709, - 450.81, - 707, - 452.81, - 705.11, - 457.11 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "8f8ef909a683eaf9852b4d9784ec63b471c58d16", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 98, - "points": [ - 1023.86, - 432.09, - 1019, - 434, - 1008, - 440, - 1001, - 447, - 960, - 450, - 952, - 453, - 945, - 460, - 940, - 472, - 942, - 496, - 945, - 500, - 948, - 500, - 954, - 510, - 958, - 514, - 980, - 515, - 992, - 504, - 999, - 506, - 1006, - 513, - 1009, - 514, - 1016.82, - 516.78, - 1023.86, - 515.86 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "d9975dc56b159b1690fbdea7b04dd35f2ba69366", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 98, - "points": [ - 6, - 447, - 0, - 459, - 0, - 528, - 2, - 531, - 12, - 530, - 20, - 536, - 25, - 536, - 33, - 530, - 61, - 530, - 77, - 528, - 86, - 534, - 94, - 535, - 99, - 532, - 100, - 525, - 102, - 522, - 109.39, - 521.38, - 111.09, - 529.47, - 122.6, - 528.2, - 126.44, - 491.97, - 122, - 474, - 118, - 465, - 110, - 456, - 103, - 442, - 99, - 439, - 47, - 438, - 16, - 442 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "76d42d45e8b0c11a055dff75a405b515bb1dd53f", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "bbox", - "classId": 72275, - "probability": 100, - "points": { - "x1": 240.68, - "x2": 304.61, - "y1": 378.93, - "y2": 410.11 - }, - "groupId": 0, - "pointLabels": { - "0": "Top Left", - "4": "Bottom Right" - }, - "locked": false, - "visible": true, - "attributes": [ - { - "id": 117848, - "groupId": 28231, - "name": "no", - "groupName": "swedish" - }, - { - "id": 117850, - "groupId": 28232, - "name": "4", - "groupName": "Num doors" - } - ], - "trackingId": "ac43151b5ac2d511beac8d2ec15695f421b93882", - "error": null, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - }, - { - "type": "template", - "templateName": "some", - "classId": 72276, - "probability": 100, - "points": [ - { - "id": 1, - "x": 590.36328125, - "y": 505.471431864795 - }, - { - "id": 2, - "x": 590.2529541686341, - "y": 504.29565523299704 - }, - { - "id": 3, - "x": 590.0863828554258, - "y": 502.0855402722193 - }, - { - "id": 4, - "x": 589.8926669948704, - "y": 500.1575188822054 - }, - { - "id": 5, - "x": 588.2789742606027, - "y": 491.4069519042969 - }, - { - "id": 6, - "x": 591.6578771570227, - "y": 498.7841862403542 - }, - { - "id": 7, - "x": 592.6675015963041, - "y": 497.5725781649412 - }, - { - "id": 8, - "x": 593.4538138253348, - "y": 495.05589353721325 - }, - { - "id": 9, - "x": 591.9352490770948, - "y": 502.2054028345276 - }, - { - "id": 10, - "x": 591.4315175486134, - "y": 504.8054433249257 - }, - { - "id": 11, - "x": 591.0675032060225, - "y": 506.48433274969244 - }, - { - "id": 12, - "x": 593.6178112658826, - "y": 501.4214392039917 - }, - { - "id": 13, - "x": 592.6682424021291, - "y": 504.65690054240156 - }, - { - "id": 14, - "x": 591.8309557568896, - "y": 507.1707458496094 - }, - { - "id": 15, - "x": 594.685306758671, - "y": 499.50420568423283 - }, - { - "id": 16, - "x": 594.4346668956044, - "y": 503.3523914672602 - }, - { - "id": 17, - "x": 593.4855715573489, - "y": 505.4433191217528 - }, - { - "id": 18, - "x": 592.9555204622038, - "y": 507.0652772868338 - }, - { - "id": 19, - "x": 589.5701713142814, - "y": 496.6512277677259 - }, - { - "id": 20, - "x": 590.8887191604782, - "y": 499.291411604618 - }, - { - "id": 21, - "x": 591.1992693890583, - "y": 501.8345208353304 - }, - { - "id": 22, - "x": 591.0341186523438, - "y": 501.9896778816582 - } - ], - "connections": [ - { - "id": 1, - "from": 5, - "to": 4 - }, - { - "id": 2, - "from": 3, - "to": 4 - }, - { - "id": 3, - "from": 3, - "to": 2 - }, - { - "id": 4, - "from": 2, - "to": 1 - }, - { - "id": 5, - "from": 5, - "to": 6 - }, - { - "id": 6, - "from": 6, - "to": 9 - }, - { - "id": 7, - "from": 9, - "to": 10 - }, - { - "id": 8, - "from": 10, - "to": 11 - }, - { - "id": 9, - "from": 5, - "to": 7 - }, - { - "id": 10, - "from": 7, - "to": 12 - }, - { - "id": 11, - "from": 12, - "to": 13 - }, - { - "id": 12, - "from": 13, - "to": 14 - }, - { - "id": 13, - "from": 5, - "to": 8 - }, - { - "id": 14, - "from": 8, - "to": 15 - }, - { - "id": 15, - "from": 15, - "to": 16 - }, - { - "id": 16, - "from": 16, - "to": 17 - }, - { - "id": 17, - "from": 17, - "to": 18 - }, - { - "id": 18, - "from": 5, - "to": 19 - }, - { - "id": 19, - "from": 19, - "to": 20 - }, - { - "id": 20, - "from": 20, - "to": 21 - }, - { - "id": 21, - "from": 21, - "to": 22 - } - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "templateName": "HandPose", - "trackingId": "2c89e809614523cf56c9aeab932e90b87aaf5e4f", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "template", - "templateName": "some", - "classId": 72276, - "probability": 100, - "points": [ - { - "id": 1, - "x": 332.9866027832032, - "y": 526.2959883676228 - }, - { - "id": 2, - "x": 332.8439004919032, - "y": 527.5132367654812 - }, - { - "id": 3, - "x": 334.35612353649776, - "y": 527.3324179308058 - }, - { - "id": 4, - "x": 336.2640990372543, - "y": 524.0976645502819 - }, - { - "id": 5, - "x": 337.51601736886164, - "y": 516.1050720214844 - }, - { - "id": 6, - "x": 339.060296362573, - "y": 524.7754271337591 - }, - { - "id": 7, - "x": 341.64884537916925, - "y": 526.5125154522543 - }, - { - "id": 8, - "x": 344.0771833147321, - "y": 527.3880219566797 - }, - { - "id": 9, - "x": 335.88342117477254, - "y": 527.9910814406194 - }, - { - "id": 10, - "x": 334.6968087835627, - "y": 529.0659044885928 - }, - { - "id": 11, - "x": 333.86405081277377, - "y": 527.8757251825314 - }, - { - "id": 12, - "x": 339.9883503337483, - "y": 529.320022177355 - }, - { - "id": 13, - "x": 338.46802612975404, - "y": 530.370269900207 - }, - { - "id": 14, - "x": 337.1430909712236, - "y": 530.7341613769531 - }, - { - "id": 15, - "x": 341.9785882300073, - "y": 531.0127476105173 - }, - { - "id": 16, - "x": 340.85258785708925, - "y": 532.1869901255352 - }, - { - "id": 17, - "x": 339.1688606346047, - "y": 532.8862634202454 - }, - { - "id": 18, - "x": 339.0958418793731, - "y": 532.8511886128618 - }, - { - "id": 19, - "x": 342.74045026171336, - "y": 523.5337313474565 - }, - { - "id": 20, - "x": 343.0975823874003, - "y": 525.8059083903495 - }, - { - "id": 21, - "x": 341.95265642103254, - "y": 527.6336142573132 - }, - { - "id": 22, - "x": 340.4774169921875, - "y": 527.7661633949826 - } - ], - "connections": [ - { - "id": 1, - "from": 5, - "to": 4 - }, - { - "id": 2, - "from": 3, - "to": 4 - }, - { - "id": 3, - "from": 3, - "to": 2 - }, - { - "id": 4, - "from": 2, - "to": 1 - }, - { - "id": 5, - "from": 5, - "to": 6 - }, - { - "id": 6, - "from": 6, - "to": 9 - }, - { - "id": 7, - "from": 9, - "to": 10 - }, - { - "id": 8, - "from": 10, - "to": 11 - }, - { - "id": 9, - "from": 5, - "to": 7 - }, - { - "id": 10, - "from": 7, - "to": 12 - }, - { - "id": 11, - "from": 12, - "to": 13 - }, - { - "id": 12, - "from": 13, - "to": 14 - }, - { - "id": 13, - "from": 5, - "to": 8 - }, - { - "id": 14, - "from": 8, - "to": 15 - }, - { - "id": 15, - "from": 15, - "to": 16 - }, - { - "id": 16, - "from": 16, - "to": 17 - }, - { - "id": 17, - "from": 17, - "to": 18 - }, - { - "id": 18, - "from": 5, - "to": 19 - }, - { - "id": 19, - "from": 19, - "to": 20 - }, - { - "id": 20, - "from": 20, - "to": 21 - }, - { - "id": 21, - "from": 21, - "to": 22 - } - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "templateName": "HandPose", - "trackingId": "bab62dc810b0cee390f8d5fb5fa62fade3c8da7", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "template", - "templateName": "some", - "classId": 72276, - "probability": 100, - "points": [ - { - "id": 1, - "x": 500.7473449707031, - "y": 512.2212813363728 - }, - { - "id": 2, - "x": 499.83990268916875, - "y": 511.0267255350125 - }, - { - "id": 3, - "x": 499.35212573376333, - "y": 508.78712984486833 - }, - { - "id": 4, - "x": 499.49539176186363, - "y": 505.6112143549695 - }, - { - "id": 5, - "x": 505.1166338239397, - "y": 498.2973327636719 - }, - { - "id": 6, - "x": 501.5269101321042, - "y": 506.7595579931341 - }, - { - "id": 7, - "x": 503.99778336745044, - "y": 506.673098948348 - }, - { - "id": 8, - "x": 506.9555402483259, - "y": 505.9015717613673 - }, - { - "id": 9, - "x": 501.35003494430373, - "y": 510.62224599140063 - }, - { - "id": 10, - "x": 501.986939398797, - "y": 512.5206164026553 - }, - { - "id": 11, - "x": 503.15418142800803, - "y": 512.9774707880001 - }, - { - "id": 12, - "x": 503.6314472575764, - "y": 510.3629298921987 - }, - { - "id": 13, - "x": 503.9346398992853, - "y": 513.4720155056757 - }, - { - "id": 14, - "x": 506.3155763227861, - "y": 514.4830017089844 - }, - { - "id": 15, - "x": 506.32755673586666, - "y": 510.11449321598604 - }, - { - "id": 16, - "x": 506.78978268130794, - "y": 513.0534452036602 - }, - { - "id": 17, - "x": 508.6354744041359, - "y": 513.6350427171204 - }, - { - "id": 18, - "x": 508.56245564890435, - "y": 512.0705489644243 - }, - { - "id": 19, - "x": 509.736452458979, - "y": 503.5178622068315 - }, - { - "id": 20, - "x": 510.1524224752909, - "y": 508.84887714034943 - }, - { - "id": 21, - "x": 509.8898512452513, - "y": 511.676521972157 - }, - { - "id": 22, - "x": 509.7675476074219, - "y": 511.8091321449826 - } - ], - "connections": [ - { - "id": 1, - "from": 5, - "to": 4 - }, - { - "id": 2, - "from": 3, - "to": 4 - }, - { - "id": 3, - "from": 3, - "to": 2 - }, - { - "id": 4, - "from": 2, - "to": 1 - }, - { - "id": 5, - "from": 5, - "to": 6 - }, - { - "id": 6, - "from": 6, - "to": 9 - }, - { - "id": 7, - "from": 9, - "to": 10 - }, - { - "id": 8, - "from": 10, - "to": 11 - }, - { - "id": 9, - "from": 5, - "to": 7 - }, - { - "id": 10, - "from": 7, - "to": 12 - }, - { - "id": 11, - "from": 12, - "to": 13 - }, - { - "id": 12, - "from": 13, - "to": 14 - }, - { - "id": 13, - "from": 5, - "to": 8 - }, - { - "id": 14, - "from": 8, - "to": 15 - }, - { - "id": 15, - "from": 15, - "to": 16 - }, - { - "id": 16, - "from": 16, - "to": 17 - }, - { - "id": 17, - "from": 17, - "to": 18 - }, - { - "id": 18, - "from": 5, - "to": 19 - }, - { - "id": 19, - "from": 19, - "to": 20 - }, - { - "id": 20, - "from": 20, - "to": 21 - }, - { - "id": 21, - "from": 21, - "to": 22 - } - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "templateName": "HandPose", - "trackingId": "f8f542a9e9da918d5d5cb8eed9052713302089", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "template", - "templateName": "some", - "classId": 72276, - "probability": 100, - "points": [ - { - "id": 1, - "x": 460.2714192848242, - "y": 486.08071083487926 - }, - { - "id": 2, - "x": 454.92882596998356, - "y": 481.9066804669699 - }, - { - "id": 3, - "x": 461.0707178220127, - "y": 481.61528130084 - }, - { - "id": 4, - "x": 462.32680898178, - "y": 482.46856689453125 - }, - { - "id": 5, - "x": 444.8684189242054, - "y": 483.808782080494 - }, - { - "id": 6, - "x": 455.8683091235324, - "y": 497.2664014146353 - }, - { - "id": 7, - "x": 439.86159351357213, - "y": 498.91779556832523 - }, - { - "id": 8, - "x": 432.98627658437374, - "y": 519.4614616257791 - }, - { - "id": 9, - "x": 415.8799309258186, - "y": 515.9119205914317 - }, - { - "id": 10, - "x": 467.5532979208077, - "y": 499.0862192385027 - }, - { - "id": 11, - "x": 479.28433580441475, - "y": 514.1935318132136 - }, - { - "id": 12, - "x": 498.51239013671875, - "y": 512.030284394326 - }, - { - "id": 13, - "x": 454.8632612058889, - "y": 546.5478157765722 - }, - { - "id": 14, - "x": 444.0484270284733, - "y": 546.0017547475499 - }, - { - "id": 15, - "x": 464.16791732413037, - "y": 546.2800095783913 - }, - { - "id": 16, - "x": 468.63255127661785, - "y": 573.6905686937465 - }, - { - "id": 17, - "x": 457.1555372435924, - "y": 577.0907707675425 - }, - { - "id": 18, - "x": 432.2792663574219, - "y": 587.0443088500142 - }, - { - "id": 19, - "x": 429.91821938954894, - "y": 606.0040783618011 - }, - { - "id": 20, - "x": 463.69909188680566, - "y": 602.9990721708784 - }, - { - "id": 21, - "x": 484.317011118421, - "y": 607.0152893066406 - } - ], - "connections": [ - { - "id": 1, - "from": 1, - "to": 6 - }, - { - "id": 2, - "from": 6, - "to": 10 - }, - { - "id": 3, - "from": 10, - "to": 11 - }, - { - "id": 4, - "from": 11, - "to": 12 - }, - { - "id": 5, - "from": 7, - "to": 8 - }, - { - "id": 6, - "from": 8, - "to": 9 - }, - { - "id": 7, - "from": 14, - "to": 7 - }, - { - "id": 8, - "from": 14, - "to": 13 - }, - { - "id": 9, - "from": 13, - "to": 15 - }, - { - "id": 10, - "from": 15, - "to": 10 - }, - { - "id": 11, - "from": 7, - "to": 6 - }, - { - "id": 12, - "from": 14, - "to": 16 - }, - { - "id": 13, - "from": 15, - "to": 17 - }, - { - "id": 14, - "from": 16, - "to": 20 - }, - { - "id": 15, - "from": 20, - "to": 21 - }, - { - "id": 16, - "from": 17, - "to": 18 - }, - { - "id": 17, - "from": 18, - "to": 19 - }, - { - "id": 18, - "from": 5, - "to": 2 - }, - { - "id": 19, - "from": 2, - "to": 1 - }, - { - "id": 20, - "from": 1, - "to": 1 - }, - { - "id": 21, - "from": 3, - "to": 1 - }, - { - "id": 22, - "from": 3, - "to": 4 - } - ], - "groupId": 0, - "pointLabels": { - "0": "Nose" - }, - "locked": false, - "visible": false, - "attributes": [], - "templateName": "HandPose", - "trackingId": "4fd95b7d6d95b7b84750e65aa89c70b9c86eb3b8", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "template", - "templateName": "some", - "classId": 72276, - "probability": 100, - "points": [ - { - "id": 1, - "x": 569.4099335784475, - "y": 411.3099511426366 - }, - { - "id": 2, - "x": 565.2798621579027, - "y": 406.3627038525488 - }, - { - "id": 3, - "x": 567.377754831435, - "y": 405.3775634765625 - }, - { - "id": 4, - "x": 562.1341137290701, - "y": 404.67809199715805 - }, - { - "id": 5, - "x": 554.7715578497942, - "y": 408.0821593507321 - }, - { - "id": 6, - "x": 543.3504267346603, - "y": 422.3509408794715 - }, - { - "id": 7, - "x": 530.5325718803996, - "y": 432.4575436529285 - }, - { - "id": 8, - "x": 513.1264329109782, - "y": 468.5712030528786 - }, - { - "id": 9, - "x": 505.0783099316068, - "y": 498.26488325838557 - }, - { - "id": 10, - "x": 564.5019009957019, - "y": 431.59166109918834 - }, - { - "id": 11, - "x": 572.9879904477306, - "y": 466.0899617391194 - }, - { - "id": 12, - "x": 588.320701407949, - "y": 491.39197319472385 - }, - { - "id": 13, - "x": 547.1874731524312, - "y": 499.0241945917735 - }, - { - "id": 14, - "x": 536.2172232162276, - "y": 499.38451563669537 - }, - { - "id": 15, - "x": 558.2200212079587, - "y": 496.61095606638287 - }, - { - "id": 16, - "x": 565.8375729727319, - "y": 546.3956734358432 - }, - { - "id": 17, - "x": 545.4810409910515, - "y": 549.0779244124057 - }, - { - "id": 18, - "x": 502.6168107549702, - "y": 573.1785073042392 - }, - { - "id": 19, - "x": 506.98697907641065, - "y": 599.8044128417969 - }, - { - "id": 20, - "x": 555.6301612734296, - "y": 594.6135561518564 - }, - { - "id": 21, - "x": 585.93212890625, - "y": 602.2106018066406 - } - ], - "connections": [ - { - "id": 1, - "from": 1, - "to": 6 - }, - { - "id": 2, - "from": 6, - "to": 10 - }, - { - "id": 3, - "from": 10, - "to": 11 - }, - { - "id": 4, - "from": 11, - "to": 12 - }, - { - "id": 5, - "from": 7, - "to": 8 - }, - { - "id": 6, - "from": 8, - "to": 9 - }, - { - "id": 7, - "from": 14, - "to": 7 - }, - { - "id": 8, - "from": 14, - "to": 13 - }, - { - "id": 9, - "from": 13, - "to": 15 - }, - { - "id": 10, - "from": 15, - "to": 10 - }, - { - "id": 11, - "from": 7, - "to": 6 - }, - { - "id": 12, - "from": 14, - "to": 16 - }, - { - "id": 13, - "from": 15, - "to": 17 - }, - { - "id": 14, - "from": 16, - "to": 20 - }, - { - "id": 15, - "from": 20, - "to": 21 - }, - { - "id": 16, - "from": 17, - "to": 18 - }, - { - "id": 17, - "from": 18, - "to": 19 - }, - { - "id": 18, - "from": 5, - "to": 2 - }, - { - "id": 19, - "from": 2, - "to": 1 - }, - { - "id": 20, - "from": 1, - "to": 1 - }, - { - "id": 21, - "from": 3, - "to": 1 - }, - { - "id": 22, - "from": 3, - "to": 4 - } - ], - "groupId": 0, - "pointLabels": { - "0": "Nose" - }, - "locked": false, - "visible": false, - "attributes": [], - "templateName": "HandPose", - "trackingId": "8894b2a1727f62631d26e885a5aaf9bc2ac2a578", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "template", - "templateName": "some", - "classId": 72276, - "probability": 100, - "points": [ - { - "id": 1, - "x": 388.9594774956746, - "y": 424.3453820508397 - }, - { - "id": 2, - "x": 383.78257983006284, - "y": 420.2971520947363 - }, - { - "id": 3, - "x": 387.1454388819895, - "y": 419.5367736816406 - }, - { - "id": 4, - "x": 382.7214935156717, - "y": 418.8373022022362 - }, - { - "id": 5, - "x": 369.81775320578504, - "y": 421.3423522218259 - }, - { - "id": 6, - "x": 368.5353785473912, - "y": 441.4006845318153 - }, - { - "id": 7, - "x": 353.1593986570741, - "y": 443.28386811581913 - }, - { - "id": 8, - "x": 340.9145244608405, - "y": 484.88446599233174 - }, - { - "id": 9, - "x": 337.471170384727, - "y": 516.0647184634637 - }, - { - "id": 10, - "x": 380.0734310110131, - "y": 441.19236910700084 - }, - { - "id": 11, - "x": 392.6590966976267, - "y": 481.59771320396317 - }, - { - "id": 12, - "x": 411.22125244140625, - "y": 510.38843315566135 - }, - { - "id": 13, - "x": 368.27931488725477, - "y": 514.5319460566172 - }, - { - "id": 14, - "x": 361.465192188568, - "y": 515.6977785761485 - }, - { - "id": 15, - "x": 378.7043428557912, - "y": 512.1187075312266 - }, - { - "id": 16, - "x": 393.26020935016874, - "y": 556.5333687483432 - }, - { - "id": 17, - "x": 344.09536524138383, - "y": 562.7657295881869 - }, - { - "id": 18, - "x": 321.86363692684523, - "y": 598.4685463667392 - }, - { - "id": 19, - "x": 345.55514438756916, - "y": 610.3072814941406 - }, - { - "id": 20, - "x": 402.05302902711884, - "y": 603.0690004877939 - }, - { - "id": 21, - "x": 426.8170225465453, - "y": 607.0261535644531 - } - ], - "connections": [ - { - "id": 1, - "from": 1, - "to": 6 - }, - { - "id": 2, - "from": 6, - "to": 10 - }, - { - "id": 3, - "from": 10, - "to": 11 - }, - { - "id": 4, - "from": 11, - "to": 12 - }, - { - "id": 5, - "from": 7, - "to": 8 - }, - { - "id": 6, - "from": 8, - "to": 9 - }, - { - "id": 7, - "from": 14, - "to": 7 - }, - { - "id": 8, - "from": 14, - "to": 13 - }, - { - "id": 9, - "from": 13, - "to": 15 - }, - { - "id": 10, - "from": 15, - "to": 10 - }, - { - "id": 11, - "from": 7, - "to": 6 - }, - { - "id": 12, - "from": 14, - "to": 16 - }, - { - "id": 13, - "from": 15, - "to": 17 - }, - { - "id": 14, - "from": 16, - "to": 20 - }, - { - "id": 15, - "from": 20, - "to": 21 - }, - { - "id": 16, - "from": 17, - "to": 18 - }, - { - "id": 17, - "from": 18, - "to": 19 - }, - { - "id": 18, - "from": 5, - "to": 2 - }, - { - "id": 19, - "from": 2, - "to": 1 - }, - { - "id": 20, - "from": 1, - "to": 1 - }, - { - "id": 21, - "from": 3, - "to": 1 - }, - { - "id": 22, - "from": 3, - "to": 4 - } - ], - "groupId": 0, - "pointLabels": { - "0": "Nose" - }, - "locked": false, - "visible": false, - "attributes": [], - "templateName": "HandPose", - "trackingId": "2fe1f0c6c4af879955d6f19cfcf113a6b929b73", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "polygon", - "classId": 72276, - "probability": 100, - "points": [ - 496.93, - 506.95, - 500.11, - 499.6, - 499.38, - 494.21, - 500.85, - 490.53, - 502.81, - 490.04, - 503.79, - 488.32, - 505.02, - 480.97, - 507.22, - 477.3, - 510.16, - 466.51, - 520.21, - 451.32, - 522.42, - 446.41, - 524.38, - 435.63, - 541.78, - 412.84, - 543, - 408.92, - 541.78, - 405.73, - 541.78, - 398.13, - 542.51, - 394.95, - 543.74, - 392.74, - 546.19, - 389.8, - 548.4, - 388.82, - 556.97, - 388.82, - 563.35, - 391.27, - 565.06, - 393.23, - 566.29, - 396.42, - 567.76, - 405.24, - 569.23, - 409.41, - 569.23, - 412.59, - 568.25, - 414.55, - 568, - 419.45, - 565.8, - 422.4, - 562.37, - 423.62, - 561.63, - 425.09, - 561.63, - 427.05, - 566.04, - 429.5, - 568, - 433.42, - 569.72, - 445.68, - 594.96, - 498.62, - 594.96, - 502.78, - 593.98, - 505.48, - 591.53, - 508.18, - 589.82, - 508.42, - 588.35, - 505.97, - 586.88, - 500.58, - 585.4, - 499.6, - 582.46, - 499.35, - 568.98, - 481.71, - 571.19, - 508.18, - 569.96, - 510.63, - 567.76, - 510.87, - 572.66, - 595.43, - 574.87, - 597.63, - 580.01, - 598.61, - 586.39, - 598.61, - 588.84, - 599.35, - 589.33, - 601.31, - 587.86, - 604.01, - 586.88, - 604.5, - 553.3, - 604.99, - 551.09, - 601.8, - 551.09, - 592.49, - 552.81, - 589.55, - 548.15, - 554.25, - 530.51, - 572.39, - 511.88, - 586.85, - 509.67, - 587.09, - 508.69, - 593.22, - 508.69, - 596.9, - 509.92, - 599.84, - 509.67, - 601.8, - 506.49, - 602.04, - 502.57, - 598.86, - 499.87, - 594.45, - 496.93, - 584.64, - 492.52, - 581.21, - 489.58, - 576.56, - 489.82, - 571.41, - 491.05, - 570.18, - 498.15, - 569.45, - 509.67, - 565.04, - 525.11, - 547.64, - 532.22, - 546.16, - 531.98, - 541.26, - 537.12, - 538.57, - 530.51, - 510.14, - 526.34, - 513.32, - 522.42, - 489.55, - 521.19, - 477.05, - 517.76, - 485.38, - 515.31, - 489.06, - 514.57, - 493.72, - 512.61, - 495.68, - 511.39, - 498.86, - 509.43, - 506.71, - 508.94, - 514.55, - 505.51, - 515.28, - 501.83, - 514.55, - 498.15, - 510.87, - 497.91, - 507.93 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "bf7e1885326a2aac18619c30d310c21e8fb89e93", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "polygon", - "classId": 72276, - "probability": 100, - "points": [ - 418.36, - 510.31, - 423.02, - 513.25, - 429.15, - 514.48, - 433.07, - 513.25, - 441.65, - 499.04, - 448.76, - 495.36, - 449.74, - 490.7, - 446.55, - 487.52, - 444.83, - 484.33, - 446.06, - 471.59, - 448.27, - 468.89, - 453.66, - 467.18, - 459.29, - 468.16, - 464.69, - 470.61, - 465.42, - 471.59, - 466.16, - 483.6, - 464.69, - 488.25, - 464.69, - 493.4, - 467.87, - 497.57, - 482.58, - 507.37, - 486.5, - 509.33, - 500.96, - 509.09, - 500.22, - 516.93, - 499.24, - 519.13, - 481.11, - 520.61, - 475.47, - 517.42, - 472.28, - 517.17, - 471.55, - 518.4, - 470.08, - 544.62, - 470.81, - 557.12, - 474.49, - 576, - 473.02, - 599.52, - 482.09, - 602.46, - 488.21, - 605.65, - 488.46, - 608.35, - 487.97, - 609.08, - 464.2, - 610.06, - 463.46, - 603.44, - 461.74, - 600.26, - 461.74, - 597.56, - 463.95, - 595.11, - 463.22, - 591.68, - 463.95, - 580.9, - 452.92, - 587.51, - 442.87, - 590.21, - 443.85, - 591.93, - 443.36, - 592.66, - 441.89, - 591.93, - 439.93, - 592.42, - 439.2, - 593.4, - 438.95, - 597.07, - 435.52, - 601.48, - 434.3, - 608.35, - 433.07, - 609.57, - 431.35, - 603.44, - 429.64, - 602.95, - 427.92, - 584.33, - 437.48, - 582.61, - 456.35, - 572.81, - 454.88, - 567.17, - 453.17, - 563.74, - 453.41, - 559.82, - 450.96, - 556.63, - 447.53, - 554.43, - 445.81, - 551.24, - 442.14, - 550.02, - 438.95, - 522.81, - 423.27, - 523.79, - 417.63, - 521.83, - 413.95, - 516.93, - 413.71, - 515.21 - ], - "groupId": 0, - "pointLabels": { - "0": "Left Hand", - "21": "Right Hand" - }, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "932bda5b59db89dd68602306c70d8da62e0afdc", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "polygon", - "classId": 72276, - "probability": 100, - "points": [ - 380.35, - 435.63, - 378.64, - 439.31, - 395.79, - 464.55, - 396.28, - 478.03, - 394.57, - 481.22, - 407.56, - 499.11, - 408.05, - 501.07, - 410.74, - 502.05, - 410.99, - 504.99, - 415.15, - 507.93, - 415.15, - 509.4, - 410.25, - 513.32, - 407.8, - 517, - 399.22, - 516.75, - 390.4, - 510.87, - 389.18, - 512.34, - 397.51, - 539.06, - 397.75, - 559.89, - 400.2, - 568.47, - 409.76, - 593.96, - 417.12, - 602.78, - 422.51, - 604.25, - 428.63, - 603.76, - 429.61, - 606.21, - 428.63, - 608.42, - 402.65, - 614.3, - 396.53, - 611.85, - 395.79, - 609.4, - 397.51, - 602.04, - 395.55, - 599.35, - 394.57, - 599.35, - 383.29, - 574.84, - 380.6, - 555.97, - 369.32, - 542, - 350.45, - 561.61, - 334.03, - 598.86, - 335.01, - 600.82, - 340.65, - 606.21, - 343.34, - 607.44, - 348.49, - 607.93, - 349.47, - 608.66, - 349.72, - 610.62, - 348.25, - 612.09, - 346.78, - 612.58, - 319.82, - 610.62, - 315.89, - 608.17, - 318.1, - 599.84, - 319.08, - 590.77, - 329.13, - 566.02, - 339.42, - 549.11, - 342.61, - 541.51, - 341.38, - 529.74, - 339.18, - 533.91, - 333.79, - 524.6, - 333.3, - 521.9, - 325.94, - 519.45, - 339.42, - 477.54, - 339.18, - 467.98, - 336.48, - 463.82, - 359.52, - 408.92, - 366.38, - 404.5, - 379.62, - 404.5, - 380.84, - 404.99, - 385.5, - 411.12, - 387.7, - 416.27, - 387.7, - 420.68, - 389.42, - 424.6, - 388.44, - 428.03, - 386.97, - 429.75, - 386.23, - 434.65 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "b4a35bf808984de199195734dfbb73b1cb5c8b5", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - } - ], - "tags": [], - "comments": [ - { - "x": 621.41, - "y": 631.6, - "resolved": true, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "correspondence": [ - { - "text": "Bordyuri mi mas@ petqa lini parking class-i mej myus mas@ Terrian class-i", - "email": "hovnatan@superannotate.com" - } - ] - }, - { - "x": 521.41, - "y": 531.6, - "resolved": false, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "correspondence": [ - { - "text": "dd", - "email": "hovnatan@superannotate.com" - } - ] - } - ] -} diff --git a/tests/data_set/sample_project_vector/example_image_2.jpg b/tests/data_set/sample_project_vector/example_image_2.jpg deleted file mode 100644 index 3ed3019..0000000 Binary files a/tests/data_set/sample_project_vector/example_image_2.jpg and /dev/null differ diff --git a/tests/data_set/sample_project_vector/example_image_2.jpg___fuse.png b/tests/data_set/sample_project_vector/example_image_2.jpg___fuse.png deleted file mode 100644 index d54f3bd..0000000 Binary files a/tests/data_set/sample_project_vector/example_image_2.jpg___fuse.png and /dev/null differ diff --git a/tests/data_set/sample_project_vector/example_image_2.jpg___objects.json b/tests/data_set/sample_project_vector/example_image_2.jpg___objects.json deleted file mode 100644 index 1444988..0000000 --- a/tests/data_set/sample_project_vector/example_image_2.jpg___objects.json +++ /dev/null @@ -1,1998 +0,0 @@ -{ - "metadata": { - "name": "example_image_2.jpg", - "width": 1885, - "height": 1060, - "status": "Completed", - "pinned": false, - "isPredicted": null, - "projectId": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "type": "polygon", - "classId": 72274, - "probability": 95, - "points": [ - 1741, - 722, - 1741, - 726, - 1744, - 729, - 1741, - 733, - 1742, - 769, - 1749, - 768, - 1752, - 763, - 1757, - 766, - 1759, - 779, - 1764, - 779, - 1770, - 772, - 1823, - 772, - 1826, - 774, - 1828, - 780, - 1834, - 780, - 1836, - 778, - 1836, - 744, - 1826, - 719, - 1817, - 709, - 1803, - 707, - 1763, - 707, - 1757, - 709, - 1748, - 719 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "9ee0c81d360b5aaeeb083b65f7863d52be7d908", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 96, - "points": [ - 1546, - 1048, - 1542, - 1045, - 1531, - 1043, - 1527, - 1039, - 1526, - 1034, - 1522, - 1033, - 1519, - 1028, - 1513, - 1025, - 1500, - 1022, - 1470, - 1022, - 1459, - 1024, - 1435, - 1024, - 1405, - 1028, - 1400, - 1030, - 1399, - 1033, - 1394, - 1037, - 1393, - 1041, - 1386, - 1047, - 1374, - 1047, - 1368, - 1050, - 1369, - 1055, - 1371, - 1057, - 1382, - 1059, - 1513, - 1059, - 1543, - 1057, - 1546, - 1055 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "fe751485fd9a28b9a39624c5110606f8a13eb497", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 96, - "points": [ - 1370, - 798, - 1367, - 796, - 1361, - 796, - 1349, - 781, - 1341, - 778, - 1304, - 779, - 1282, - 782, - 1277, - 790, - 1276, - 795, - 1273, - 798, - 1264, - 801, - 1266, - 811, - 1264, - 817, - 1265, - 859, - 1267, - 861, - 1272, - 861, - 1276, - 864, - 1277, - 878, - 1285, - 878, - 1288, - 869, - 1292, - 864, - 1318, - 866, - 1344, - 866, - 1353, - 864, - 1359, - 868, - 1361, - 876, - 1364, - 878, - 1370, - 878, - 1372, - 874, - 1375, - 841, - 1371, - 826, - 1371, - 817, - 1364, - 809, - 1364, - 807, - 1371, - 801 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "d4b8b9ee86ab57be7c5afa067f5fa9cb8cc62f83", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 97, - "points": [ - 1563, - 887, - 1563, - 889, - 1569, - 896, - 1569, - 900, - 1566, - 905, - 1567, - 939, - 1568, - 942, - 1573, - 946, - 1579, - 946, - 1581, - 948, - 1582, - 958, - 1589, - 968, - 1594, - 969, - 1597, - 967, - 1603, - 958, - 1613, - 957, - 1677, - 958, - 1687, - 968, - 1690, - 969, - 1694, - 968, - 1698, - 964, - 1699, - 927, - 1694, - 918, - 1693, - 906, - 1689, - 900, - 1690, - 891, - 1680, - 880, - 1679, - 873, - 1673, - 868, - 1650, - 865, - 1596, - 866, - 1588, - 868, - 1582, - 874, - 1581, - 879, - 1578, - 882, - 1569, - 883 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "68ef609bf39454907c1d87b7aabce86739a06b2c", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 98, - "points": [ - 1685, - 835, - 1685, - 874, - 1687, - 877, - 1700, - 879, - 1703, - 882, - 1704, - 890, - 1709, - 894, - 1714, - 895, - 1719, - 891, - 1722, - 886, - 1792, - 886, - 1795, - 888, - 1796, - 893, - 1799, - 895, - 1808, - 895, - 1811, - 892, - 1812, - 848, - 1803, - 839, - 1798, - 827, - 1783, - 813, - 1771, - 810, - 1718, - 811, - 1710, - 815, - 1701, - 824, - 1689, - 829 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "125e64e93ac3c9f1c0d6b74a90529a2a59d587d5", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 98, - "points": [ - 1481, - 781, - 1481, - 824, - 1492, - 833, - 1493, - 842, - 1502, - 842, - 1506, - 836, - 1567, - 836, - 1570, - 839, - 1570, - 841, - 1574, - 844, - 1579, - 844, - 1582, - 841, - 1583, - 801, - 1576, - 792, - 1579, - 783, - 1571, - 781, - 1564, - 771, - 1561, - 769, - 1543, - 766, - 1505, - 766, - 1498, - 768, - 1487, - 780 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "28f5b6c51b70739c2cdc2692af341a7098e056b", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 99, - "points": [ - 162, - 777, - 162, - 803, - 167, - 809, - 175, - 808, - 181, - 802, - 193, - 803, - 202, - 801, - 236, - 800, - 241, - 804, - 248, - 805, - 253, - 802, - 257, - 795, - 274, - 794, - 274, - 763, - 267, - 753, - 266, - 748, - 262, - 744, - 262, - 741, - 257, - 735, - 254, - 734, - 192, - 735, - 187, - 738, - 185, - 742, - 175, - 752, - 170, - 753, - 169, - 765, - 166, - 768, - 165, - 774 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "ccfe97f74dece5fea03619ff669dfefe68a5e0ba", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 99, - "points": [ - 666, - 700, - 667, - 708, - 663, - 716, - 663, - 741, - 667, - 744, - 671, - 744, - 679, - 739, - 724, - 739, - 732, - 744, - 737, - 744, - 740, - 738, - 747, - 732, - 746, - 701, - 738, - 695, - 738, - 688, - 731, - 681, - 684, - 681, - 678, - 687, - 678, - 690, - 675, - 695 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "c2bf17c4f161e7f0419bb2c41ea07412d42224", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 99, - "points": [ - 340, - 911, - 350, - 921, - 350, - 930, - 345, - 935, - 344, - 942, - 339, - 950, - 339, - 983, - 340, - 987, - 346, - 994, - 355, - 994, - 361, - 989, - 361, - 984, - 364, - 981, - 397, - 982, - 432, - 981, - 445, - 979, - 451, - 981, - 454, - 984, - 455, - 990, - 459, - 994, - 470, - 995, - 476, - 990, - 477, - 978, - 482, - 971, - 486, - 969, - 493, - 970, - 501, - 965, - 502, - 949, - 505, - 940, - 505, - 926, - 501, - 916, - 501, - 903, - 499, - 895, - 496, - 890, - 494, - 877, - 490, - 874, - 488, - 866, - 482, - 861, - 402, - 861, - 389, - 865, - 378, - 872, - 370, - 881, - 368, - 889, - 364, - 894, - 362, - 901, - 359, - 904 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "4f0117afcf92115345dba62a9a2585ed294c53d0", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 99, - "points": [ - 1137, - 876, - 1130, - 874, - 1122, - 874, - 1118, - 870, - 1112, - 857, - 1107, - 852, - 1101, - 849, - 1032, - 851, - 1027, - 853, - 1020, - 870, - 1014, - 875, - 1002, - 876, - 1001, - 880, - 1003, - 883, - 1007, - 884, - 1010, - 889, - 1004, - 898, - 1006, - 946, - 1011, - 971, - 1018, - 972, - 1022, - 970, - 1024, - 967, - 1024, - 962, - 1027, - 959, - 1045, - 959, - 1059, - 955, - 1079, - 955, - 1089, - 957, - 1111, - 956, - 1117, - 962, - 1118, - 966, - 1121, - 969, - 1130, - 968, - 1132, - 966, - 1132, - 917, - 1129, - 905, - 1132, - 898, - 1127, - 891, - 1126, - 886, - 1129, - 883, - 1136, - 881 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "b54277bb2047b209ceead953f10aed42d553cef", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 99, - "points": [ - 396, - 738, - 399, - 743, - 399, - 746, - 392, - 760, - 392, - 785, - 396, - 789, - 400, - 789, - 406, - 784, - 437, - 785, - 444, - 783, - 458, - 783, - 463, - 788, - 469, - 790, - 474, - 787, - 474, - 784, - 478, - 778, - 489, - 777, - 491, - 775, - 490, - 738, - 480, - 727, - 470, - 720, - 419, - 720, - 414, - 723, - 408, - 733, - 404, - 736 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "95f82dd6154efc3a9d9cd45e4db0a3852d5a798d", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 99, - "points": [ - 277, - 812, - 271, - 810, - 183, - 810, - 173, - 812, - 163, - 820, - 152, - 826, - 142, - 837, - 141, - 842, - 131, - 850, - 125, - 850, - 121, - 854, - 121, - 858, - 123, - 860, - 122, - 870, - 116, - 876, - 115, - 883, - 110, - 891, - 109, - 933, - 111, - 941, - 118, - 945, - 124, - 945, - 137, - 937, - 150, - 935, - 215, - 936, - 220, - 939, - 226, - 947, - 239, - 947, - 241, - 946, - 242, - 941, - 242, - 926, - 249, - 919, - 255, - 918, - 263, - 922, - 277, - 921, - 280, - 916, - 280, - 890, - 285, - 869, - 281, - 845, - 282, - 831, - 279, - 824, - 279, - 816 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "7940b1490647cc4607eada6f15a7e3f94d6b9577", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1, - 698, - 0, - 747, - 2, - 751, - 23, - 749, - 27, - 747, - 35, - 748, - 45, - 746, - 49, - 751, - 57, - 753, - 66, - 743, - 70, - 741, - 82, - 743, - 86, - 740, - 86, - 704, - 68, - 688, - 16, - 688, - 8, - 691 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "6a6929b0485169689f253af1ad9278b3cce11258", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 997, - 1002, - 998, - 1009, - 1010, - 1012, - 1014, - 1015, - 1008, - 1023, - 1005, - 1030, - 1005, - 1034, - 1000, - 1038, - 998, - 1042, - 998, - 1048, - 1000, - 1050, - 999, - 1053, - 1005, - 1056, - 1124, - 1057, - 1135, - 1055, - 1150, - 1056, - 1151, - 1054, - 1151, - 1036, - 1146, - 1027, - 1146, - 1019, - 1137, - 1009, - 1149, - 1007, - 1155, - 1004, - 1156, - 1000, - 1150, - 995, - 1142, - 995, - 1136, - 997, - 1128, - 982, - 1120, - 977, - 1038, - 977, - 1031, - 979, - 1025, - 983, - 1023, - 995, - 1021, - 997, - 1003, - 997 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "1ce1dbabb3758d266e9217575dc1d4e9ff60b128", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 277, - 811, - 279, - 816, - 277, - 820, - 277, - 827, - 284, - 835, - 284, - 854, - 288, - 861, - 312, - 861, - 342, - 857, - 348, - 858, - 354, - 866, - 359, - 866, - 363, - 863, - 364, - 855, - 368, - 850, - 382, - 849, - 385, - 847, - 384, - 837, - 387, - 829, - 388, - 817, - 384, - 810, - 383, - 799, - 367, - 782, - 305, - 782, - 293, - 792, - 288, - 801 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "654b7f814096da7f4b71e842b6c918c36a08eacf", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1649, - 1011, - 1655, - 1020, - 1653, - 1030, - 1649, - 1038, - 1648, - 1055, - 1651, - 1058, - 1696, - 1058, - 1726, - 1055, - 1800, - 1056, - 1814, - 1058, - 1820, - 1057, - 1821, - 1055, - 1815, - 1047, - 1815, - 1041, - 1811, - 1037, - 1808, - 1027, - 1805, - 1026, - 1803, - 1023, - 1802, - 1011, - 1793, - 1005, - 1789, - 999, - 1775, - 992, - 1735, - 990, - 1687, - 992, - 1680, - 996, - 1673, - 1003, - 1672, - 1007, - 1669, - 1010, - 1652, - 1009 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "32405cbb3cec698d9036f02fed1a973c44b1deff", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1339, - 926, - 1338, - 931, - 1347, - 940, - 1347, - 945, - 1343, - 952, - 1344, - 986, - 1348, - 997, - 1354, - 1002, - 1356, - 1009, - 1356, - 1022, - 1362, - 1031, - 1369, - 1031, - 1374, - 1029, - 1376, - 1022, - 1379, - 1019, - 1397, - 1019, - 1413, - 1021, - 1418, - 1023, - 1444, - 1023, - 1455, - 1020, - 1474, - 1020, - 1486, - 1024, - 1494, - 1023, - 1495, - 970, - 1491, - 963, - 1489, - 953, - 1485, - 949, - 1482, - 936, - 1483, - 932, - 1491, - 925, - 1487, - 922, - 1481, - 923, - 1475, - 920, - 1467, - 911, - 1466, - 906, - 1459, - 899, - 1370, - 899, - 1360, - 906, - 1359, - 917, - 1355, - 922 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "1681fb7d71c3e2b0ce28cbfc24eac3ea1922e8e", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1142, - 764, - 1138, - 761, - 1126, - 763, - 1123, - 759, - 1122, - 747, - 1117, - 742, - 1072, - 742, - 1054, - 744, - 1046, - 747, - 1044, - 750, - 1041, - 764, - 1038, - 767, - 1032, - 768, - 1038, - 773, - 1039, - 776, - 1035, - 785, - 1036, - 844, - 1049, - 844, - 1050, - 836, - 1054, - 833, - 1072, - 835, - 1117, - 833, - 1123, - 837, - 1124, - 842, - 1126, - 844, - 1133, - 845, - 1136, - 841, - 1138, - 806, - 1135, - 798, - 1134, - 784, - 1131, - 781, - 1129, - 775, - 1133, - 770, - 1140, - 769, - 1142, - 767 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "93a1502c2bc706c3fd3884c8ce8fc20ec097ea0", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1252, - 740, - 1252, - 749, - 1256, - 752, - 1255, - 767, - 1257, - 772, - 1257, - 781, - 1262, - 789, - 1263, - 796, - 1271, - 796, - 1280, - 784, - 1284, - 781, - 1292, - 779, - 1308, - 779, - 1321, - 777, - 1336, - 777, - 1341, - 779, - 1346, - 778, - 1347, - 763, - 1345, - 746, - 1342, - 743, - 1342, - 736, - 1346, - 734, - 1346, - 731, - 1337, - 728, - 1325, - 718, - 1307, - 716, - 1277, - 716, - 1270, - 719, - 1258, - 734, - 1256, - 744 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "53b3eb4966d32ad0adcd8acb8623b1942ec7050", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 184, - 638, - 179, - 633, - 173, - 631, - 124, - 631, - 115, - 636, - 109, - 643, - 105, - 651, - 100, - 655, - 94, - 655, - 91, - 662, - 89, - 686, - 93, - 708, - 103, - 708, - 107, - 701, - 115, - 698, - 160, - 698, - 180, - 679, - 192, - 677, - 192, - 654, - 185, - 643 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "3f2f416c69fc616840e064fe6abf6a9f578aa2c7", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 152, - 720, - 152, - 729, - 155, - 736, - 155, - 744, - 159, - 746, - 166, - 746, - 170, - 742, - 173, - 744, - 173, - 746, - 176, - 746, - 189, - 737, - 197, - 734, - 249, - 734, - 253, - 732, - 253, - 702, - 250, - 698, - 249, - 693, - 234, - 680, - 183, - 680, - 170, - 689, - 169, - 692, - 159, - 702 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "e801e4a11f7ad9b390cd4ee6c392435ba8fef570", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72275, - "probability": 98, - "points": [ - 1048, - 572, - 1045, - 579, - 1040, - 583, - 1041, - 592, - 1040, - 597, - 1037, - 601, - 1037, - 626, - 1035, - 648, - 1037, - 655, - 1036, - 667, - 1038, - 684, - 1041, - 684, - 1045, - 680, - 1053, - 667, - 1058, - 663, - 1082, - 663, - 1097, - 661, - 1102, - 665, - 1105, - 671, - 1113, - 680, - 1117, - 693, - 1123, - 693, - 1125, - 689, - 1125, - 673, - 1127, - 666, - 1126, - 650, - 1128, - 640, - 1126, - 599, - 1120, - 594, - 1099, - 591, - 1092, - 582, - 1091, - 577, - 1086, - 572 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "5874772e1428946f9be2b28ea88ed9cf22e625b3", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - }, - { - "type": "polygon", - "classId": 72275, - "probability": 100, - "points": [ - 1194, - 583, - 1192, - 601, - 1190, - 604, - 1190, - 608, - 1192, - 611, - 1191, - 629, - 1193, - 647, - 1198, - 651, - 1199, - 656, - 1205, - 657, - 1209, - 649, - 1220, - 649, - 1225, - 638, - 1229, - 634, - 1236, - 634, - 1241, - 636, - 1262, - 635, - 1262, - 595, - 1259, - 591, - 1255, - 589, - 1254, - 583, - 1242, - 577, - 1206, - 577 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "e9c738b1fd328df97c3f51616cdb574737a21eed", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - }, - { - "type": "polygon", - "classId": 72276, - "probability": 100, - "points": [ - 725.84, - 738.06, - 730.5, - 741, - 736.63, - 742.23, - 740.55, - 741, - 749.13, - 726.79, - 756.24, - 723.11, - 757.22, - 718.45, - 754.03, - 715.27, - 752.31, - 712.08, - 753.54, - 699.34, - 755.75, - 696.64, - 761.14, - 694.93, - 766.77, - 695.91, - 772.17, - 698.36, - 772.9, - 699.34, - 773.64, - 711.35, - 772.17, - 716, - 772.17, - 721.15, - 775.35, - 725.32, - 790.06, - 735.12, - 793.98, - 737.08, - 808.44, - 736.84, - 807.7, - 744.68, - 806.72, - 746.88, - 788.59, - 748.36, - 782.95, - 745.17, - 779.76, - 744.92, - 779.03, - 746.15, - 777.56, - 772.37, - 778.29, - 784.87, - 781.97, - 803.75, - 780.5, - 827.27, - 789.57, - 830.21, - 795.69, - 833.4, - 795.94, - 836.1, - 795.45, - 836.83, - 771.68, - 837.81, - 770.94, - 831.19, - 769.22, - 828.01, - 769.22, - 825.31, - 771.43, - 822.86, - 770.7, - 819.43, - 771.43, - 808.65, - 760.4, - 815.26, - 750.35, - 817.96, - 751.33, - 819.68, - 750.84, - 820.41, - 749.37, - 819.68, - 747.41, - 820.17, - 746.68, - 821.15, - 746.43, - 824.82, - 743, - 829.23, - 741.78, - 836.1, - 741.46, - 838.04, - 740.55, - 837.32, - 738.83, - 831.19, - 737.12, - 830.7, - 735.4, - 812.08, - 744.96, - 810.36, - 763.83, - 800.56, - 762.36, - 794.92, - 760.65, - 791.49, - 760.89, - 787.57, - 758.44, - 784.38, - 755.01, - 782.18, - 753.29, - 778.99, - 749.62, - 777.77, - 746.43, - 750.56, - 730.75, - 751.54, - 725.11, - 749.58, - 721.43, - 744.68, - 721.19, - 742.96 - ], - "groupId": 0, - "pointLabels": { - "0": "Left Hand", - "21": "Right Hand", - "36": "Right Foot", - "53": "Left Foot" - }, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "932bda5b59db89dd68602306c70d8da62e0afdc", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - } - ], - "tags": [ - "tag1", - "tag2" - ], - "comments": [ - { - "x": 521.41, - "y": 531.6, - "resolved": false, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "correspondence": [ - { - "text": "dd2", - "email": "hovnatan@superannotate.com" - } - ] - } - ] -} diff --git a/tests/data_set/sample_project_vector/example_image_3.jpg b/tests/data_set/sample_project_vector/example_image_3.jpg deleted file mode 100644 index be81964..0000000 Binary files a/tests/data_set/sample_project_vector/example_image_3.jpg and /dev/null differ diff --git a/tests/data_set/sample_project_vector/example_image_3.jpg___fuse.png b/tests/data_set/sample_project_vector/example_image_3.jpg___fuse.png deleted file mode 100644 index 2c0fe51..0000000 Binary files a/tests/data_set/sample_project_vector/example_image_3.jpg___fuse.png and /dev/null differ diff --git a/tests/data_set/sample_project_vector/example_image_3.jpg___objects.json b/tests/data_set/sample_project_vector/example_image_3.jpg___objects.json deleted file mode 100644 index 36c8a9f..0000000 --- a/tests/data_set/sample_project_vector/example_image_3.jpg___objects.json +++ /dev/null @@ -1,720 +0,0 @@ -{ - "metadata": { - "name": "example_image_3.jpg", - "width": 1200, - "height": 675, - "status": "Completed", - "pinned": false, - "isPredicted": null, - "projectId": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 0.39, - 272.46, - 4.33, - 260.62, - 30.82, - 222.01, - 17.3, - 216.66, - 41.25, - 208.77, - 58.72, - 209.33, - 72.81, - 200.03, - 99.02, - 194.96, - 182.44, - 185.09, - 280.79, - 179.18, - 315.73, - 183.97, - 319.4, - 186.78, - 359.13, - 183.4, - 380.55, - 184.25, - 441.98, - 205.38, - 485.38, - 231.59, - 561.19, - 240.89, - 562.88, - 242.3, - 568.52, - 244.27, - 583.17, - 254.7, - 585.99, - 259.21, - 589.94, - 267.95, - 593.32, - 280.63, - 594.16, - 286.83, - 593.88, - 290.21, - 580.92, - 311.06, - 575.56, - 318.11, - 573.03, - 328.82, - 573.31, - 342.91, - 571.05, - 351.36, - 567.96, - 358.97, - 565.42, - 364.05, - 561.47, - 369.12, - 556.68, - 373.35, - 549.07, - 378.14, - 544, - 380.11, - 531.88, - 382.08, - 521.46, - 381.8, - 502.57, - 372.22, - 495.25, - 364.33, - 492.43, - 359.54, - 306.15, - 408.01, - 302.21, - 414.77, - 300.23, - 415.9, - 295.72, - 416.18, - 291.5, - 418.44, - 284.17, - 441.83, - 277.41, - 449.72, - 272.9, - 457.04, - 267.26, - 463.24, - 257.96, - 468.32, - 245.28, - 471.98, - 238.23, - 472.54, - 228.93, - 474.52, - 218.22, - 474.52, - 204.13, - 470.85, - 190.61, - 462.12, - 183, - 453.94, - 176.23, - 453.1, - 168.63, - 450.84, - 165.81, - 449.43, - 161.3, - 445.49, - 158.48, - 440.7, - 157.63, - 437.88, - 156.51, - 428.02, - 89.44, - 432.81, - 79.01, - 428.3, - 72.81, - 431.4, - 62.66, - 434.5, - 57.87, - 435.34, - 48.57, - 435.63, - 46.88, - 434.5, - 46.04, - 429.99, - 44.06, - 426.04, - 0.39, - 404.91 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1198.84, - 310.57, - 1099.1, - 298.81, - 1059.15, - 297.83, - 1035.87, - 304.93, - 993.48, - 321.84, - 918, - 370.61, - 917.27, - 376.98, - 867.77, - 435.79, - 857.96, - 439.71, - 854.78, - 444.12, - 852.33, - 451.72, - 843.75, - 488.72, - 843.26, - 488.72, - 835.17, - 504.41, - 833.7, - 512.25, - 833.21, - 565.43, - 834.44, - 605.37, - 835.91, - 607.58, - 840.81, - 611.01, - 877.08, - 643.6, - 893.5, - 648.25, - 949.86, - 674.23, - 1199.33, - 674.48 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 981.04, - 326.53, - 979.55, - 317.59, - 972.1, - 288.89, - 933.33, - 276.22, - 923.27, - 270.63, - 884.88, - 245.28, - 849.85, - 237.08, - 783.88, - 227.02, - 709.72, - 229.63, - 608.34, - 256.09, - 607.6, - 256.84, - 604.62, - 264.29, - 606.85, - 267.64, - 591.94, - 294.85, - 574.43, - 320.19, - 572.94, - 328.39, - 572.94, - 340.69, - 574.05, - 343.3, - 574.43, - 347.77, - 574.43, - 360.45, - 571.44, - 368.64, - 569.95, - 375.35, - 570.33, - 394.36, - 575.17, - 413, - 576.66, - 415.98, - 580.76, - 420.08, - 583.37, - 425.67, - 587.84, - 429.77, - 592.69, - 437.22, - 596.42, - 439.46, - 600.89, - 440.58, - 612.07, - 445.42, - 627.35, - 445.79, - 640.02, - 441.69, - 645.24, - 436.1, - 684.75, - 448.03, - 716.8, - 459.96, - 729.84, - 469.65, - 741.77, - 489.77, - 750.34, - 493.87, - 754.81, - 494.62, - 765.25, - 494.62, - 779.41, - 490.52, - 786.12, - 487.16, - 793.57, - 480.45, - 798.79, - 472.25, - 802.14, - 463.68, - 802.89, - 456.6, - 804.01, - 453.62, - 804.38, - 440.58, - 893.83, - 403.31, - 915.82, - 378.33, - 918.05, - 368.27, - 976.19, - 331.75 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 653.44, - 240.81, - 656.42, - 217.7, - 703.01, - 171.86, - 763.76, - 158.44, - 811.84, - 159.19, - 840.16, - 164.41, - 843.51, - 166.64, - 846.87, - 167.76, - 855.81, - 177.08, - 861.4, - 181.18, - 865.88, - 185.65, - 867.74, - 186.77, - 873.33, - 187.51, - 879.29, - 192.36, - 898.67, - 192.73, - 907.25, - 196.83, - 914.7, - 205.03, - 919.17, - 215.09, - 918.43, - 242.3, - 912.84, - 262.43, - 885.26, - 244.16, - 785.75, - 226.27, - 707.85, - 228.88, - 659.03, - 242.3 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 770.09, - 156.21, - 763.76, - 153.23, - 689.96, - 148.01, - 647.1, - 152.85, - 634.06, - 158.44, - 619.15, - 168.51, - 611.7, - 172.23, - 608.71, - 174.84, - 586.35, - 181.92, - 581.51, - 186.02, - 579.64, - 196.83, - 579.64, - 203.54, - 577.78, - 209.88, - 574.05, - 215.47, - 573.68, - 223.29, - 577.78, - 230.75, - 582.25, - 235.96, - 595.3, - 239.69, - 604.99, - 240.81, - 611.32, - 244.16, - 614.68, - 247.15, - 618.4, - 248.64, - 628.84, - 248.64, - 651.95, - 243.42, - 654.93, - 217.7, - 702.26, - 171.11 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1198.32, - 145.4, - 1134.59, - 143.91, - 1118.57, - 145.77, - 1113.72, - 148.38, - 1107.38, - 150.24, - 1102.17, - 154.72, - 1097.32, - 162.54, - 1093.97, - 171.49, - 1090.24, - 177.08, - 1081.67, - 185.28, - 1028.37, - 202.05, - 1022.04, - 212.48, - 1018.31, - 235.22, - 1018.68, - 249.75, - 1020.92, - 253.11, - 1023.53, - 254.97, - 1035.08, - 260.56, - 1037.32, - 264.29, - 1039.93, - 297.09, - 1043.28, - 301.56, - 1058.93, - 297.09, - 1079.06, - 298.2, - 1080.55, - 295.97, - 1082.79, - 288.89, - 1082.79, - 282.18, - 1174.84, - 293.36, - 1178.57, - 306.4, - 1180.43, - 308.27, - 1182.67, - 309.39, - 1197.95, - 309.76 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1104.78, - 149.87, - 1073.84, - 127.14, - 1046.63, - 123.04, - 1016.82, - 120.06, - 948.99, - 128.25, - 947.5, - 129.37, - 929.61, - 161.8, - 928.49, - 168.13, - 928.49, - 194.22, - 930.73, - 199.81, - 932.22, - 200.93, - 935.57, - 201.68, - 938.55, - 203.91, - 938.93, - 209.88, - 940.04, - 213.6, - 946.01, - 224.04, - 959.42, - 224.78, - 963.9, - 220.68, - 965.01, - 218.45, - 965.01, - 215.47, - 1019.06, - 214.72, - 1019.8, - 212.11, - 1026.51, - 201.68, - 1081.3, - 184.53, - 1090.61, - 175.21 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 311.68, - 181.44, - 374.66, - 182.42, - 320.01, - 185.86 - ], - "groupId": 1, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 394.51, - 187.08, - 470.97, - 212.81, - 502.34, - 221.88, - 509.2, - 228.25, - 510.67, - 233.64, - 484.69, - 230.95, - 438.62, - 204.72, - 397.7, - 190.02, - 390.35, - 186.35 - ], - "groupId": 1, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - } - ], - "tags": [ - "tag2", - "tag3" - ], - "comments": [] -} diff --git a/tests/data_set/sample_project_vector/example_image_4.jpg b/tests/data_set/sample_project_vector/example_image_4.jpg deleted file mode 100644 index 5a53004..0000000 Binary files a/tests/data_set/sample_project_vector/example_image_4.jpg and /dev/null differ diff --git a/tests/data_set/sample_project_vector/example_image_4.jpg___fuse.png b/tests/data_set/sample_project_vector/example_image_4.jpg___fuse.png deleted file mode 100644 index 10285ee..0000000 Binary files a/tests/data_set/sample_project_vector/example_image_4.jpg___fuse.png and /dev/null differ diff --git a/tests/data_set/sample_project_vector/example_image_4.jpg___objects.json b/tests/data_set/sample_project_vector/example_image_4.jpg___objects.json deleted file mode 100644 index 6ac232a..0000000 --- a/tests/data_set/sample_project_vector/example_image_4.jpg___objects.json +++ /dev/null @@ -1,1143 +0,0 @@ -{ - "metadata": { - "name": "example_image_4.jpg", - "width": 680, - "height": 382, - "status": "Completed", - "pinned": false, - "isPredicted": null, - "projectId": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 150.76, - 215.35, - 151.45, - 224.38, - 152.7, - 226.18, - 158.12, - 226.74, - 159.23, - 226.32, - 160.48, - 219.24, - 188.67, - 223.13, - 214.5, - 223.13, - 217.27, - 229.51, - 222.27, - 229.24, - 226.02, - 227.57, - 228.8, - 217.85, - 243.93, - 206.32, - 245.88, - 210.63, - 250.32, - 211.18, - 252.68, - 210.07, - 255.32, - 204.52, - 257.13, - 186.05, - 254.35, - 164.66, - 244.07, - 147.45, - 236.02, - 146.06, - 215.74, - 144.11, - 190.89, - 143, - 180.75, - 150.64, - 171.45, - 159.53, - 167.14, - 164.66, - 163.39, - 170.36, - 161.03, - 167.3, - 157.14, - 167.86, - 155.06, - 168.97, - 154.51, - 170.22, - 154.64, - 171.33, - 158.53, - 173.83, - 160.06, - 174.25, - 153.53, - 185.08, - 151.73, - 196.88, - 149.23, - 205.91 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 0.07, - 270.84, - 10.21, - 271.08, - 15.77, - 272.77, - 22.89, - 279.29, - 25.43, - 280.5, - 32.31, - 285.93, - 39.8, - 289.07, - 42.7, - 292.09, - 45.47, - 296.32, - 48.25, - 301.27, - 49.22, - 305.13, - 45.71, - 336.53, - 44.39, - 339.91, - 42.94, - 342.2, - 38.83, - 344.38, - 34.24, - 344.98, - 31.22, - 344.98, - 28.33, - 343.41, - 0.07, - 362.49 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 0.15, - 363.3, - 4.62, - 360.62, - 10.53, - 359.01, - 44.89, - 355.07, - 77.63, - 358.11, - 94.09, - 363.3, - 97.49, - 366.34, - 100.89, - 371, - 102.15, - 374.4, - 102.5, - 381.73, - 0.51, - 381.73 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 110.2, - 380.84, - 105.72, - 373.68, - 103.75, - 367.95, - 106.44, - 338.61, - 108.05, - 331.81, - 125.41, - 312.48, - 117, - 308.37, - 115.92, - 307.29, - 115.38, - 304.97, - 115.56, - 301.93, - 116.64, - 300.49, - 117.89, - 299.96, - 120.93, - 299.96, - 125.41, - 301.75, - 126.66, - 302.82, - 128.09, - 308.37, - 151.35, - 276.87, - 169.96, - 261.84, - 178.91, - 256.83, - 223.11, - 258.26, - 244.94, - 266.5, - 254.78, - 276.69, - 258.36, - 283.49, - 263.37, - 288.68, - 266.05, - 293.34, - 267.3, - 300.49, - 267.66, - 313.2, - 262.83, - 348.09, - 262.83, - 347.73, - 259.43, - 355.07, - 258.71, - 355.61, - 249.95, - 356.14, - 246.19, - 353.82, - 245.29, - 351.31, - 226.33, - 369.03, - 225.07, - 381.37 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 396.32, - 381.55, - 397.21, - 364.02, - 391.84, - 345.77, - 388.8, - 338.07, - 387.91, - 337.36, - 380.21, - 333.6, - 374.67, - 331.45, - 371.45, - 330.56, - 308.64, - 325.55, - 296.11, - 328.59, - 289.49, - 333.6, - 273.21, - 353.46, - 264.44, - 369.74, - 255.49, - 367.06, - 252.99, - 368.13, - 250.66, - 371, - 250.66, - 374.22, - 252.27, - 376.36, - 255.85, - 379.05, - 256.03, - 381.37 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 339.59, - 327.16, - 340.67, - 317.31, - 356.59, - 298.35, - 355.88, - 297.45, - 346.75, - 294.23, - 346.57, - 293.52, - 349.62, - 288.86, - 354.8, - 288.5, - 356.95, - 290.29, - 357.67, - 292.62, - 359.64, - 292.98, - 377.89, - 267.21, - 390.59, - 255.76, - 417.61, - 254.33, - 454.29, - 259.7, - 465.21, - 265.24, - 474.51, - 273.83, - 478.09, - 285.64, - 479.52, - 301.93, - 478.27, - 324.11, - 477.02, - 331.81, - 472.37, - 340.04, - 469.5, - 342.37, - 466.64, - 342.72, - 462.53, - 342.01, - 460.38, - 339.14, - 460.2, - 337.18, - 449.1, - 354, - 449.28, - 367.95, - 447.67, - 370.82, - 444.27, - 374.57, - 436.76, - 376.9, - 431.57, - 376.36, - 427.81, - 371.89, - 396.68, - 373.68, - 396.85, - 362.41, - 389.16, - 337.53, - 379.32, - 332.34, - 374.13, - 330.38 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 406.52, - 253.43, - 414.39, - 239.83, - 405.44, - 236.43, - 403.65, - 233.57, - 404.01, - 230.17, - 405.27, - 229.46, - 407.77, - 229.46, - 411.53, - 230.53, - 415.29, - 233.75, - 416.72, - 233.93, - 436.58, - 209.24, - 448.75, - 200.82, - 493.3, - 202.61, - 508.33, - 207.98, - 515.13, - 227.67, - 517.1, - 230.53, - 519.79, - 238.22, - 521.04, - 239.65, - 521.58, - 241.44, - 521.58, - 246.81, - 517.82, - 274.01, - 512.81, - 279.92, - 506.01, - 280.81, - 505.11, - 278.13, - 498.67, - 286.36, - 496.88, - 300.85, - 495.63, - 304.07, - 493.66, - 305.33, - 491.16, - 305.86, - 487.76, - 305.68, - 483.1, - 302.82, - 483.1, - 297.09, - 479.17, - 296.74, - 477.74, - 285.46, - 474.51, - 273.65, - 463.96, - 264.17, - 455.01, - 259.7, - 416.9, - 253.43 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 232.6, - 261.5, - 231.9, - 254.42, - 233.43, - 249.84, - 241.2, - 233.31, - 241.34, - 231.51, - 234.96, - 227.9, - 234.4, - 226.93, - 234.4, - 224.43, - 237.04, - 223.45, - 243.29, - 223.45, - 243.98, - 224.01, - 244.95, - 227.06, - 261.34, - 203.74, - 272.87, - 195.13, - 286.06, - 192.77, - 304.53, - 193.32, - 325.08, - 196.38, - 336.19, - 201.93, - 339.52, - 211.65, - 345.77, - 218.04, - 348.13, - 222.48, - 350.07, - 247.89, - 348.41, - 251.92, - 346.88, - 264.84, - 343.69, - 270.11, - 341.05, - 271.36, - 338.13, - 271.92, - 335.35, - 271.64, - 332.86, - 266.78, - 323.41, - 276.92, - 323.55, - 286.78, - 321.33, - 292.47, - 319.66, - 294, - 315.08, - 295.39, - 312.58, - 294.83, - 310.36, - 292.47, - 308.69, - 288.03, - 284.81, - 290.53, - 263.98, - 290.39, - 261.9, - 287.19, - 257.87, - 283.44, - 254.81, - 276.5, - 245.09, - 266.22 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 423.82, - 109.43, - 445.55, - 110.52, - 450.74, - 115.71, - 451.95, - 120.9, - 455.82, - 129.23, - 457.75, - 135.39, - 458.11, - 142.52, - 456.06, - 146.26, - 455.57, - 148.07, - 455.57, - 150.12, - 456.18, - 151.69, - 455.82, - 165.94, - 455.09, - 167.88, - 452.07, - 169.81, - 450.38, - 170.17, - 447.48, - 169.93, - 445.67, - 168, - 440.24, - 173.55, - 439.63, - 181.64, - 437.34, - 183.57, - 434.44, - 183.45, - 432.39, - 182.61, - 430.94, - 180.67, - 430.58, - 176.81, - 422.73, - 176.93, - 423.45, - 174.03, - 422.97, - 125.61, - 419.47, - 121.99, - 423.57, - 118.85 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 12.52, - 102.15, - 17.66, - 94.48, - 15.77, - 92.17, - 15.56, - 88.71, - 16.4, - 88.39, - 18.4, - 89.23, - 22.39, - 89.44, - 23.86, - 90.6, - 39.72, - 73.17, - 46.02, - 71.17, - 65.76, - 69.28, - 80.04, - 68.97, - 89.91, - 69.7, - 98.1, - 73.48, - 102.51, - 77.79, - 103.77, - 81.15, - 97.89, - 123.68, - 97.89, - 127.35, - 97.05, - 124.31, - 82.66, - 126.93, - 81.61, - 134.39, - 80.46, - 136.38, - 75, - 138.27, - 72.79, - 138.27, - 71.22, - 137.54, - 69.96, - 136.28, - 68.59, - 132.81, - 63.55, - 133.13, - 63.24, - 104.04, - 57.88, - 103.51, - 57.67, - 106.88 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72275, - "probability": 100, - "points": [ - 0.41, - 101.91, - 57.82, - 107.46, - 58.02, - 103.56, - 63.37, - 103.97, - 63.17, - 173.31, - 55.76, - 174.96, - 57.2, - 186.07, - 57.2, - 205.21, - 53.08, - 205.42, - 52.06, - 222.5, - 50.41, - 226.82, - 47.32, - 232.17, - 45.88, - 236.49, - 42.18, - 239.78, - 36.42, - 242.25, - 27.57, - 242.46, - 24.28, - 245.13, - 22.42, - 248.63, - 20.78, - 250.28, - 15.02, - 253.16, - 5.76, - 253.77, - 0.41, - 252.13 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - }, - { - "type": "polygon", - "classId": 72275, - "probability": 100, - "points": [ - 222.23, - 143.48, - 232.31, - 141.21, - 234.17, - 142.45, - 239.52, - 142.65, - 242.6, - 140.18, - 244.87, - 135.66, - 245.69, - 116.52, - 262.15, - 116.72, - 261.74, - 109.52, - 265.65, - 100.67, - 261.95, - 83.39, - 253.72, - 81.54, - 214, - 77.42, - 210.5, - 76.39, - 208.65, - 62.4, - 206.8, - 60.75, - 206.59, - 57.25, - 118.52, - 53.96, - 117.9, - 56.64, - 109.26, - 61.16, - 106.38, - 65.49, - 98.56, - 122.69, - 97.94, - 141.62, - 99.18, - 152.74, - 108.02, - 155, - 110.7, - 160.35, - 112.96, - 162.61, - 115.02, - 163.23, - 118.11, - 162.41, - 119.96, - 155.82, - 169.14, - 159.12, - 168.94, - 161.38, - 190.95, - 142.45 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - }, - { - "type": "polygon", - "classId": 72275, - "probability": 100, - "points": [ - 303.1, - 192.66, - 304.75, - 181.96, - 307.84, - 169.2, - 301.66, - 167.96, - 300.84, - 160.56, - 302.69, - 156.44, - 306.4, - 156.44, - 309.48, - 157.68, - 321.01, - 130.92, - 318.95, - 129.28, - 318.95, - 120.84, - 350.64, - 103.14, - 351.46, - 106.64, - 421.84, - 110.14, - 422.66, - 108.7, - 423.28, - 108.7, - 423.28, - 118.58, - 419.16, - 122.08, - 422.45, - 125.78, - 423.07, - 173.73, - 419.16, - 196.77, - 415.46, - 208.91, - 412.99, - 212.82, - 410.52, - 214.06, - 407.84, - 213.85, - 404.55, - 210.35, - 403.93, - 208.3, - 392.41, - 217.76, - 390.76, - 231.76, - 389.74, - 233.4, - 385.83, - 235.67, - 376.57, - 235.67, - 374.3, - 233.81, - 372.66, - 227.85, - 348.37, - 228.46, - 348.58, - 222.29, - 339.73, - 211.59, - 335.82, - 201.3, - 324.92, - 195.95 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - }, - { - "type": "polygon", - "classId": 72275, - "probability": 100, - "points": [ - 462.58, - 200.89, - 463.2, - 193.07, - 468.34, - 173.93, - 459.08, - 172.08, - 461.96, - 158.29, - 467.31, - 158.29, - 467.93, - 165.7, - 471.22, - 165.91, - 481.31, - 128.04, - 490.36, - 110.96, - 486.86, - 109.52, - 486.45, - 99.85, - 489.33, - 82.36, - 518.76, - 62.61, - 517.73, - 80.1, - 586.87, - 88.74, - 585.64, - 91.62, - 589.55, - 95.53, - 589.96, - 160.35, - 587.08, - 178.05, - 582.76, - 190.81, - 581.11, - 211.18, - 579.46, - 214.06, - 576.79, - 215.91, - 572.47, - 216.32, - 568.56, - 214.47, - 559.71, - 226.2, - 556.21, - 228.05, - 551.89, - 234.02, - 552.1, - 250.07, - 548.8, - 249.66, - 545.1, - 246.37, - 544.28, - 243.9, - 543.86, - 236.08, - 519.58, - 237.11, - 516.7, - 229.49, - 514.85, - 227.85, - 508.26, - 207.06, - 493.45, - 202.12 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - } - ], - "tags": [ - "tag1" - ], - "comments": [] -} diff --git a/tests/data_set/sample_project_vector_invalid/classes/classes.json b/tests/data_set/sample_project_vector_invalid/classes/classes.json deleted file mode 100644 index 810a5a4..0000000 --- a/tests/data_set/sample_project_vector_invalid/classes/classes.json +++ /dev/null @@ -1,157 +0,0 @@ -[ - { - "id": 55917, - "project_id": 11979, - "name": "Personal vehicle", - "color": "#ecb65f", - "count": 25, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:48:19.000Z", - "attribute_groups": [ - { - "id": 17245, - "class_id": 55917, - "name": "Num doors", - "is_multiselect": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z", - "attributes": [ - { - "id": 62792, - "group_id": 17245, - "project_id": 11979, - "name": "2", - "count": 1, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:46:28.000Z" - }, - { - "id": 62793, - "group_id": 17245, - "project_id": 11979, - "name": "4", - "count": 1, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z" - } - ] - } - ] - }, - { - "id": 55918, - "project_id": 11979, - "name": "Large vehicle", - "color": "#737b28", - "count": 1, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:48:19.000Z", - "attribute_groups": [ - { - "id": 17246, - "class_id": 55918, - "name": "swedish", - "is_multiselect": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z", - "attributes": [ - { - "id": 62794, - "group_id": 17246, - "project_id": 11979, - "name": "yes", - "count": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z" - }, - { - "id": 62795, - "group_id": 17246, - "project_id": 11979, - "name": "no", - "count": 1, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:46:28.000Z" - } - ] - }, - { - "id": 17247, - "class_id": 55918, - "name": "Num doors", - "is_multiselect": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z", - "attributes": [ - { - "id": 62796, - "group_id": 17247, - "project_id": 11979, - "name": "2", - "count": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z" - }, - { - "id": 62797, - "group_id": 17247, - "project_id": 11979, - "name": "4", - "count": 1, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:46:28.000Z" - } - ] - } - ] - }, - { - "id": 55919, - "project_id": 11979, - "name": "Human", - "color": "#e4542b", - "count": 9, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:48:14.000Z", - "attribute_groups": [ - { - "id": 17248, - "class_id": 55919, - "name": "Height", - "is_multiselect": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z", - "attributes": [ - { - "id": 62798, - "group_id": 17248, - "project_id": 11979, - "name": "Tall", - "count": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z" - }, - { - "id": 62799, - "group_id": 17248, - "project_id": 11979, - "name": "Short", - "count": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z" - } - ] - } - ] - }, - { - "id": 55920, - "project_id": 11979, - "name": "Plant", - "color": "#46ccb2", - "count": 0, - "createdAt": "2020-10-12T11:35:20.000Z", - "updatedAt": "2020-10-12T11:35:20.000Z", - "attribute_groups": [] - } -] diff --git a/tests/data_set/sample_project_vector_invalid/example_image_1.jpg b/tests/data_set/sample_project_vector_invalid/example_image_1.jpg deleted file mode 100644 index 4dee935..0000000 Binary files a/tests/data_set/sample_project_vector_invalid/example_image_1.jpg and /dev/null differ diff --git a/tests/data_set/sample_project_vector_invalid/example_image_1.jpg___fuse.png b/tests/data_set/sample_project_vector_invalid/example_image_1.jpg___fuse.png deleted file mode 100644 index ae61b39..0000000 Binary files a/tests/data_set/sample_project_vector_invalid/example_image_1.jpg___fuse.png and /dev/null differ diff --git a/tests/data_set/sample_project_vector_invalid/example_image_1.jpg___objects.json b/tests/data_set/sample_project_vector_invalid/example_image_1.jpg___objects.json deleted file mode 100644 index 3c007fc..0000000 --- a/tests/data_set/sample_project_vector_invalid/example_image_1.jpg___objects.json +++ /dev/null @@ -1,3091 +0,0 @@ -{ - "metadata": { - "name": "example_image_1.jpg", - "width": 1024, - "height": 683, - "status": "Completed", - "pinned": false, - "isPredicted": null, - "projectId": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "type": "loligon", - "probability": 100, - "points": { - "x1": 437.16, - "x2": 465.23, - "y1": 341.5, - "y2": 357.09 - }, - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [ - { - "id": 117845, - "groupId": 28230, - "name": "2", - "groupName": "Num doors" - } - ], - "trackingId": "aaa97f80c9e54a5f2dc2e920fc92e5033d9af45b", - "error": null, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "bbox", - "classId": 72274, - "probability": 100, - "points": { - "x1": 480.0, - "x2": 490.0, - "y1": 340.0, - "y2": 350.0 - }, - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [ - { - "id": 117845, - "groupId": 28230, - "name": "2", - "groupName": "Num doors" - } - ], - "error": null, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle1" - }, - { - "type": "bbox", - "classId": 72274, - "probability": 100, - "points": { - "x1": 500.0, - "x2": 510.0, - "y1": 340.0, - "y2": 350.0 - }, - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [ - { - "id": 117845, - "groupId": 28230, - "name": "10", - "groupName": "Num doors" - } - ], - "error": null, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "bbox", - "classId": 72274, - "probability": 100, - "points": { - "x1": 520.0, - "x2": 530.0, - "y1": 340.0, - "y2": 350.0 - }, - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [ - { - "id": 117845, - "groupId": 28230, - "name": "4", - "groupName": "Num doors1" - } - ], - "error": null, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "template", - "classId": 72274, - "probability": 100, - "points": [ - { - "id": 1, - "x": 800.8311630011381, - "y": 431.7220764160156 - }, - { - "id": 2, - "x": 834.6965942382812, - "y": 431.8820692877566 - }, - { - "id": 3, - "x": 834.6965942382812, - "y": 480.848388671875 - }, - { - "id": 4, - "x": 801.0125574701838, - "y": 480.848388671875 - }, - { - "id": 5, - "x": 702.6083268971072, - "y": 437.5428573337124 - }, - { - "id": 6, - "x": 702.5221557617188, - "y": 474.8859480851478 - } - ], - "connections": [ - { - "id": 1, - "from": 1, - "to": 2 - }, - { - "id": 2, - "from": 2, - "to": 3 - }, - { - "id": 3, - "from": 3, - "to": 4 - }, - { - "id": 4, - "from": 4, - "to": 1 - }, - { - "id": 5, - "from": 1, - "to": 5 - }, - { - "id": 6, - "from": 5, - "to": 6 - }, - { - "id": 7, - "from": 6, - "to": 4 - } - ], - "groupId": 0, - "pointLabels": { - "4": "top_left", - "5": "bottom_left" - }, - "locked": false, - "visible": false, - "attributes": [ - { - "name": "4", - "groupName": "Num doors", - "groupId": 28230, - "id": 117846 - } - ], - "templateName": "HandPose", - "trackingId": "cbde2787e76c41be77c1079e8d090252ad701ea", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 281.98, - 383.75, - 282.55, - 378.1, - 287.26, - 376.12, - 297.35, - 372.91, - 311.01, - 372.82, - 319.59, - 375.74, - 323.55, - 378.28, - 325.91, - 381.68, - 326.66, - 385.45, - 325.43, - 387.62, - 324.02, - 388.75, - 317.23, - 388.84, - 315.54, - 390.26, - 312.43, - 390.54, - 308.66, - 388.46, - 306.39, - 388.84, - 297.44, - 389.03, - 291.5, - 388.18, - 287.64, - 384.51 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "bf069efee9e65463824466f442a409a137eabaee", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 266.9, - 384.88, - 267.47, - 404.21, - 276.23, - 404.87, - 277.65, - 407.32, - 278.78, - 407.79, - 282.17, - 407.79, - 284.15, - 407.32, - 285.19, - 403.92, - 292.73, - 403.83, - 293.29, - 405.43, - 294.99, - 406.37, - 297.53, - 406.28, - 298.57, - 405.43, - 301.12, - 404.39, - 302.15, - 402.41, - 303.38, - 395.53, - 301.49, - 391.39, - 296.12, - 389.03, - 291.78, - 388.84, - 286.79, - 384.13, - 284.9, - 384.51 - ], - "groupId": 1, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "a520dde722112d1579ff65260166d02ac1c14e2", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 262.94, - 385.54, - 263.88, - 404.68, - 262.47, - 404.96, - 262.19, - 406.66, - 261.34, - 408.07, - 259.74, - 408.54, - 256.53, - 408.64, - 255.59, - 408.16, - 254.84, - 407.13, - 254.08, - 403.92, - 252.76, - 402.79, - 250.69, - 402.32, - 249.75, - 401.19, - 250.5, - 389.03, - 254.18, - 384.51, - 262.56, - 384.32 - ], - "groupId": 1, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "97e8bcc305b69af97b1a51c102c22a03887410bd", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 348.62, - 395.91, - 367.76, - 395.34, - 367, - 384.32, - 364.36, - 378, - 349.09, - 377.81, - 346.55, - 385.54, - 346.55, - 395.82 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "15e2bd2a9bf66c4df00df9fbe6fd6db43abc56", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 325.25, - 402.32, - 321.1, - 410.99, - 321, - 424.47, - 329.21, - 424.75, - 329.49, - 423.06, - 344.57, - 423.15, - 344.85, - 424.85, - 349.94, - 424.38, - 349.09, - 409.2, - 344.57, - 401.47 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "98c3f3a3fdeb809c7a8de125447acce21abda84f", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 114.81, - 432.5, - 149.32, - 430.8, - 169.65, - 442.24, - 187.65, - 446.05, - 192.94, - 453.25, - 192.31, - 462.14, - 189.77, - 467.44, - 183.84, - 470.83, - 177.48, - 472.52, - 169.65, - 480.57, - 163.93, - 481.62, - 160.54, - 477.18, - 159.27, - 472.73, - 159.91, - 468.28, - 159.49, - 458.76, - 156.94, - 450.71, - 136.62, - 437.37, - 119.04, - 436.52 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "de78e5e22b397426228bed63c15ad2f41bfe653", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 120.52, - 437.37, - 135.77, - 437.79, - 156.31, - 450.5, - 158.85, - 459.39, - 159.27, - 468.71, - 158.21, - 474.21, - 152.92, - 480.78, - 147.84, - 483.74, - 142.54, - 484.17, - 139.37, - 482.05, - 140.43, - 477.6, - 144.87, - 475.91, - 146.78, - 471.25, - 144.03, - 457.27 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "7adde574ed24f845d700b0c8371122bfe667751f", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 81.46, - 437.16, - 94.38, - 435.04, - 110.9, - 433.56, - 117.67, - 434.83, - 133.77, - 448.8, - 144.99, - 457.27, - 147.32, - 471.67, - 145.62, - 475.91, - 141.6, - 477.6, - 136.31, - 485.22, - 131.65, - 487.98, - 126.78, - 488.61, - 122.97, - 472.73, - 118.52, - 464.26, - 110.9, - 455.37, - 103.06, - 441.18, - 99.89, - 438.64 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "4a47c34904d5e6bafdab32f9896c4013b1ddd153", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 684.8, - 420.93, - 683.1, - 416.3, - 634.11, - 414.48, - 626.68, - 419.72, - 622.9, - 424.35, - 609.62, - 425.69, - 604.63, - 427.76, - 600.73, - 434.34, - 600.48, - 440.19, - 600.97, - 440.92, - 604.02, - 442.01, - 604.99, - 445.67, - 607.18, - 447.99, - 610.96, - 450.18, - 618.64, - 450.91, - 621.2, - 448.72, - 622.54, - 446.16, - 626.8, - 446.16, - 626.92, - 440.67, - 629.6, - 435.31, - 633.75, - 432.39, - 646.79, - 430.32, - 664.09, - 420.81, - 685.05, - 422.4 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "eb882a5e0012c77a1557c47bf386b21b6f6848", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 674.69, - 421.91, - 664.82, - 421.3, - 646.66, - 430.56, - 634.24, - 432.63, - 629.85, - 435.68, - 627.29, - 440.55, - 627.05, - 444.94, - 628.14, - 447.13, - 628.63, - 447.86, - 631.68, - 448.35, - 633.38, - 451.4, - 634.48, - 452.25, - 634.72, - 446.89, - 636.43, - 437.99, - 645.57, - 434.34, - 656.53, - 431.05 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "56b1a09c67ce96a1719afe9f8c2b50b3dd08cd1c", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 729.77, - 442.26, - 729.89, - 436.04, - 726.11, - 425.93, - 719.9, - 423.86, - 676.27, - 422.93, - 670.06, - 424.22, - 656.78, - 431.41, - 641.67, - 435.68, - 636.92, - 438.12, - 635.09, - 447.25, - 634.97, - 452.86, - 635.7, - 453.71, - 640.33, - 455.17, - 643.25, - 457.86, - 649.59, - 458.22, - 652.27, - 457.86, - 654.95, - 454.32, - 656.29, - 453.47, - 664.45, - 453.96, - 667.62, - 458.71, - 668.72, - 458.95, - 671.64, - 458.95, - 673.96, - 458.34, - 676.52, - 456.76, - 678.35, - 454.32, - 686.75, - 454.93, - 689.92, - 459.56, - 691.51, - 460.78, - 696.87, - 461.27, - 699.67, - 460.29, - 702.84, - 456.51, - 705.27, - 455.91, - 706.86, - 452.37, - 708.69, - 450.79, - 722.21, - 445.18, - 725.87, - 445.43 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "12a358abc908ad69e8b599ab359e12ecfe1047", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 96, - "points": [ - 703, - 462.81, - 703, - 464.81, - 712, - 472.81, - 712, - 474.81, - 724, - 474.81, - 729, - 471.81, - 741.61, - 472.86, - 745.32, - 476.75, - 753.29, - 476.57, - 756.25, - 473.97, - 770, - 473.81, - 780, - 478.81, - 784, - 478.81, - 792, - 474.81, - 802, - 474.81, - 806, - 478.81, - 812, - 479.81, - 817, - 477.81, - 820, - 473.81, - 832.61, - 472.49, - 834, - 468.81, - 833, - 453.81, - 827, - 448.81, - 805, - 437.81, - 783, - 434.81, - 750, - 434.81, - 739, - 437.81, - 726, - 445.81, - 722, - 445.81, - 709, - 450.81, - 707, - 452.81, - 705.11, - 457.11 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "8f8ef909a683eaf9852b4d9784ec63b471c58d16", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 98, - "points": [ - 1023.86, - 432.09, - 1019, - 434, - 1008, - 440, - 1001, - 447, - 960, - 450, - 952, - 453, - 945, - 460, - 940, - 472, - 942, - 496, - 945, - 500, - 948, - 500, - 954, - 510, - 958, - 514, - 980, - 515, - 992, - 504, - 999, - 506, - 1006, - 513, - 1009, - 514, - 1016.82, - 516.78, - 1023.86, - 515.86 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "d9975dc56b159b1690fbdea7b04dd35f2ba69366", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 98, - "points": [ - 6, - 447, - 0, - 459, - 0, - 528, - 2, - 531, - 12, - 530, - 20, - 536, - 25, - 536, - 33, - 530, - 61, - 530, - 77, - 528, - 86, - 534, - 94, - 535, - 99, - 532, - 100, - 525, - 102, - 522, - 109.39, - 521.38, - 111.09, - 529.47, - 122.6, - 528.2, - 126.44, - 491.97, - 122, - 474, - 118, - 465, - 110, - 456, - 103, - 442, - 99, - 439, - 47, - 438, - 16, - 442 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "76d42d45e8b0c11a055dff75a405b515bb1dd53f", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "bbox", - "classId": 72275, - "probability": 100, - "points": { - "x1": 240.68, - "x2": 304.61, - "y1": 378.93, - "y2": 410.11 - }, - "groupId": 0, - "pointLabels": { - "0": "Top Left", - "4": "Bottom Right" - }, - "locked": false, - "visible": true, - "attributes": [ - { - "id": 117848, - "groupId": 28231, - "name": "no", - "groupName": "swedish" - }, - { - "id": 117850, - "groupId": 28232, - "name": "4", - "groupName": "Num doors" - } - ], - "trackingId": "ac43151b5ac2d511beac8d2ec15695f421b93882", - "error": null, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - }, - { - "type": "template", - "classId": 72276, - "probability": 100, - "points": [ - { - "id": 1, - "x": 590.36328125, - "y": 505.471431864795 - }, - { - "id": 2, - "x": 590.2529541686341, - "y": 504.29565523299704 - }, - { - "id": 3, - "x": 590.0863828554258, - "y": 502.0855402722193 - }, - { - "id": 4, - "x": 589.8926669948704, - "y": 500.1575188822054 - }, - { - "id": 5, - "x": 588.2789742606027, - "y": 491.4069519042969 - }, - { - "id": 6, - "x": 591.6578771570227, - "y": 498.7841862403542 - }, - { - "id": 7, - "x": 592.6675015963041, - "y": 497.5725781649412 - }, - { - "id": 8, - "x": 593.4538138253348, - "y": 495.05589353721325 - }, - { - "id": 9, - "x": 591.9352490770948, - "y": 502.2054028345276 - }, - { - "id": 10, - "x": 591.4315175486134, - "y": 504.8054433249257 - }, - { - "id": 11, - "x": 591.0675032060225, - "y": 506.48433274969244 - }, - { - "id": 12, - "x": 593.6178112658826, - "y": 501.4214392039917 - }, - { - "id": 13, - "x": 592.6682424021291, - "y": 504.65690054240156 - }, - { - "id": 14, - "x": 591.8309557568896, - "y": 507.1707458496094 - }, - { - "id": 15, - "x": 594.685306758671, - "y": 499.50420568423283 - }, - { - "id": 16, - "x": 594.4346668956044, - "y": 503.3523914672602 - }, - { - "id": 17, - "x": 593.4855715573489, - "y": 505.4433191217528 - }, - { - "id": 18, - "x": 592.9555204622038, - "y": 507.0652772868338 - }, - { - "id": 19, - "x": 589.5701713142814, - "y": 496.6512277677259 - }, - { - "id": 20, - "x": 590.8887191604782, - "y": 499.291411604618 - }, - { - "id": 21, - "x": 591.1992693890583, - "y": 501.8345208353304 - }, - { - "id": 22, - "x": 591.0341186523438, - "y": 501.9896778816582 - } - ], - "connections": [ - { - "id": 1, - "from": 5, - "to": 4 - }, - { - "id": 2, - "from": 3, - "to": 4 - }, - { - "id": 3, - "from": 3, - "to": 2 - }, - { - "id": 4, - "from": 2, - "to": 1 - }, - { - "id": 5, - "from": 5, - "to": 6 - }, - { - "id": 6, - "from": 6, - "to": 9 - }, - { - "id": 7, - "from": 9, - "to": 10 - }, - { - "id": 8, - "from": 10, - "to": 11 - }, - { - "id": 9, - "from": 5, - "to": 7 - }, - { - "id": 10, - "from": 7, - "to": 12 - }, - { - "id": 11, - "from": 12, - "to": 13 - }, - { - "id": 12, - "from": 13, - "to": 14 - }, - { - "id": 13, - "from": 5, - "to": 8 - }, - { - "id": 14, - "from": 8, - "to": 15 - }, - { - "id": 15, - "from": 15, - "to": 16 - }, - { - "id": 16, - "from": 16, - "to": 17 - }, - { - "id": 17, - "from": 17, - "to": 18 - }, - { - "id": 18, - "from": 5, - "to": 19 - }, - { - "id": 19, - "from": 19, - "to": 20 - }, - { - "id": 20, - "from": 20, - "to": 21 - }, - { - "id": 21, - "from": 21, - "to": 22 - } - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "templateName": "HandPose", - "trackingId": "2c89e809614523cf56c9aeab932e90b87aaf5e4f", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "template", - "classId": 72276, - "probability": 100, - "points": [ - { - "id": 1, - "x": 332.9866027832032, - "y": 526.2959883676228 - }, - { - "id": 2, - "x": 332.8439004919032, - "y": 527.5132367654812 - }, - { - "id": 3, - "x": 334.35612353649776, - "y": 527.3324179308058 - }, - { - "id": 4, - "x": 336.2640990372543, - "y": 524.0976645502819 - }, - { - "id": 5, - "x": 337.51601736886164, - "y": 516.1050720214844 - }, - { - "id": 6, - "x": 339.060296362573, - "y": 524.7754271337591 - }, - { - "id": 7, - "x": 341.64884537916925, - "y": 526.5125154522543 - }, - { - "id": 8, - "x": 344.0771833147321, - "y": 527.3880219566797 - }, - { - "id": 9, - "x": 335.88342117477254, - "y": 527.9910814406194 - }, - { - "id": 10, - "x": 334.6968087835627, - "y": 529.0659044885928 - }, - { - "id": 11, - "x": 333.86405081277377, - "y": 527.8757251825314 - }, - { - "id": 12, - "x": 339.9883503337483, - "y": 529.320022177355 - }, - { - "id": 13, - "x": 338.46802612975404, - "y": 530.370269900207 - }, - { - "id": 14, - "x": 337.1430909712236, - "y": 530.7341613769531 - }, - { - "id": 15, - "x": 341.9785882300073, - "y": 531.0127476105173 - }, - { - "id": 16, - "x": 340.85258785708925, - "y": 532.1869901255352 - }, - { - "id": 17, - "x": 339.1688606346047, - "y": 532.8862634202454 - }, - { - "id": 18, - "x": 339.0958418793731, - "y": 532.8511886128618 - }, - { - "id": 19, - "x": 342.74045026171336, - "y": 523.5337313474565 - }, - { - "id": 20, - "x": 343.0975823874003, - "y": 525.8059083903495 - }, - { - "id": 21, - "x": 341.95265642103254, - "y": 527.6336142573132 - }, - { - "id": 22, - "x": 340.4774169921875, - "y": 527.7661633949826 - } - ], - "connections": [ - { - "id": 1, - "from": 5, - "to": 4 - }, - { - "id": 2, - "from": 3, - "to": 4 - }, - { - "id": 3, - "from": 3, - "to": 2 - }, - { - "id": 4, - "from": 2, - "to": 1 - }, - { - "id": 5, - "from": 5, - "to": 6 - }, - { - "id": 6, - "from": 6, - "to": 9 - }, - { - "id": 7, - "from": 9, - "to": 10 - }, - { - "id": 8, - "from": 10, - "to": 11 - }, - { - "id": 9, - "from": 5, - "to": 7 - }, - { - "id": 10, - "from": 7, - "to": 12 - }, - { - "id": 11, - "from": 12, - "to": 13 - }, - { - "id": 12, - "from": 13, - "to": 14 - }, - { - "id": 13, - "from": 5, - "to": 8 - }, - { - "id": 14, - "from": 8, - "to": 15 - }, - { - "id": 15, - "from": 15, - "to": 16 - }, - { - "id": 16, - "from": 16, - "to": 17 - }, - { - "id": 17, - "from": 17, - "to": 18 - }, - { - "id": 18, - "from": 5, - "to": 19 - }, - { - "id": 19, - "from": 19, - "to": 20 - }, - { - "id": 20, - "from": 20, - "to": 21 - }, - { - "id": 21, - "from": 21, - "to": 22 - } - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "templateName": "HandPose", - "trackingId": "bab62dc810b0cee390f8d5fb5fa62fade3c8da7", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "template", - "classId": 72276, - "probability": 100, - "points": [ - { - "id": 1, - "x": 500.7473449707031, - "y": 512.2212813363728 - }, - { - "id": 2, - "x": 499.83990268916875, - "y": 511.0267255350125 - }, - { - "id": 3, - "x": 499.35212573376333, - "y": 508.78712984486833 - }, - { - "id": 4, - "x": 499.49539176186363, - "y": 505.6112143549695 - }, - { - "id": 5, - "x": 505.1166338239397, - "y": 498.2973327636719 - }, - { - "id": 6, - "x": 501.5269101321042, - "y": 506.7595579931341 - }, - { - "id": 7, - "x": 503.99778336745044, - "y": 506.673098948348 - }, - { - "id": 8, - "x": 506.9555402483259, - "y": 505.9015717613673 - }, - { - "id": 9, - "x": 501.35003494430373, - "y": 510.62224599140063 - }, - { - "id": 10, - "x": 501.986939398797, - "y": 512.5206164026553 - }, - { - "id": 11, - "x": 503.15418142800803, - "y": 512.9774707880001 - }, - { - "id": 12, - "x": 503.6314472575764, - "y": 510.3629298921987 - }, - { - "id": 13, - "x": 503.9346398992853, - "y": 513.4720155056757 - }, - { - "id": 14, - "x": 506.3155763227861, - "y": 514.4830017089844 - }, - { - "id": 15, - "x": 506.32755673586666, - "y": 510.11449321598604 - }, - { - "id": 16, - "x": 506.78978268130794, - "y": 513.0534452036602 - }, - { - "id": 17, - "x": 508.6354744041359, - "y": 513.6350427171204 - }, - { - "id": 18, - "x": 508.56245564890435, - "y": 512.0705489644243 - }, - { - "id": 19, - "x": 509.736452458979, - "y": 503.5178622068315 - }, - { - "id": 20, - "x": 510.1524224752909, - "y": 508.84887714034943 - }, - { - "id": 21, - "x": 509.8898512452513, - "y": 511.676521972157 - }, - { - "id": 22, - "x": 509.7675476074219, - "y": 511.8091321449826 - } - ], - "connections": [ - { - "id": 1, - "from": 5, - "to": 4 - }, - { - "id": 2, - "from": 3, - "to": 4 - }, - { - "id": 3, - "from": 3, - "to": 2 - }, - { - "id": 4, - "from": 2, - "to": 1 - }, - { - "id": 5, - "from": 5, - "to": 6 - }, - { - "id": 6, - "from": 6, - "to": 9 - }, - { - "id": 7, - "from": 9, - "to": 10 - }, - { - "id": 8, - "from": 10, - "to": 11 - }, - { - "id": 9, - "from": 5, - "to": 7 - }, - { - "id": 10, - "from": 7, - "to": 12 - }, - { - "id": 11, - "from": 12, - "to": 13 - }, - { - "id": 12, - "from": 13, - "to": 14 - }, - { - "id": 13, - "from": 5, - "to": 8 - }, - { - "id": 14, - "from": 8, - "to": 15 - }, - { - "id": 15, - "from": 15, - "to": 16 - }, - { - "id": 16, - "from": 16, - "to": 17 - }, - { - "id": 17, - "from": 17, - "to": 18 - }, - { - "id": 18, - "from": 5, - "to": 19 - }, - { - "id": 19, - "from": 19, - "to": 20 - }, - { - "id": 20, - "from": 20, - "to": 21 - }, - { - "id": 21, - "from": 21, - "to": 22 - } - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "templateName": "HandPose", - "trackingId": "f8f542a9e9da918d5d5cb8eed9052713302089", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "template", - "classId": 72276, - "probability": 100, - "points": [ - { - "id": 1, - "x": 460.2714192848242, - "y": 486.08071083487926 - }, - { - "id": 2, - "x": 454.92882596998356, - "y": 481.9066804669699 - }, - { - "id": 3, - "x": 461.0707178220127, - "y": 481.61528130084 - }, - { - "id": 4, - "x": 462.32680898178, - "y": 482.46856689453125 - }, - { - "id": 5, - "x": 444.8684189242054, - "y": 483.808782080494 - }, - { - "id": 6, - "x": 455.8683091235324, - "y": 497.2664014146353 - }, - { - "id": 7, - "x": 439.86159351357213, - "y": 498.91779556832523 - }, - { - "id": 8, - "x": 432.98627658437374, - "y": 519.4614616257791 - }, - { - "id": 9, - "x": 415.8799309258186, - "y": 515.9119205914317 - }, - { - "id": 10, - "x": 467.5532979208077, - "y": 499.0862192385027 - }, - { - "id": 11, - "x": 479.28433580441475, - "y": 514.1935318132136 - }, - { - "id": 12, - "x": 498.51239013671875, - "y": 512.030284394326 - }, - { - "id": 13, - "x": 454.8632612058889, - "y": 546.5478157765722 - }, - { - "id": 14, - "x": 444.0484270284733, - "y": 546.0017547475499 - }, - { - "id": 15, - "x": 464.16791732413037, - "y": 546.2800095783913 - }, - { - "id": 16, - "x": 468.63255127661785, - "y": 573.6905686937465 - }, - { - "id": 17, - "x": 457.1555372435924, - "y": 577.0907707675425 - }, - { - "id": 18, - "x": 432.2792663574219, - "y": 587.0443088500142 - }, - { - "id": 19, - "x": 429.91821938954894, - "y": 606.0040783618011 - }, - { - "id": 20, - "x": 463.69909188680566, - "y": 602.9990721708784 - }, - { - "id": 21, - "x": 484.317011118421, - "y": 607.0152893066406 - } - ], - "connections": [ - { - "id": 1, - "from": 1, - "to": 6 - }, - { - "id": 2, - "from": 6, - "to": 10 - }, - { - "id": 3, - "from": 10, - "to": 11 - }, - { - "id": 4, - "from": 11, - "to": 12 - }, - { - "id": 5, - "from": 7, - "to": 8 - }, - { - "id": 6, - "from": 8, - "to": 9 - }, - { - "id": 7, - "from": 14, - "to": 7 - }, - { - "id": 8, - "from": 14, - "to": 13 - }, - { - "id": 9, - "from": 13, - "to": 15 - }, - { - "id": 10, - "from": 15, - "to": 10 - }, - { - "id": 11, - "from": 7, - "to": 6 - }, - { - "id": 12, - "from": 14, - "to": 16 - }, - { - "id": 13, - "from": 15, - "to": 17 - }, - { - "id": 14, - "from": 16, - "to": 20 - }, - { - "id": 15, - "from": 20, - "to": 21 - }, - { - "id": 16, - "from": 17, - "to": 18 - }, - { - "id": 17, - "from": 18, - "to": 19 - }, - { - "id": 18, - "from": 5, - "to": 2 - }, - { - "id": 19, - "from": 2, - "to": 1 - }, - { - "id": 20, - "from": 1, - "to": 1 - }, - { - "id": 21, - "from": 3, - "to": 1 - }, - { - "id": 22, - "from": 3, - "to": 4 - } - ], - "groupId": 0, - "pointLabels": { - "0": "Nose" - }, - "locked": false, - "visible": false, - "attributes": [], - "templateName": "HandPose", - "trackingId": "4fd95b7d6d95b7b84750e65aa89c70b9c86eb3b8", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "template", - "classId": 72276, - "probability": 100, - "points": [ - { - "id": 1, - "x": 569.4099335784475, - "y": 411.3099511426366 - }, - { - "id": 2, - "x": 565.2798621579027, - "y": 406.3627038525488 - }, - { - "id": 3, - "x": 567.377754831435, - "y": 405.3775634765625 - }, - { - "id": 4, - "x": 562.1341137290701, - "y": 404.67809199715805 - }, - { - "id": 5, - "x": 554.7715578497942, - "y": 408.0821593507321 - }, - { - "id": 6, - "x": 543.3504267346603, - "y": 422.3509408794715 - }, - { - "id": 7, - "x": 530.5325718803996, - "y": 432.4575436529285 - }, - { - "id": 8, - "x": 513.1264329109782, - "y": 468.5712030528786 - }, - { - "id": 9, - "x": 505.0783099316068, - "y": 498.26488325838557 - }, - { - "id": 10, - "x": 564.5019009957019, - "y": 431.59166109918834 - }, - { - "id": 11, - "x": 572.9879904477306, - "y": 466.0899617391194 - }, - { - "id": 12, - "x": 588.320701407949, - "y": 491.39197319472385 - }, - { - "id": 13, - "x": 547.1874731524312, - "y": 499.0241945917735 - }, - { - "id": 14, - "x": 536.2172232162276, - "y": 499.38451563669537 - }, - { - "id": 15, - "x": 558.2200212079587, - "y": 496.61095606638287 - }, - { - "id": 16, - "x": 565.8375729727319, - "y": 546.3956734358432 - }, - { - "id": 17, - "x": 545.4810409910515, - "y": 549.0779244124057 - }, - { - "id": 18, - "x": 502.6168107549702, - "y": 573.1785073042392 - }, - { - "id": 19, - "x": 506.98697907641065, - "y": 599.8044128417969 - }, - { - "id": 20, - "x": 555.6301612734296, - "y": 594.6135561518564 - }, - { - "id": 21, - "x": 585.93212890625, - "y": 602.2106018066406 - } - ], - "connections": [ - { - "id": 1, - "from": 1, - "to": 6 - }, - { - "id": 2, - "from": 6, - "to": 10 - }, - { - "id": 3, - "from": 10, - "to": 11 - }, - { - "id": 4, - "from": 11, - "to": 12 - }, - { - "id": 5, - "from": 7, - "to": 8 - }, - { - "id": 6, - "from": 8, - "to": 9 - }, - { - "id": 7, - "from": 14, - "to": 7 - }, - { - "id": 8, - "from": 14, - "to": 13 - }, - { - "id": 9, - "from": 13, - "to": 15 - }, - { - "id": 10, - "from": 15, - "to": 10 - }, - { - "id": 11, - "from": 7, - "to": 6 - }, - { - "id": 12, - "from": 14, - "to": 16 - }, - { - "id": 13, - "from": 15, - "to": 17 - }, - { - "id": 14, - "from": 16, - "to": 20 - }, - { - "id": 15, - "from": 20, - "to": 21 - }, - { - "id": 16, - "from": 17, - "to": 18 - }, - { - "id": 17, - "from": 18, - "to": 19 - }, - { - "id": 18, - "from": 5, - "to": 2 - }, - { - "id": 19, - "from": 2, - "to": 1 - }, - { - "id": 20, - "from": 1, - "to": 1 - }, - { - "id": 21, - "from": 3, - "to": 1 - }, - { - "id": 22, - "from": 3, - "to": 4 - } - ], - "groupId": 0, - "pointLabels": { - "0": "Nose" - }, - "locked": false, - "visible": false, - "attributes": [], - "templateName": "HandPose", - "trackingId": "8894b2a1727f62631d26e885a5aaf9bc2ac2a578", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "template", - "classId": 72276, - "probability": 100, - "points": [ - { - "id": 1, - "x": 388.9594774956746, - "y": 424.3453820508397 - }, - { - "id": 2, - "x": 383.78257983006284, - "y": 420.2971520947363 - }, - { - "id": 3, - "x": 387.1454388819895, - "y": 419.5367736816406 - }, - { - "id": 4, - "x": 382.7214935156717, - "y": 418.8373022022362 - }, - { - "id": 5, - "x": 369.81775320578504, - "y": 421.3423522218259 - }, - { - "id": 6, - "x": 368.5353785473912, - "y": 441.4006845318153 - }, - { - "id": 7, - "x": 353.1593986570741, - "y": 443.28386811581913 - }, - { - "id": 8, - "x": 340.9145244608405, - "y": 484.88446599233174 - }, - { - "id": 9, - "x": 337.471170384727, - "y": 516.0647184634637 - }, - { - "id": 10, - "x": 380.0734310110131, - "y": 441.19236910700084 - }, - { - "id": 11, - "x": 392.6590966976267, - "y": 481.59771320396317 - }, - { - "id": 12, - "x": 411.22125244140625, - "y": 510.38843315566135 - }, - { - "id": 13, - "x": 368.27931488725477, - "y": 514.5319460566172 - }, - { - "id": 14, - "x": 361.465192188568, - "y": 515.6977785761485 - }, - { - "id": 15, - "x": 378.7043428557912, - "y": 512.1187075312266 - }, - { - "id": 16, - "x": 393.26020935016874, - "y": 556.5333687483432 - }, - { - "id": 17, - "x": 344.09536524138383, - "y": 562.7657295881869 - }, - { - "id": 18, - "x": 321.86363692684523, - "y": 598.4685463667392 - }, - { - "id": 19, - "x": 345.55514438756916, - "y": 610.3072814941406 - }, - { - "id": 20, - "x": 402.05302902711884, - "y": 603.0690004877939 - }, - { - "id": 21, - "x": 426.8170225465453, - "y": 607.0261535644531 - } - ], - "connections": [ - { - "id": 1, - "from": 1, - "to": 6 - }, - { - "id": 2, - "from": 6, - "to": 10 - }, - { - "id": 3, - "from": 10, - "to": 11 - }, - { - "id": 4, - "from": 11, - "to": 12 - }, - { - "id": 5, - "from": 7, - "to": 8 - }, - { - "id": 6, - "from": 8, - "to": 9 - }, - { - "id": 7, - "from": 14, - "to": 7 - }, - { - "id": 8, - "from": 14, - "to": 13 - }, - { - "id": 9, - "from": 13, - "to": 15 - }, - { - "id": 10, - "from": 15, - "to": 10 - }, - { - "id": 11, - "from": 7, - "to": 6 - }, - { - "id": 12, - "from": 14, - "to": 16 - }, - { - "id": 13, - "from": 15, - "to": 17 - }, - { - "id": 14, - "from": 16, - "to": 20 - }, - { - "id": 15, - "from": 20, - "to": 21 - }, - { - "id": 16, - "from": 17, - "to": 18 - }, - { - "id": 17, - "from": 18, - "to": 19 - }, - { - "id": 18, - "from": 5, - "to": 2 - }, - { - "id": 19, - "from": 2, - "to": 1 - }, - { - "id": 20, - "from": 1, - "to": 1 - }, - { - "id": 21, - "from": 3, - "to": 1 - }, - { - "id": 22, - "from": 3, - "to": 4 - } - ], - "groupId": 0, - "pointLabels": { - "0": "Nose" - }, - "locked": false, - "visible": false, - "attributes": [], - "templateName": "HandPose", - "trackingId": "2fe1f0c6c4af879955d6f19cfcf113a6b929b73", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "polygon", - "classId": 72276, - "probability": 100, - "points": [ - 496.93, - 506.95, - 500.11, - 499.6, - 499.38, - 494.21, - 500.85, - 490.53, - 502.81, - 490.04, - 503.79, - 488.32, - 505.02, - 480.97, - 507.22, - 477.3, - 510.16, - 466.51, - 520.21, - 451.32, - 522.42, - 446.41, - 524.38, - 435.63, - 541.78, - 412.84, - 543, - 408.92, - 541.78, - 405.73, - 541.78, - 398.13, - 542.51, - 394.95, - 543.74, - 392.74, - 546.19, - 389.8, - 548.4, - 388.82, - 556.97, - 388.82, - 563.35, - 391.27, - 565.06, - 393.23, - 566.29, - 396.42, - 567.76, - 405.24, - 569.23, - 409.41, - 569.23, - 412.59, - 568.25, - 414.55, - 568, - 419.45, - 565.8, - 422.4, - 562.37, - 423.62, - 561.63, - 425.09, - 561.63, - 427.05, - 566.04, - 429.5, - 568, - 433.42, - 569.72, - 445.68, - 594.96, - 498.62, - 594.96, - 502.78, - 593.98, - 505.48, - 591.53, - 508.18, - 589.82, - 508.42, - 588.35, - 505.97, - 586.88, - 500.58, - 585.4, - 499.6, - 582.46, - 499.35, - 568.98, - 481.71, - 571.19, - 508.18, - 569.96, - 510.63, - 567.76, - 510.87, - 572.66, - 595.43, - 574.87, - 597.63, - 580.01, - 598.61, - 586.39, - 598.61, - 588.84, - 599.35, - 589.33, - 601.31, - 587.86, - 604.01, - 586.88, - 604.5, - 553.3, - 604.99, - 551.09, - 601.8, - 551.09, - 592.49, - 552.81, - 589.55, - 548.15, - 554.25, - 530.51, - 572.39, - 511.88, - 586.85, - 509.67, - 587.09, - 508.69, - 593.22, - 508.69, - 596.9, - 509.92, - 599.84, - 509.67, - 601.8, - 506.49, - 602.04, - 502.57, - 598.86, - 499.87, - 594.45, - 496.93, - 584.64, - 492.52, - 581.21, - 489.58, - 576.56, - 489.82, - 571.41, - 491.05, - 570.18, - 498.15, - 569.45, - 509.67, - 565.04, - 525.11, - 547.64, - 532.22, - 546.16, - 531.98, - 541.26, - 537.12, - 538.57, - 530.51, - 510.14, - 526.34, - 513.32, - 522.42, - 489.55, - 521.19, - 477.05, - 517.76, - 485.38, - 515.31, - 489.06, - 514.57, - 493.72, - 512.61, - 495.68, - 511.39, - 498.86, - 509.43, - 506.71, - 508.94, - 514.55, - 505.51, - 515.28, - 501.83, - 514.55, - 498.15, - 510.87, - 497.91, - 507.93 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "bf7e1885326a2aac18619c30d310c21e8fb89e93", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "polygon", - "classId": 72276, - "probability": 100, - "points": [ - 418.36, - 510.31, - 423.02, - 513.25, - 429.15, - 514.48, - 433.07, - 513.25, - 441.65, - 499.04, - 448.76, - 495.36, - 449.74, - 490.7, - 446.55, - 487.52, - 444.83, - 484.33, - 446.06, - 471.59, - 448.27, - 468.89, - 453.66, - 467.18, - 459.29, - 468.16, - 464.69, - 470.61, - 465.42, - 471.59, - 466.16, - 483.6, - 464.69, - 488.25, - 464.69, - 493.4, - 467.87, - 497.57, - 482.58, - 507.37, - 486.5, - 509.33, - 500.96, - 509.09, - 500.22, - 516.93, - 499.24, - 519.13, - 481.11, - 520.61, - 475.47, - 517.42, - 472.28, - 517.17, - 471.55, - 518.4, - 470.08, - 544.62, - 470.81, - 557.12, - 474.49, - 576, - 473.02, - 599.52, - 482.09, - 602.46, - 488.21, - 605.65, - 488.46, - 608.35, - 487.97, - 609.08, - 464.2, - 610.06, - 463.46, - 603.44, - 461.74, - 600.26, - 461.74, - 597.56, - 463.95, - 595.11, - 463.22, - 591.68, - 463.95, - 580.9, - 452.92, - 587.51, - 442.87, - 590.21, - 443.85, - 591.93, - 443.36, - 592.66, - 441.89, - 591.93, - 439.93, - 592.42, - 439.2, - 593.4, - 438.95, - 597.07, - 435.52, - 601.48, - 434.3, - 608.35, - 433.07, - 609.57, - 431.35, - 603.44, - 429.64, - 602.95, - 427.92, - 584.33, - 437.48, - 582.61, - 456.35, - 572.81, - 454.88, - 567.17, - 453.17, - 563.74, - 453.41, - 559.82, - 450.96, - 556.63, - 447.53, - 554.43, - 445.81, - 551.24, - 442.14, - 550.02, - 438.95, - 522.81, - 423.27, - 523.79, - 417.63, - 521.83, - 413.95, - 516.93, - 413.71, - 515.21 - ], - "groupId": 0, - "pointLabels": { - "0": "Left Hand", - "21": "Right Hand" - }, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "932bda5b59db89dd68602306c70d8da62e0afdc", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - }, - { - "type": "polygon", - "classId": 72276, - "probability": 100, - "points": [ - 380.35, - 435.63, - 378.64, - 439.31, - 395.79, - 464.55, - 396.28, - 478.03, - 394.57, - 481.22, - 407.56, - 499.11, - 408.05, - 501.07, - 410.74, - 502.05, - 410.99, - 504.99, - 415.15, - 507.93, - 415.15, - 509.4, - 410.25, - 513.32, - 407.8, - 517, - 399.22, - 516.75, - 390.4, - 510.87, - 389.18, - 512.34, - 397.51, - 539.06, - 397.75, - 559.89, - 400.2, - 568.47, - 409.76, - 593.96, - 417.12, - 602.78, - 422.51, - 604.25, - 428.63, - 603.76, - 429.61, - 606.21, - 428.63, - 608.42, - 402.65, - 614.3, - 396.53, - 611.85, - 395.79, - 609.4, - 397.51, - 602.04, - 395.55, - 599.35, - 394.57, - 599.35, - 383.29, - 574.84, - 380.6, - 555.97, - 369.32, - 542, - 350.45, - 561.61, - 334.03, - 598.86, - 335.01, - 600.82, - 340.65, - 606.21, - 343.34, - 607.44, - 348.49, - 607.93, - 349.47, - 608.66, - 349.72, - 610.62, - 348.25, - 612.09, - 346.78, - 612.58, - 319.82, - 610.62, - 315.89, - 608.17, - 318.1, - 599.84, - 319.08, - 590.77, - 329.13, - 566.02, - 339.42, - 549.11, - 342.61, - 541.51, - 341.38, - 529.74, - 339.18, - 533.91, - 333.79, - 524.6, - 333.3, - 521.9, - 325.94, - 519.45, - 339.42, - 477.54, - 339.18, - 467.98, - 336.48, - 463.82, - 359.52, - 408.92, - 366.38, - 404.5, - 379.62, - 404.5, - 380.84, - 404.99, - 385.5, - 411.12, - 387.7, - 416.27, - 387.7, - 420.68, - 389.42, - 424.6, - 388.44, - 428.03, - 386.97, - 429.75, - 386.23, - 434.65 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [], - "trackingId": "b4a35bf808984de199195734dfbb73b1cb5c8b5", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - } - ], - "tags": [], - "comments": [ - { - "x": 621.41, - "y": 631.6, - "resolved": true, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "correspondence": [ - { - "text": "Bordyuri mi mas@ petqa lini parking class-i mej myus mas@ Terrian class-i", - "email": "hovnatan@superannotate.com" - } - ] - }, - { - "x": 521.41, - "y": 531.6, - "resolved": false, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "correspondence": [ - { - "text": "dd", - "email": "hovnatan@superannotate.com" - } - ] - } - ] -} diff --git a/tests/data_set/sample_project_vector_invalid/example_image_2.jpg b/tests/data_set/sample_project_vector_invalid/example_image_2.jpg deleted file mode 100644 index 3ed3019..0000000 Binary files a/tests/data_set/sample_project_vector_invalid/example_image_2.jpg and /dev/null differ diff --git a/tests/data_set/sample_project_vector_invalid/example_image_2.jpg___fuse.png b/tests/data_set/sample_project_vector_invalid/example_image_2.jpg___fuse.png deleted file mode 100644 index d54f3bd..0000000 Binary files a/tests/data_set/sample_project_vector_invalid/example_image_2.jpg___fuse.png and /dev/null differ diff --git a/tests/data_set/sample_project_vector_invalid/example_image_2.jpg___objects.json b/tests/data_set/sample_project_vector_invalid/example_image_2.jpg___objects.json deleted file mode 100644 index 5fc05b6..0000000 --- a/tests/data_set/sample_project_vector_invalid/example_image_2.jpg___objects.json +++ /dev/null @@ -1,1950 +0,0 @@ -{ - "metadata": { - "name": "example_image_2.jpg", - "width": 1885, - "height": 1060, - "status": "Completed", - "pinned": false, - "isPredicted": null, - "projectId": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "type": "polygon", - "classId": 72274, - "probability": 95, - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "9ee0c81d360b5aaeeb083b65f7863d52be7d908", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 96, - "points": [ - 1546, - 1048, - 1542, - 1045, - 1531, - 1043, - 1527, - 1039, - 1526, - 1034, - 1522, - 1033, - 1519, - 1028, - 1513, - 1025, - 1500, - 1022, - 1470, - 1022, - 1459, - 1024, - 1435, - 1024, - 1405, - 1028, - 1400, - 1030, - 1399, - 1033, - 1394, - 1037, - 1393, - 1041, - 1386, - 1047, - 1374, - 1047, - 1368, - 1050, - 1369, - 1055, - 1371, - 1057, - 1382, - 1059, - 1513, - 1059, - 1543, - 1057, - 1546, - 1055 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "fe751485fd9a28b9a39624c5110606f8a13eb497", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 96, - "points": [ - 1370, - 798, - 1367, - 796, - 1361, - 796, - 1349, - 781, - 1341, - 778, - 1304, - 779, - 1282, - 782, - 1277, - 790, - 1276, - 795, - 1273, - 798, - 1264, - 801, - 1266, - 811, - 1264, - 817, - 1265, - 859, - 1267, - 861, - 1272, - 861, - 1276, - 864, - 1277, - 878, - 1285, - 878, - 1288, - 869, - 1292, - 864, - 1318, - 866, - 1344, - 866, - 1353, - 864, - 1359, - 868, - 1361, - 876, - 1364, - 878, - 1370, - 878, - 1372, - 874, - 1375, - 841, - 1371, - 826, - 1371, - 817, - 1364, - 809, - 1364, - 807, - 1371, - 801 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "d4b8b9ee86ab57be7c5afa067f5fa9cb8cc62f83", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 97, - "points": [ - 1563, - 887, - 1563, - 889, - 1569, - 896, - 1569, - 900, - 1566, - 905, - 1567, - 939, - 1568, - 942, - 1573, - 946, - 1579, - 946, - 1581, - 948, - 1582, - 958, - 1589, - 968, - 1594, - 969, - 1597, - 967, - 1603, - 958, - 1613, - 957, - 1677, - 958, - 1687, - 968, - 1690, - 969, - 1694, - 968, - 1698, - 964, - 1699, - 927, - 1694, - 918, - 1693, - 906, - 1689, - 900, - 1690, - 891, - 1680, - 880, - 1679, - 873, - 1673, - 868, - 1650, - 865, - 1596, - 866, - 1588, - 868, - 1582, - 874, - 1581, - 879, - 1578, - 882, - 1569, - 883 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "68ef609bf39454907c1d87b7aabce86739a06b2c", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 98, - "points": [ - 1685, - 835, - 1685, - 874, - 1687, - 877, - 1700, - 879, - 1703, - 882, - 1704, - 890, - 1709, - 894, - 1714, - 895, - 1719, - 891, - 1722, - 886, - 1792, - 886, - 1795, - 888, - 1796, - 893, - 1799, - 895, - 1808, - 895, - 1811, - 892, - 1812, - 848, - 1803, - 839, - 1798, - 827, - 1783, - 813, - 1771, - 810, - 1718, - 811, - 1710, - 815, - 1701, - 824, - 1689, - 829 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "125e64e93ac3c9f1c0d6b74a90529a2a59d587d5", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 98, - "points": [ - 1481, - 781, - 1481, - 824, - 1492, - 833, - 1493, - 842, - 1502, - 842, - 1506, - 836, - 1567, - 836, - 1570, - 839, - 1570, - 841, - 1574, - 844, - 1579, - 844, - 1582, - 841, - 1583, - 801, - 1576, - 792, - 1579, - 783, - 1571, - 781, - 1564, - 771, - 1561, - 769, - 1543, - 766, - 1505, - 766, - 1498, - 768, - 1487, - 780 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "28f5b6c51b70739c2cdc2692af341a7098e056b", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 99, - "points": [ - 162, - 777, - 162, - 803, - 167, - 809, - 175, - 808, - 181, - 802, - 193, - 803, - 202, - 801, - 236, - 800, - 241, - 804, - 248, - 805, - 253, - 802, - 257, - 795, - 274, - 794, - 274, - 763, - 267, - 753, - 266, - 748, - 262, - 744, - 262, - 741, - 257, - 735, - 254, - 734, - 192, - 735, - 187, - 738, - 185, - 742, - 175, - 752, - 170, - 753, - 169, - 765, - 166, - 768, - 165, - 774 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "ccfe97f74dece5fea03619ff669dfefe68a5e0ba", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 99, - "points": [ - 666, - 700, - 667, - 708, - 663, - 716, - 663, - 741, - 667, - 744, - 671, - 744, - 679, - 739, - 724, - 739, - 732, - 744, - 737, - 744, - 740, - 738, - 747, - 732, - 746, - 701, - 738, - 695, - 738, - 688, - 731, - 681, - 684, - 681, - 678, - 687, - 678, - 690, - 675, - 695 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "c2bf17c4f161e7f0419bb2c41ea07412d42224", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 99, - "points": [ - 340, - 911, - 350, - 921, - 350, - 930, - 345, - 935, - 344, - 942, - 339, - 950, - 339, - 983, - 340, - 987, - 346, - 994, - 355, - 994, - 361, - 989, - 361, - 984, - 364, - 981, - 397, - 982, - 432, - 981, - 445, - 979, - 451, - 981, - 454, - 984, - 455, - 990, - 459, - 994, - 470, - 995, - 476, - 990, - 477, - 978, - 482, - 971, - 486, - 969, - 493, - 970, - 501, - 965, - 502, - 949, - 505, - 940, - 505, - 926, - 501, - 916, - 501, - 903, - 499, - 895, - 496, - 890, - 494, - 877, - 490, - 874, - 488, - 866, - 482, - 861, - 402, - 861, - 389, - 865, - 378, - 872, - 370, - 881, - 368, - 889, - 364, - 894, - 362, - 901, - 359, - 904 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "4f0117afcf92115345dba62a9a2585ed294c53d0", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 99, - "points": [ - 1137, - 876, - 1130, - 874, - 1122, - 874, - 1118, - 870, - 1112, - 857, - 1107, - 852, - 1101, - 849, - 1032, - 851, - 1027, - 853, - 1020, - 870, - 1014, - 875, - 1002, - 876, - 1001, - 880, - 1003, - 883, - 1007, - 884, - 1010, - 889, - 1004, - 898, - 1006, - 946, - 1011, - 971, - 1018, - 972, - 1022, - 970, - 1024, - 967, - 1024, - 962, - 1027, - 959, - 1045, - 959, - 1059, - 955, - 1079, - 955, - 1089, - 957, - 1111, - 956, - 1117, - 962, - 1118, - 966, - 1121, - 969, - 1130, - 968, - 1132, - 966, - 1132, - 917, - 1129, - 905, - 1132, - 898, - 1127, - 891, - 1126, - 886, - 1129, - 883, - 1136, - 881 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "b54277bb2047b209ceead953f10aed42d553cef", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 99, - "points": [ - 396, - 738, - 399, - 743, - 399, - 746, - 392, - 760, - 392, - 785, - 396, - 789, - 400, - 789, - 406, - 784, - 437, - 785, - 444, - 783, - 458, - 783, - 463, - 788, - 469, - 790, - 474, - 787, - 474, - 784, - 478, - 778, - 489, - 777, - 491, - 775, - 490, - 738, - 480, - 727, - 470, - 720, - 419, - 720, - 414, - 723, - 408, - 733, - 404, - 736 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "95f82dd6154efc3a9d9cd45e4db0a3852d5a798d", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 99, - "points": [ - 277, - 812, - 271, - 810, - 183, - 810, - 173, - 812, - 163, - 820, - 152, - 826, - 142, - 837, - 141, - 842, - 131, - 850, - 125, - 850, - 121, - 854, - 121, - 858, - 123, - 860, - 122, - 870, - 116, - 876, - 115, - 883, - 110, - 891, - 109, - 933, - 111, - 941, - 118, - 945, - 124, - 945, - 137, - 937, - 150, - 935, - 215, - 936, - 220, - 939, - 226, - 947, - 239, - 947, - 241, - 946, - 242, - 941, - 242, - 926, - 249, - 919, - 255, - 918, - 263, - 922, - 277, - 921, - 280, - 916, - 280, - 890, - 285, - 869, - 281, - 845, - 282, - 831, - 279, - 824, - 279, - 816 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "7940b1490647cc4607eada6f15a7e3f94d6b9577", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1, - 698, - 0, - 747, - 2, - 751, - 23, - 749, - 27, - 747, - 35, - 748, - 45, - 746, - 49, - 751, - 57, - 753, - 66, - 743, - 70, - 741, - 82, - 743, - 86, - 740, - 86, - 704, - 68, - 688, - 16, - 688, - 8, - 691 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "6a6929b0485169689f253af1ad9278b3cce11258", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 997, - 1002, - 998, - 1009, - 1010, - 1012, - 1014, - 1015, - 1008, - 1023, - 1005, - 1030, - 1005, - 1034, - 1000, - 1038, - 998, - 1042, - 998, - 1048, - 1000, - 1050, - 999, - 1053, - 1005, - 1056, - 1124, - 1057, - 1135, - 1055, - 1150, - 1056, - 1151, - 1054, - 1151, - 1036, - 1146, - 1027, - 1146, - 1019, - 1137, - 1009, - 1149, - 1007, - 1155, - 1004, - 1156, - 1000, - 1150, - 995, - 1142, - 995, - 1136, - 997, - 1128, - 982, - 1120, - 977, - 1038, - 977, - 1031, - 979, - 1025, - 983, - 1023, - 995, - 1021, - 997, - 1003, - 997 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "1ce1dbabb3758d266e9217575dc1d4e9ff60b128", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 277, - 811, - 279, - 816, - 277, - 820, - 277, - 827, - 284, - 835, - 284, - 854, - 288, - 861, - 312, - 861, - 342, - 857, - 348, - 858, - 354, - 866, - 359, - 866, - 363, - 863, - 364, - 855, - 368, - 850, - 382, - 849, - 385, - 847, - 384, - 837, - 387, - 829, - 388, - 817, - 384, - 810, - 383, - 799, - 367, - 782, - 305, - 782, - 293, - 792, - 288, - 801 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "654b7f814096da7f4b71e842b6c918c36a08eacf", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1649, - 1011, - 1655, - 1020, - 1653, - 1030, - 1649, - 1038, - 1648, - 1055, - 1651, - 1058, - 1696, - 1058, - 1726, - 1055, - 1800, - 1056, - 1814, - 1058, - 1820, - 1057, - 1821, - 1055, - 1815, - 1047, - 1815, - 1041, - 1811, - 1037, - 1808, - 1027, - 1805, - 1026, - 1803, - 1023, - 1802, - 1011, - 1793, - 1005, - 1789, - 999, - 1775, - 992, - 1735, - 990, - 1687, - 992, - 1680, - 996, - 1673, - 1003, - 1672, - 1007, - 1669, - 1010, - 1652, - 1009 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "32405cbb3cec698d9036f02fed1a973c44b1deff", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1339, - 926, - 1338, - 931, - 1347, - 940, - 1347, - 945, - 1343, - 952, - 1344, - 986, - 1348, - 997, - 1354, - 1002, - 1356, - 1009, - 1356, - 1022, - 1362, - 1031, - 1369, - 1031, - 1374, - 1029, - 1376, - 1022, - 1379, - 1019, - 1397, - 1019, - 1413, - 1021, - 1418, - 1023, - 1444, - 1023, - 1455, - 1020, - 1474, - 1020, - 1486, - 1024, - 1494, - 1023, - 1495, - 970, - 1491, - 963, - 1489, - 953, - 1485, - 949, - 1482, - 936, - 1483, - 932, - 1491, - 925, - 1487, - 922, - 1481, - 923, - 1475, - 920, - 1467, - 911, - 1466, - 906, - 1459, - 899, - 1370, - 899, - 1360, - 906, - 1359, - 917, - 1355, - 922 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "1681fb7d71c3e2b0ce28cbfc24eac3ea1922e8e", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1142, - 764, - 1138, - 761, - 1126, - 763, - 1123, - 759, - 1122, - 747, - 1117, - 742, - 1072, - 742, - 1054, - 744, - 1046, - 747, - 1044, - 750, - 1041, - 764, - 1038, - 767, - 1032, - 768, - 1038, - 773, - 1039, - 776, - 1035, - 785, - 1036, - 844, - 1049, - 844, - 1050, - 836, - 1054, - 833, - 1072, - 835, - 1117, - 833, - 1123, - 837, - 1124, - 842, - 1126, - 844, - 1133, - 845, - 1136, - 841, - 1138, - 806, - 1135, - 798, - 1134, - 784, - 1131, - 781, - 1129, - 775, - 1133, - 770, - 1140, - 769, - 1142, - 767 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "93a1502c2bc706c3fd3884c8ce8fc20ec097ea0", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1252, - 740, - 1252, - 749, - 1256, - 752, - 1255, - 767, - 1257, - 772, - 1257, - 781, - 1262, - 789, - 1263, - 796, - 1271, - 796, - 1280, - 784, - 1284, - 781, - 1292, - 779, - 1308, - 779, - 1321, - 777, - 1336, - 777, - 1341, - 779, - 1346, - 778, - 1347, - 763, - 1345, - 746, - 1342, - 743, - 1342, - 736, - 1346, - 734, - 1346, - 731, - 1337, - 728, - 1325, - 718, - 1307, - 716, - 1277, - 716, - 1270, - 719, - 1258, - 734, - 1256, - 744 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "53b3eb4966d32ad0adcd8acb8623b1942ec7050", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 184, - 638, - 179, - 633, - 173, - 631, - 124, - 631, - 115, - 636, - 109, - 643, - 105, - 651, - 100, - 655, - 94, - 655, - 91, - 662, - 89, - 686, - 93, - 708, - 103, - 708, - 107, - 701, - 115, - 698, - 160, - 698, - 180, - 679, - 192, - 677, - 192, - 654, - 185, - 643 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "3f2f416c69fc616840e064fe6abf6a9f578aa2c7", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 152, - 720, - 152, - 729, - 155, - 736, - 155, - 744, - 159, - 746, - 166, - 746, - 170, - 742, - 173, - 744, - 173, - 746, - 176, - 746, - 189, - 737, - 197, - 734, - 249, - 734, - 253, - 732, - 253, - 702, - 250, - 698, - 249, - 693, - 234, - 680, - 183, - 680, - 170, - 689, - 169, - 692, - 159, - 702 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "e801e4a11f7ad9b390cd4ee6c392435ba8fef570", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72275, - "probability": 98, - "points": [ - 1048, - 572, - 1045, - 579, - 1040, - 583, - 1041, - 592, - 1040, - 597, - 1037, - 601, - 1037, - 626, - 1035, - 648, - 1037, - 655, - 1036, - 667, - 1038, - 684, - 1041, - 684, - 1045, - 680, - 1053, - 667, - 1058, - 663, - 1082, - 663, - 1097, - 661, - 1102, - 665, - 1105, - 671, - 1113, - 680, - 1117, - 693, - 1123, - 693, - 1125, - 689, - 1125, - 673, - 1127, - 666, - 1126, - 650, - 1128, - 640, - 1126, - 599, - 1120, - 594, - 1099, - 591, - 1092, - 582, - 1091, - 577, - 1086, - 572 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "5874772e1428946f9be2b28ea88ed9cf22e625b3", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - }, - { - "type": "polygon", - "classId": 72275, - "probability": 100, - "points": [ - 1194, - 583, - 1192, - 601, - 1190, - 604, - 1190, - 608, - 1192, - 611, - 1191, - 629, - 1193, - 647, - 1198, - 651, - 1199, - 656, - 1205, - 657, - 1209, - 649, - 1220, - 649, - 1225, - 638, - 1229, - 634, - 1236, - 634, - 1241, - 636, - 1262, - 635, - 1262, - 595, - 1259, - 591, - 1255, - 589, - 1254, - 583, - 1242, - 577, - 1206, - 577 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "e9c738b1fd328df97c3f51616cdb574737a21eed", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - }, - { - "type": "polygon", - "classId": 72276, - "probability": 100, - "points": [ - 725.84, - 738.06, - 730.5, - 741, - 736.63, - 742.23, - 740.55, - 741, - 749.13, - 726.79, - 756.24, - 723.11, - 757.22, - 718.45, - 754.03, - 715.27, - 752.31, - 712.08, - 753.54, - 699.34, - 755.75, - 696.64, - 761.14, - 694.93, - 766.77, - 695.91, - 772.17, - 698.36, - 772.9, - 699.34, - 773.64, - 711.35, - 772.17, - 716, - 772.17, - 721.15, - 775.35, - 725.32, - 790.06, - 735.12, - 793.98, - 737.08, - 808.44, - 736.84, - 807.7, - 744.68, - 806.72, - 746.88, - 788.59, - 748.36, - 782.95, - 745.17, - 779.76, - 744.92, - 779.03, - 746.15, - 777.56, - 772.37, - 778.29, - 784.87, - 781.97, - 803.75, - 780.5, - 827.27, - 789.57, - 830.21, - 795.69, - 833.4, - 795.94, - 836.1, - 795.45, - 836.83, - 771.68, - 837.81, - 770.94, - 831.19, - 769.22, - 828.01, - 769.22, - 825.31, - 771.43, - 822.86, - 770.7, - 819.43, - 771.43, - 808.65, - 760.4, - 815.26, - 750.35, - 817.96, - 751.33, - 819.68, - 750.84, - 820.41, - 749.37, - 819.68, - 747.41, - 820.17, - 746.68, - 821.15, - 746.43, - 824.82, - 743, - 829.23, - 741.78, - 836.1, - 741.46, - 838.04, - 740.55, - 837.32, - 738.83, - 831.19, - 737.12, - 830.7, - 735.4, - 812.08, - 744.96, - 810.36, - 763.83, - 800.56, - 762.36, - 794.92, - 760.65, - 791.49, - 760.89, - 787.57, - 758.44, - 784.38, - 755.01, - 782.18, - 753.29, - 778.99, - 749.62, - 777.77, - 746.43, - 750.56, - 730.75, - 751.54, - 725.11, - 749.58, - 721.43, - 744.68, - 721.19, - 742.96 - ], - "groupId": 0, - "pointLabels": { - "0": "Left Hand", - "21": "Right Hand", - "36": "Right Foot", - "53": "Left Foot" - }, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": "932bda5b59db89dd68602306c70d8da62e0afdc", - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Human" - } - ], - "tags": [ - "tag1", - "tag2" - ], - "comments": [ - { - "x": 521.41, - "y": 531.6, - "resolved": false, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "correspondence": [ - { - "text": "dd2", - "email": "hovnatan@superannotate.com" - } - ] - } - ] -} diff --git a/tests/data_set/sample_project_vector_invalid/example_image_3.jpg b/tests/data_set/sample_project_vector_invalid/example_image_3.jpg deleted file mode 100644 index be81964..0000000 Binary files a/tests/data_set/sample_project_vector_invalid/example_image_3.jpg and /dev/null differ diff --git a/tests/data_set/sample_project_vector_invalid/example_image_3.jpg___fuse.png b/tests/data_set/sample_project_vector_invalid/example_image_3.jpg___fuse.png deleted file mode 100644 index 2c0fe51..0000000 Binary files a/tests/data_set/sample_project_vector_invalid/example_image_3.jpg___fuse.png and /dev/null differ diff --git a/tests/data_set/sample_project_vector_invalid/example_image_3.jpg___objects.json b/tests/data_set/sample_project_vector_invalid/example_image_3.jpg___objects.json deleted file mode 100644 index 36c8a9f..0000000 --- a/tests/data_set/sample_project_vector_invalid/example_image_3.jpg___objects.json +++ /dev/null @@ -1,720 +0,0 @@ -{ - "metadata": { - "name": "example_image_3.jpg", - "width": 1200, - "height": 675, - "status": "Completed", - "pinned": false, - "isPredicted": null, - "projectId": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 0.39, - 272.46, - 4.33, - 260.62, - 30.82, - 222.01, - 17.3, - 216.66, - 41.25, - 208.77, - 58.72, - 209.33, - 72.81, - 200.03, - 99.02, - 194.96, - 182.44, - 185.09, - 280.79, - 179.18, - 315.73, - 183.97, - 319.4, - 186.78, - 359.13, - 183.4, - 380.55, - 184.25, - 441.98, - 205.38, - 485.38, - 231.59, - 561.19, - 240.89, - 562.88, - 242.3, - 568.52, - 244.27, - 583.17, - 254.7, - 585.99, - 259.21, - 589.94, - 267.95, - 593.32, - 280.63, - 594.16, - 286.83, - 593.88, - 290.21, - 580.92, - 311.06, - 575.56, - 318.11, - 573.03, - 328.82, - 573.31, - 342.91, - 571.05, - 351.36, - 567.96, - 358.97, - 565.42, - 364.05, - 561.47, - 369.12, - 556.68, - 373.35, - 549.07, - 378.14, - 544, - 380.11, - 531.88, - 382.08, - 521.46, - 381.8, - 502.57, - 372.22, - 495.25, - 364.33, - 492.43, - 359.54, - 306.15, - 408.01, - 302.21, - 414.77, - 300.23, - 415.9, - 295.72, - 416.18, - 291.5, - 418.44, - 284.17, - 441.83, - 277.41, - 449.72, - 272.9, - 457.04, - 267.26, - 463.24, - 257.96, - 468.32, - 245.28, - 471.98, - 238.23, - 472.54, - 228.93, - 474.52, - 218.22, - 474.52, - 204.13, - 470.85, - 190.61, - 462.12, - 183, - 453.94, - 176.23, - 453.1, - 168.63, - 450.84, - 165.81, - 449.43, - 161.3, - 445.49, - 158.48, - 440.7, - 157.63, - 437.88, - 156.51, - 428.02, - 89.44, - 432.81, - 79.01, - 428.3, - 72.81, - 431.4, - 62.66, - 434.5, - 57.87, - 435.34, - 48.57, - 435.63, - 46.88, - 434.5, - 46.04, - 429.99, - 44.06, - 426.04, - 0.39, - 404.91 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1198.84, - 310.57, - 1099.1, - 298.81, - 1059.15, - 297.83, - 1035.87, - 304.93, - 993.48, - 321.84, - 918, - 370.61, - 917.27, - 376.98, - 867.77, - 435.79, - 857.96, - 439.71, - 854.78, - 444.12, - 852.33, - 451.72, - 843.75, - 488.72, - 843.26, - 488.72, - 835.17, - 504.41, - 833.7, - 512.25, - 833.21, - 565.43, - 834.44, - 605.37, - 835.91, - 607.58, - 840.81, - 611.01, - 877.08, - 643.6, - 893.5, - 648.25, - 949.86, - 674.23, - 1199.33, - 674.48 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 981.04, - 326.53, - 979.55, - 317.59, - 972.1, - 288.89, - 933.33, - 276.22, - 923.27, - 270.63, - 884.88, - 245.28, - 849.85, - 237.08, - 783.88, - 227.02, - 709.72, - 229.63, - 608.34, - 256.09, - 607.6, - 256.84, - 604.62, - 264.29, - 606.85, - 267.64, - 591.94, - 294.85, - 574.43, - 320.19, - 572.94, - 328.39, - 572.94, - 340.69, - 574.05, - 343.3, - 574.43, - 347.77, - 574.43, - 360.45, - 571.44, - 368.64, - 569.95, - 375.35, - 570.33, - 394.36, - 575.17, - 413, - 576.66, - 415.98, - 580.76, - 420.08, - 583.37, - 425.67, - 587.84, - 429.77, - 592.69, - 437.22, - 596.42, - 439.46, - 600.89, - 440.58, - 612.07, - 445.42, - 627.35, - 445.79, - 640.02, - 441.69, - 645.24, - 436.1, - 684.75, - 448.03, - 716.8, - 459.96, - 729.84, - 469.65, - 741.77, - 489.77, - 750.34, - 493.87, - 754.81, - 494.62, - 765.25, - 494.62, - 779.41, - 490.52, - 786.12, - 487.16, - 793.57, - 480.45, - 798.79, - 472.25, - 802.14, - 463.68, - 802.89, - 456.6, - 804.01, - 453.62, - 804.38, - 440.58, - 893.83, - 403.31, - 915.82, - 378.33, - 918.05, - 368.27, - 976.19, - 331.75 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 653.44, - 240.81, - 656.42, - 217.7, - 703.01, - 171.86, - 763.76, - 158.44, - 811.84, - 159.19, - 840.16, - 164.41, - 843.51, - 166.64, - 846.87, - 167.76, - 855.81, - 177.08, - 861.4, - 181.18, - 865.88, - 185.65, - 867.74, - 186.77, - 873.33, - 187.51, - 879.29, - 192.36, - 898.67, - 192.73, - 907.25, - 196.83, - 914.7, - 205.03, - 919.17, - 215.09, - 918.43, - 242.3, - 912.84, - 262.43, - 885.26, - 244.16, - 785.75, - 226.27, - 707.85, - 228.88, - 659.03, - 242.3 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 770.09, - 156.21, - 763.76, - 153.23, - 689.96, - 148.01, - 647.1, - 152.85, - 634.06, - 158.44, - 619.15, - 168.51, - 611.7, - 172.23, - 608.71, - 174.84, - 586.35, - 181.92, - 581.51, - 186.02, - 579.64, - 196.83, - 579.64, - 203.54, - 577.78, - 209.88, - 574.05, - 215.47, - 573.68, - 223.29, - 577.78, - 230.75, - 582.25, - 235.96, - 595.3, - 239.69, - 604.99, - 240.81, - 611.32, - 244.16, - 614.68, - 247.15, - 618.4, - 248.64, - 628.84, - 248.64, - 651.95, - 243.42, - 654.93, - 217.7, - 702.26, - 171.11 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1198.32, - 145.4, - 1134.59, - 143.91, - 1118.57, - 145.77, - 1113.72, - 148.38, - 1107.38, - 150.24, - 1102.17, - 154.72, - 1097.32, - 162.54, - 1093.97, - 171.49, - 1090.24, - 177.08, - 1081.67, - 185.28, - 1028.37, - 202.05, - 1022.04, - 212.48, - 1018.31, - 235.22, - 1018.68, - 249.75, - 1020.92, - 253.11, - 1023.53, - 254.97, - 1035.08, - 260.56, - 1037.32, - 264.29, - 1039.93, - 297.09, - 1043.28, - 301.56, - 1058.93, - 297.09, - 1079.06, - 298.2, - 1080.55, - 295.97, - 1082.79, - 288.89, - 1082.79, - 282.18, - 1174.84, - 293.36, - 1178.57, - 306.4, - 1180.43, - 308.27, - 1182.67, - 309.39, - 1197.95, - 309.76 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 1104.78, - 149.87, - 1073.84, - 127.14, - 1046.63, - 123.04, - 1016.82, - 120.06, - 948.99, - 128.25, - 947.5, - 129.37, - 929.61, - 161.8, - 928.49, - 168.13, - 928.49, - 194.22, - 930.73, - 199.81, - 932.22, - 200.93, - 935.57, - 201.68, - 938.55, - 203.91, - 938.93, - 209.88, - 940.04, - 213.6, - 946.01, - 224.04, - 959.42, - 224.78, - 963.9, - 220.68, - 965.01, - 218.45, - 965.01, - 215.47, - 1019.06, - 214.72, - 1019.8, - 212.11, - 1026.51, - 201.68, - 1081.3, - 184.53, - 1090.61, - 175.21 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 311.68, - 181.44, - 374.66, - 182.42, - 320.01, - 185.86 - ], - "groupId": 1, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 394.51, - 187.08, - 470.97, - 212.81, - 502.34, - 221.88, - 509.2, - 228.25, - 510.67, - 233.64, - 484.69, - 230.95, - 438.62, - 204.72, - 397.7, - 190.02, - 390.35, - 186.35 - ], - "groupId": 1, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - } - ], - "tags": [ - "tag2", - "tag3" - ], - "comments": [] -} diff --git a/tests/data_set/sample_project_vector_invalid/example_image_4.jpg b/tests/data_set/sample_project_vector_invalid/example_image_4.jpg deleted file mode 100644 index 5a53004..0000000 Binary files a/tests/data_set/sample_project_vector_invalid/example_image_4.jpg and /dev/null differ diff --git a/tests/data_set/sample_project_vector_invalid/example_image_4.jpg___fuse.png b/tests/data_set/sample_project_vector_invalid/example_image_4.jpg___fuse.png deleted file mode 100644 index 10285ee..0000000 Binary files a/tests/data_set/sample_project_vector_invalid/example_image_4.jpg___fuse.png and /dev/null differ diff --git a/tests/data_set/sample_project_vector_invalid/example_image_4.jpg___objects.json b/tests/data_set/sample_project_vector_invalid/example_image_4.jpg___objects.json deleted file mode 100644 index 6ac232a..0000000 --- a/tests/data_set/sample_project_vector_invalid/example_image_4.jpg___objects.json +++ /dev/null @@ -1,1143 +0,0 @@ -{ - "metadata": { - "name": "example_image_4.jpg", - "width": 680, - "height": 382, - "status": "Completed", - "pinned": false, - "isPredicted": null, - "projectId": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 150.76, - 215.35, - 151.45, - 224.38, - 152.7, - 226.18, - 158.12, - 226.74, - 159.23, - 226.32, - 160.48, - 219.24, - 188.67, - 223.13, - 214.5, - 223.13, - 217.27, - 229.51, - 222.27, - 229.24, - 226.02, - 227.57, - 228.8, - 217.85, - 243.93, - 206.32, - 245.88, - 210.63, - 250.32, - 211.18, - 252.68, - 210.07, - 255.32, - 204.52, - 257.13, - 186.05, - 254.35, - 164.66, - 244.07, - 147.45, - 236.02, - 146.06, - 215.74, - 144.11, - 190.89, - 143, - 180.75, - 150.64, - 171.45, - 159.53, - 167.14, - 164.66, - 163.39, - 170.36, - 161.03, - 167.3, - 157.14, - 167.86, - 155.06, - 168.97, - 154.51, - 170.22, - 154.64, - 171.33, - 158.53, - 173.83, - 160.06, - 174.25, - 153.53, - 185.08, - 151.73, - 196.88, - 149.23, - 205.91 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 0.07, - 270.84, - 10.21, - 271.08, - 15.77, - 272.77, - 22.89, - 279.29, - 25.43, - 280.5, - 32.31, - 285.93, - 39.8, - 289.07, - 42.7, - 292.09, - 45.47, - 296.32, - 48.25, - 301.27, - 49.22, - 305.13, - 45.71, - 336.53, - 44.39, - 339.91, - 42.94, - 342.2, - 38.83, - 344.38, - 34.24, - 344.98, - 31.22, - 344.98, - 28.33, - 343.41, - 0.07, - 362.49 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 0.15, - 363.3, - 4.62, - 360.62, - 10.53, - 359.01, - 44.89, - 355.07, - 77.63, - 358.11, - 94.09, - 363.3, - 97.49, - 366.34, - 100.89, - 371, - 102.15, - 374.4, - 102.5, - 381.73, - 0.51, - 381.73 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 110.2, - 380.84, - 105.72, - 373.68, - 103.75, - 367.95, - 106.44, - 338.61, - 108.05, - 331.81, - 125.41, - 312.48, - 117, - 308.37, - 115.92, - 307.29, - 115.38, - 304.97, - 115.56, - 301.93, - 116.64, - 300.49, - 117.89, - 299.96, - 120.93, - 299.96, - 125.41, - 301.75, - 126.66, - 302.82, - 128.09, - 308.37, - 151.35, - 276.87, - 169.96, - 261.84, - 178.91, - 256.83, - 223.11, - 258.26, - 244.94, - 266.5, - 254.78, - 276.69, - 258.36, - 283.49, - 263.37, - 288.68, - 266.05, - 293.34, - 267.3, - 300.49, - 267.66, - 313.2, - 262.83, - 348.09, - 262.83, - 347.73, - 259.43, - 355.07, - 258.71, - 355.61, - 249.95, - 356.14, - 246.19, - 353.82, - 245.29, - 351.31, - 226.33, - 369.03, - 225.07, - 381.37 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 396.32, - 381.55, - 397.21, - 364.02, - 391.84, - 345.77, - 388.8, - 338.07, - 387.91, - 337.36, - 380.21, - 333.6, - 374.67, - 331.45, - 371.45, - 330.56, - 308.64, - 325.55, - 296.11, - 328.59, - 289.49, - 333.6, - 273.21, - 353.46, - 264.44, - 369.74, - 255.49, - 367.06, - 252.99, - 368.13, - 250.66, - 371, - 250.66, - 374.22, - 252.27, - 376.36, - 255.85, - 379.05, - 256.03, - 381.37 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 339.59, - 327.16, - 340.67, - 317.31, - 356.59, - 298.35, - 355.88, - 297.45, - 346.75, - 294.23, - 346.57, - 293.52, - 349.62, - 288.86, - 354.8, - 288.5, - 356.95, - 290.29, - 357.67, - 292.62, - 359.64, - 292.98, - 377.89, - 267.21, - 390.59, - 255.76, - 417.61, - 254.33, - 454.29, - 259.7, - 465.21, - 265.24, - 474.51, - 273.83, - 478.09, - 285.64, - 479.52, - 301.93, - 478.27, - 324.11, - 477.02, - 331.81, - 472.37, - 340.04, - 469.5, - 342.37, - 466.64, - 342.72, - 462.53, - 342.01, - 460.38, - 339.14, - 460.2, - 337.18, - 449.1, - 354, - 449.28, - 367.95, - 447.67, - 370.82, - 444.27, - 374.57, - 436.76, - 376.9, - 431.57, - 376.36, - 427.81, - 371.89, - 396.68, - 373.68, - 396.85, - 362.41, - 389.16, - 337.53, - 379.32, - 332.34, - 374.13, - 330.38 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 406.52, - 253.43, - 414.39, - 239.83, - 405.44, - 236.43, - 403.65, - 233.57, - 404.01, - 230.17, - 405.27, - 229.46, - 407.77, - 229.46, - 411.53, - 230.53, - 415.29, - 233.75, - 416.72, - 233.93, - 436.58, - 209.24, - 448.75, - 200.82, - 493.3, - 202.61, - 508.33, - 207.98, - 515.13, - 227.67, - 517.1, - 230.53, - 519.79, - 238.22, - 521.04, - 239.65, - 521.58, - 241.44, - 521.58, - 246.81, - 517.82, - 274.01, - 512.81, - 279.92, - 506.01, - 280.81, - 505.11, - 278.13, - 498.67, - 286.36, - 496.88, - 300.85, - 495.63, - 304.07, - 493.66, - 305.33, - 491.16, - 305.86, - 487.76, - 305.68, - 483.1, - 302.82, - 483.1, - 297.09, - 479.17, - 296.74, - 477.74, - 285.46, - 474.51, - 273.65, - 463.96, - 264.17, - 455.01, - 259.7, - 416.9, - 253.43 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 232.6, - 261.5, - 231.9, - 254.42, - 233.43, - 249.84, - 241.2, - 233.31, - 241.34, - 231.51, - 234.96, - 227.9, - 234.4, - 226.93, - 234.4, - 224.43, - 237.04, - 223.45, - 243.29, - 223.45, - 243.98, - 224.01, - 244.95, - 227.06, - 261.34, - 203.74, - 272.87, - 195.13, - 286.06, - 192.77, - 304.53, - 193.32, - 325.08, - 196.38, - 336.19, - 201.93, - 339.52, - 211.65, - 345.77, - 218.04, - 348.13, - 222.48, - 350.07, - 247.89, - 348.41, - 251.92, - 346.88, - 264.84, - 343.69, - 270.11, - 341.05, - 271.36, - 338.13, - 271.92, - 335.35, - 271.64, - 332.86, - 266.78, - 323.41, - 276.92, - 323.55, - 286.78, - 321.33, - 292.47, - 319.66, - 294, - 315.08, - 295.39, - 312.58, - 294.83, - 310.36, - 292.47, - 308.69, - 288.03, - 284.81, - 290.53, - 263.98, - 290.39, - 261.9, - 287.19, - 257.87, - 283.44, - 254.81, - 276.5, - 245.09, - 266.22 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 423.82, - 109.43, - 445.55, - 110.52, - 450.74, - 115.71, - 451.95, - 120.9, - 455.82, - 129.23, - 457.75, - 135.39, - 458.11, - 142.52, - 456.06, - 146.26, - 455.57, - 148.07, - 455.57, - 150.12, - 456.18, - 151.69, - 455.82, - 165.94, - 455.09, - 167.88, - 452.07, - 169.81, - 450.38, - 170.17, - 447.48, - 169.93, - 445.67, - 168, - 440.24, - 173.55, - 439.63, - 181.64, - 437.34, - 183.57, - 434.44, - 183.45, - 432.39, - 182.61, - 430.94, - 180.67, - 430.58, - 176.81, - 422.73, - 176.93, - 423.45, - 174.03, - 422.97, - 125.61, - 419.47, - 121.99, - 423.57, - 118.85 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72274, - "probability": 100, - "points": [ - 12.52, - 102.15, - 17.66, - 94.48, - 15.77, - 92.17, - 15.56, - 88.71, - 16.4, - 88.39, - 18.4, - 89.23, - 22.39, - 89.44, - 23.86, - 90.6, - 39.72, - 73.17, - 46.02, - 71.17, - 65.76, - 69.28, - 80.04, - 68.97, - 89.91, - 69.7, - 98.1, - 73.48, - 102.51, - 77.79, - 103.77, - 81.15, - 97.89, - 123.68, - 97.89, - 127.35, - 97.05, - 124.31, - 82.66, - 126.93, - 81.61, - 134.39, - 80.46, - 136.38, - 75, - 138.27, - 72.79, - 138.27, - 71.22, - 137.54, - 69.96, - 136.28, - 68.59, - 132.81, - 63.55, - 133.13, - 63.24, - 104.04, - 57.88, - 103.51, - 57.67, - 106.88 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - }, - { - "type": "polygon", - "classId": 72275, - "probability": 100, - "points": [ - 0.41, - 101.91, - 57.82, - 107.46, - 58.02, - 103.56, - 63.37, - 103.97, - 63.17, - 173.31, - 55.76, - 174.96, - 57.2, - 186.07, - 57.2, - 205.21, - 53.08, - 205.42, - 52.06, - 222.5, - 50.41, - 226.82, - 47.32, - 232.17, - 45.88, - 236.49, - 42.18, - 239.78, - 36.42, - 242.25, - 27.57, - 242.46, - 24.28, - 245.13, - 22.42, - 248.63, - 20.78, - 250.28, - 15.02, - 253.16, - 5.76, - 253.77, - 0.41, - 252.13 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - }, - { - "type": "polygon", - "classId": 72275, - "probability": 100, - "points": [ - 222.23, - 143.48, - 232.31, - 141.21, - 234.17, - 142.45, - 239.52, - 142.65, - 242.6, - 140.18, - 244.87, - 135.66, - 245.69, - 116.52, - 262.15, - 116.72, - 261.74, - 109.52, - 265.65, - 100.67, - 261.95, - 83.39, - 253.72, - 81.54, - 214, - 77.42, - 210.5, - 76.39, - 208.65, - 62.4, - 206.8, - 60.75, - 206.59, - 57.25, - 118.52, - 53.96, - 117.9, - 56.64, - 109.26, - 61.16, - 106.38, - 65.49, - 98.56, - 122.69, - 97.94, - 141.62, - 99.18, - 152.74, - 108.02, - 155, - 110.7, - 160.35, - 112.96, - 162.61, - 115.02, - 163.23, - 118.11, - 162.41, - 119.96, - 155.82, - 169.14, - 159.12, - 168.94, - 161.38, - 190.95, - 142.45 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - }, - { - "type": "polygon", - "classId": 72275, - "probability": 100, - "points": [ - 303.1, - 192.66, - 304.75, - 181.96, - 307.84, - 169.2, - 301.66, - 167.96, - 300.84, - 160.56, - 302.69, - 156.44, - 306.4, - 156.44, - 309.48, - 157.68, - 321.01, - 130.92, - 318.95, - 129.28, - 318.95, - 120.84, - 350.64, - 103.14, - 351.46, - 106.64, - 421.84, - 110.14, - 422.66, - 108.7, - 423.28, - 108.7, - 423.28, - 118.58, - 419.16, - 122.08, - 422.45, - 125.78, - 423.07, - 173.73, - 419.16, - 196.77, - 415.46, - 208.91, - 412.99, - 212.82, - 410.52, - 214.06, - 407.84, - 213.85, - 404.55, - 210.35, - 403.93, - 208.3, - 392.41, - 217.76, - 390.76, - 231.76, - 389.74, - 233.4, - 385.83, - 235.67, - 376.57, - 235.67, - 374.3, - 233.81, - 372.66, - 227.85, - 348.37, - 228.46, - 348.58, - 222.29, - 339.73, - 211.59, - 335.82, - 201.3, - 324.92, - 195.95 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - }, - { - "type": "polygon", - "classId": 72275, - "probability": 100, - "points": [ - 462.58, - 200.89, - 463.2, - 193.07, - 468.34, - 173.93, - 459.08, - 172.08, - 461.96, - 158.29, - 467.31, - 158.29, - 467.93, - 165.7, - 471.22, - 165.91, - 481.31, - 128.04, - 490.36, - 110.96, - 486.86, - 109.52, - 486.45, - 99.85, - 489.33, - 82.36, - 518.76, - 62.61, - 517.73, - 80.1, - 586.87, - 88.74, - 585.64, - 91.62, - 589.55, - 95.53, - 589.96, - 160.35, - 587.08, - 178.05, - 582.76, - 190.81, - 581.11, - 211.18, - 579.46, - 214.06, - 576.79, - 215.91, - 572.47, - 216.32, - 568.56, - 214.47, - 559.71, - 226.2, - 556.21, - 228.05, - 551.89, - 234.02, - 552.1, - 250.07, - 548.8, - 249.66, - 545.1, - 246.37, - 544.28, - 243.9, - 543.86, - 236.08, - 519.58, - 237.11, - 516.7, - 229.49, - 514.85, - 227.85, - 508.26, - 207.06, - 493.45, - 202.12 - ], - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Large vehicle" - } - ], - "tags": [ - "tag1" - ], - "comments": [] -} diff --git a/tests/test_cli.py b/tests/test_cli.py deleted file mode 100644 index 2a5681f..0000000 --- a/tests/test_cli.py +++ /dev/null @@ -1,85 +0,0 @@ -from subprocess import Popen, PIPE -import os -from os.path import dirname -from tests import LIB_PATH - -from unittest import TestCase - - -class TestCLI(TestCase): - VALID_ANNOTATION_PATHS = "tests/data_set/sample_project_vector" - INVALID_ANNOTATION_PATHS = "tests/data_set/sample_project_vector_invalid" - IMAGE_1 = "example_image_1.jpg___objects.json" - IMAGE_2 = "example_image_2.jpg___objects.json" - - - @property - def valid_json_path(self): - return os.path.join(dirname(dirname(__file__)), self.VALID_ANNOTATION_PATHS) - - @property - def invalid_json_path(self): - return os.path.join(dirname(dirname(__file__)), self.INVALID_ANNOTATION_PATHS) - - def test_version_output(self): - args = ["version"] - p = Popen(["python3", f'{LIB_PATH}/bin/app.py', args[0]], stdout=PIPE, stderr=PIPE) - out, _ = p.communicate() - self.assertIsNotNone(out) - - def test_single_annotation_validation(self): - args = ["validate", "--project_type", "vector", f"{self.valid_json_path}/{self.IMAGE_1}"] - p = Popen(["python3", f'{LIB_PATH}/bin/app.py', *args], stdout=PIPE, stderr=PIPE) - out, _ = p.communicate() - self.assertTrue(b"[]" in out) - - def test_multiple_annotation_validation(self): - args = [ - "validate", "--project_type", "vector", - f"{self.valid_json_path}/{self.IMAGE_1}", - f"{self.valid_json_path}/{self.IMAGE_2}" - ] - p = Popen(["python3", f'{LIB_PATH}/bin/app.py', *args], stdout=PIPE, stderr=PIPE) - out, _ = p.communicate() - self.assertTrue(b"[]" in out) - - def test_single_invalid_annotation_validation(self): - args = ["validate", "--project_type", "vector", f"{self.invalid_json_path}/{self.IMAGE_2}"] - p = Popen(["python3", f'{LIB_PATH}/bin/app.py', *args], stdout=PIPE, stderr=PIPE) - out, _ = p.communicate() - self.assertIsNotNone(out) - - def test_single_invalid_annotation_validation__verbose(self): - args = [ - "validate", "--project_type", "vector", - f"{self.invalid_json_path}/{self.IMAGE_2}", - "--verbose" - ] - p = Popen(["python3", f'{LIB_PATH}/bin/app.py', *args], stdout=PIPE, stderr=PIPE) - out, _ = p.communicate() - self.assertTrue(b"instances[0].points field required" in out) - - def test_multiple_invalid_annotation_validation(self): - args = [ - "validate", "--project_type", "vector", - f"{self.invalid_json_path}/{self.IMAGE_2}", - f"{self.invalid_json_path}/{self.IMAGE_1}" - ] - p = Popen(["python3", f'{LIB_PATH}/bin/app.py', *args], stdout=PIPE, stderr=PIPE) - out, _ = p.communicate() - self.assertIsNotNone(out) - - - def test_(self): - # from email_validator import validate_email - # import time - # s = time.time() - # for i in range(10): - # try: - # validate_email("vaghinak@superannotate.com", check_deliverability=True) - # except Exception: - # pass - # print(time.time() - s) - import datetime - print((datetime.datetime.now(datetime.timezone.utc)).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + 'Z') - diff --git a/tests/test_document.py b/tests/test_document.py deleted file mode 100644 index 2de5553..0000000 --- a/tests/test_document.py +++ /dev/null @@ -1,339 +0,0 @@ -import json -import tempfile -from unittest import TestCase -from unittest.mock import patch - -from superannotate_schemas.schemas.classes import AnnotationClass -from superannotate_schemas.schemas.enums import DocumentAnnotationTypeEnum -from superannotate_schemas.validators import AnnotationValidators - - -class TestDocumentSchemas(TestCase): - def test_tag_enum_serialization(self): - annotations_class = AnnotationClass( - **{'id': 56820, 'project_id': 7617, 'name': 'Personal vehicle', 'color': '#547497', 'count': 18, - 'createdAt': '2020-09-29T10:39:39.000Z', 'updatedAt': '2020-09-29T10:48:18.000Z', 'type': 'tag', - 'attribute_groups': [{'id': 21448, 'class_id': 56820, 'name': 'Large', 'is_multiselect': 0, - 'createdAt': '2020-09-29T10:39:39.000Z', - 'updatedAt': '2020-09-29T10:39:39.000Z', 'attributes': [ - {'id': 57096, 'group_id': 21448, 'project_id': 7617, 'name': 'no', 'count': 0, - 'createdAt': '2020-09-29T10:39:39.000Z', 'updatedAt': '2020-09-29T10:39:39.000Z'}, - {'id': 57097, 'group_id': 21448, 'project_id': 7617, 'name': 'yes', 'count': 1, - 'createdAt': '2020-09-29T10:39:39.000Z', 'updatedAt': '2020-09-29T10:48:18.000Z'}]}]}) - data = json.loads(annotations_class.json()) - self.assertEqual(data["type"], "tag") - - def test_validate_document_annotation_without_classname(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - path = f"{tmpdir_name}/test_validate_document_annotation_without_classname.json" - with open(path, "w") as test_validate_document_annotation_without_classname: - test_validate_document_annotation_without_classname.write( - ''' - { - "metadata": { - "name": "text_file_example_1", - "status": "NotStarted", - "url": "https://sa-public-files.s3.us-west-2.amazonaws.com/Text+project/text_file_example_1.txt", - "projectId": 167826, - "annotatorEmail": null, - "qaEmail": null, - "lastAction": { - "email": "some.email@gmail.com", - "timestamp": 1636620976450 - } - }, - "instances": [{ - "type": "entity", - "start": 253, - "end": 593, - "classId": -1, - "createdAt": "2021-10-22T10:40:26.151Z", - "createdBy": { - "email": "some.email@gmail.com", - "role": "Admin" - }, - "updatedAt": "2021-10-22T10:40:29.953Z", - "updatedBy": { - "email": "some.email@gmail.com", - "role": "Admin" - }, - "attributes": [], - "creationType": "Manual" - }], - "tags": [], - "freeText": "" - } - ''' - ) - with open(path, "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("document")(data) - self.assertTrue(validator.is_valid()) - - def test_validate_document_annotation(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/doc.json", "w") as doc_json: - doc_json.write( - ''' - { - "metadata": { - "name": "text_file_example_1", - "status": "NotStarted", - "url": "https://sa-public-files.s3.us-west-2.amazonaws.com/Text+project/text_file_example_1.txt", - "projectId": 167826, - "annotatorEmail": null, - "qaEmail": null, - "lastAction": { - "email": "some.email@gmail.com", - "timestamp": 1636620976450 - } - }, - "instances": [], - "tags": [], - "freeText": "" - } - ''' - ) - - with open(f"{tmpdir_name}/doc.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("document")(data) - self.assertTrue(validator.is_valid()) - - @patch('builtins.print') - def test_validate_document_annotation_wrong_class_id(self, mock_print): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/test_validate_document_annotation_wrong_class_id.json", - "w") as test_validate_document_annotation_wrong_class_id: - test_validate_document_annotation_wrong_class_id.write( - ''' - { - "metadata": { - "name": "text_file_example_1", - "status": "NotStarted", - "url": "https://sa-public-files.s3.us-west-2.amazonaws.com/Text+project/text_file_example_1.txt", - "projectId": 167826, - "annotatorEmail": null, - "qaEmail": null, - "lastAction": { - "email": "some.email@gmail.com", - "timestamp": 1636620976450 - } - }, - "instances": [{ - "type": "entity", - "start": 253, - "end": 593, - "classId": "string", - "createdAt": "2021-10-22T10:40:26.151Z", - "createdBy": { - "email": "some.email@gmail.com", - "role": "Admin" - }, - "updatedAt": "2021-10-22T10:40:29.953Z", - "updatedBy": { - "email": "some.email@gmail.com", - "role": "Admin" - }, - "attributes": [ - { - "id": 1175876, - "groupId": 338357 - } - ], - "creationType": "Manual", - "className": "vid" - }], - "tags": [], - "freeText": "" - } - ''' - ) - - with open(f"{tmpdir_name}/test_validate_document_annotation_wrong_class_id.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("document")(data) - self.assertFalse(validator.is_valid()) - # TODO adjust - self.assertEqual( - validator.generate_report().strip(), - "instances[0].classId integer type expected\n" \ - "instances[0].attributes[0].name field required\n" \ - "instances[0].attributes[0].groupName field required".strip() - ) - - def test_validate_document_annotation_with_null_created_at(self): - test_validate_document_annotation_with_null_created_at = ( - ''' - { - "metadata": { - "name": "text_file_example_1", - "status": "NotStarted", - "url": "https://sa-public-files.s3.us-west-2.amazonaws.com/Text+project/text_file_example_1.txt", - "projectId": 167826, - "annotatorEmail": null, - "qaEmail": null, - "lastAction": { - "email": "some.email@gmail.com", - "timestamp": 1636620976450 - } - }, - "instances": [{ - "type": "entity", - "start": 253, - "end": 593, - "classId": 1, - "createdAt": null, - "createdBy": { - "email": "some.email@gmail.com", - "role": "Admin" - }, - "updatedAt": null, - "updatedBy": { - "email": "some.email@gmail.com", - "role": "Admin" - }, - "attributes": [], - "creationType": "Manual", - "className": "vid" - }], - "tags": [], - "freeText": "" - } - ''' - ) - data = json.loads(test_validate_document_annotation_with_null_created_at) - validator = AnnotationValidators.get_validator("document")(data) - self.assertTrue(validator.is_valid()) - - def test_validate_document_wrong_meta_data(self): - test_validate_document_annotation_without_classname = ( - ''' - { - "metadata": { - "name": "text_file_example_1", - "status": "NotStarted", - "width": ["fsda" ,1], - "height" : ["dfsadfsdf"], - "is_pinned": "afdasdfadf", - "url": "https://sa-public-files.s3.us-west-2.amazonaws.com/Text+project/text_file_example_1.txt", - "projectId": 167826, - "annotatorEmail": null, - "qaEmail": null, - "lastAction": { - "email": "some.email@gmail.com", - "timestamp": 1636620976450 - } - }, - "instances": [], - "tags": [], - "freeText": "" - } - ''' - ) - data = json.loads(test_validate_document_annotation_without_classname) - validator = AnnotationValidators.get_validator("vector")(data) - validator.is_valid() - print(validator.generate_report()) - self.assertFalse(validator.is_valid()) - self.assertEqual(len(validator.generate_report()), 141) - - def test_validate_document_with_tag_instance(self): - test_validate_document_annotation_with_null_created_at = ( - ''' - { - "metadata": { - "name": "text_file_example_1", - "status": "NotStarted", - "url": "https://sa-public-files.s3.us-west-2.amazonaws.com/Text+project/text_file_example_1.txt", - "projectId": 167826, - "annotatorEmail": null, - "qaEmail": null, - "lastAction": { - "email": "some.email@gmail.com", - "timestamp": 1636620976450 - } - }, - "instances": [{ - "type": "tag", - "start": 253, - "end": 593, - "classId": 1, - "createdAt": null, - "createdBy": { - "email": "some.email@gmail.com", - "role": "Admin" - }, - "updatedAt": null, - "updatedBy": { - "email": "some.email@gmail.com", - "role": "Admin" - }, - "attributes": [], - "creationType": "Manual", - "className": "vid" - }], - "tags": [], - "freeText": "" - } - ''' - ) - data = json.loads(test_validate_document_annotation_with_null_created_at) - model = AnnotationValidators.get_validator("document").validate(data) - self.assertEqual( - model.instances[0].type, - DocumentAnnotationTypeEnum.TAG.value - ) - - def test_validate_document_wrong_instance_data(self): - test_validate_document_annotation_without_classname = ( - ''' - { - "metadata": { - "name": "text_file_example_1", - "status": "NotStarted", - "width": ["fsda" ,1], - "height" : ["dfsadfsdf"], - "is_pinned": "afdasdfadf", - "url": "https://sa-public-files.s3.us-west-2.amazonaws.com/Text+project/text_file_example_1.txt", - "projectId": 167826, - "annotatorEmail": null, - "qaEmail": null, - "lastAction": { - "email": "some.email@gmail.com", - "timestamp": 1636620976450 - } - }, - "instances": [ - { - "type": "entit", - "start": 253, - "end": 593, - "classId": 1, - "createdAt": null, - "createdBy": { - "email": "some.email@gmail.com", - "role": "Admin" - }, - "updatedAt": null, - "updatedBy": { - "email": "some.email@gmail.com", - "role": "Admin" - }, - "attributes": [], - "creationType": "Manual", - "className": "vid" - } - ], - "tags": [], - "freeText": "" - } - ''' - ) - data = json.loads(test_validate_document_annotation_without_classname) - validator = AnnotationValidators.get_validator("document")(data) - validator.is_valid() - self.assertEqual( - "instances[0].type invalid type, valid types are entity, tag".strip(), - validator.generate_report().strip() - ) diff --git a/tests/test_validators.py b/tests/test_validators.py deleted file mode 100644 index 9022591..0000000 --- a/tests/test_validators.py +++ /dev/null @@ -1,1967 +0,0 @@ -import json -import tempfile -from unittest import TestCase -from unittest.mock import patch - -from superannotate_schemas.schemas.classes import AnnotationClass -from superannotate_schemas.validators import AnnotationValidators -from superannotate_schemas.schemas.external.vector import Polygon - - -class TestSchemas(TestCase): - def test_tag_enum_serialization(self): - annotations_class = AnnotationClass( - **{'id': 56820, 'project_id': 7617, 'name': 'Personal vehicle', 'color': '#547497', 'count': 18, - 'createdAt': '2020-09-29T10:39:39.000Z', 'updatedAt': '2020-09-29T10:48:18.000Z', 'type': 'tag', - 'attribute_groups': [{'id': 21448, 'class_id': 56820, 'name': 'Large', 'is_multiselect': 0, - 'createdAt': '2020-09-29T10:39:39.000Z', - 'updatedAt': '2020-09-29T10:39:39.000Z', 'attributes': [ - {'id': 57096, 'group_id': 21448, 'project_id': 7617, 'name': 'no', 'count': 0, - 'createdAt': '2020-09-29T10:39:39.000Z', 'updatedAt': '2020-09-29T10:39:39.000Z'}, - {'id': 57097, 'group_id': 21448, 'project_id': 7617, 'name': 'yes', 'count': 1, - 'createdAt': '2020-09-29T10:39:39.000Z', 'updatedAt': '2020-09-29T10:48:18.000Z'}]}]}) - data = json.loads(annotations_class.json()) - self.assertEqual(data["type"], "tag") - - def test_validate_annotation_with_wrong_bbox(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/vector.json", "w") as vector_json: - vector_json.write( - """ - { - "metadata": { - "name": "example_image_1.jpg", - "width": 1024, - "height": 683, - "status": "Completed", - "pinned": false, - "isPredicted": null, - "projectId": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "type": "bbox", - "classId": 72274, - "probability": 100, - "points": { - - "x2": 465.23, - "y1": 341.5, - "y2": 357.09 - }, - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": false, - "attributes": [ - { - "id": 117845, - "groupId": 28230, - "name": "2", - "groupName": "Num doors" - } - ], - "trackingId": "aaa97f80c9e54a5f2dc2e920fc92e5033d9af45b", - "error": null, - "createdAt": null, - "createdBy": null, - "creationType": null, - "updatedAt": null, - "updatedBy": null, - "className": "Personal vehicle" - } - ] - } - - """ - - ) - - with open(f"{tmpdir_name}/vector.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("vector")(data) - self.assertFalse(validator.is_valid()) - self.assertEqual(len(validator.generate_report()), 63) - - # sa.validate_annotations("Vector", os.path.join(self.vector_folder_path, f"{tmpdir_name}/vector.json")) - # mock_print.assert_any_call( - # "instances[0].type invalid type, valid types are bbox, " - # "template, cuboid, polygon, point, polyline, ellipse, rbbox" - # ) - - def test_validate_pixel_annotation(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/pixel.json", "w") as pix_json: - pix_json.write( - ''' - { - "metadata": { - "lastAction": { - "email": "some.email@gmail.com", - "timestamp": 1636627539398 - }, - "width": 1024, - "height": 683, - "name": "example_image_1.jpg", - "projectId": 164324, - "isPredicted": false, - "isSegmented": false, - "status": "NotStarted", - "pinned": false, - "annotatorEmail": null, - "qaEmail": null - }, - "comments": [], - "tags": [], - "instances": [] - } - ''' - ) - - with open(f"{tmpdir_name}/pixel.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("pixel")(data) - self.assertTrue(validator.is_valid()) - - def test_validate_video_export_annotation(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/video_export.json", "w") as video_export: - video_export.write( - ''' - { - "metadata": { - "name": "video.mp4", - "width": 848, - "height": 476, - "status": "NotStarted", - "url": "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4", - "duration": 2817000, - "projectId": 164334, - "error": null, - "annotatorEmail": null, - "qaEmail": null, - "lastAction": { - "timestamp": 1636384061135, - "email": "some.email@gmail.com" - } - }, - "instances": [], - "tags": [] - } - ''' - ) - - with open(f"{tmpdir_name}/video_export.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("video")(data) - self.assertTrue(validator.is_valid()) - - def test_validate_vector_empty_annotation(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/vector_empty.json", "w") as vector_empty: - vector_empty.write( - ''' - { - "metadata": { - "lastAction": { - "email": "some.email@gmail.com", - "timestamp": 1636627956948 - }, - "width": 1024, - "height": 683, - "name": "example_image_1.jpg", - "projectId": 162462, - "isPredicted": false, - "status": "NotStarted", - "pinned": false, - "annotatorEmail": null, - "qaEmail": null - }, - "comments": [], - "tags": [], - "instances": [] - } - ''' - ) - - with open(f"{tmpdir_name}/vector_empty.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("video")(data) - self.assertTrue(validator.is_valid()) - - def test_validate_error_message_format(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/test_validate_error_message_format.json", - "w") as test_validate_error_message_format: - test_validate_error_message_format.write( - ''' - { - "metadata": {} - } - ''' - ) - - with open(f"{tmpdir_name}/test_validate_error_message_format.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("vector")(data) - self.assertFalse(validator.is_valid()) - self.assertEqual(validator.generate_report(), - "metadata.name field required") - - @patch('builtins.print') - def test_validate_vector_instance_type_and_attr_annotation(self, mock_print): - json_name = "test.json" - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/{json_name}", "w") as json_file: - json_file.write( - ''' - { - "metadata": { - "lastAction": { - "email": "some.email@gmail.com", - "timestamp": 1636958573242 - }, - "width": 1234, - "height": 1540, - "name": "t.png", - "projectId": 164988, - "isPredicted": false, - "status": "Completed", - "pinned": false, - "annotatorEmail": null, - "qaEmail": null - }, - "comments": [], - "tags": [], - "instances": [ - { - "classId": 880080, - "probability": 100, - "points": { - "x1": 148.99, - "x2": 1005.27, - "y1": 301.96, - "y2": 1132.36 - }, - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": null, - "error": null, - "createdAt": "2021-11-15T06:43:09.812Z", - "createdBy": { - "email": "shab.prog@gmail.com", - "role": "Admin" - }, - "creationType": "Manual", - "updatedAt": "2021-11-15T06:43:13.831Z", - "updatedBy": { - "email": "shab.prog@gmail.com", - "role": "Admin" - }, - "className": "kj" - } - ] - } - ''' - ) - - with open(f"{tmpdir_name}/{json_name}", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("vector")(data) - self.assertFalse(validator.is_valid()) - self.assertEqual(validator.generate_report(), - "instances[0].type field required") - - @patch('builtins.print') - def test_validate_vector_invalid_instance_type_and_attr_annotation(self, mock_print): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/test_validate_vector_invalid_instace_type_and_attr_annotation.json", - "w") as test_validate_vector_invalid_instace_type_and_attr_annotation: - test_validate_vector_invalid_instace_type_and_attr_annotation.write( - ''' - { - "metadata": { - "lastAction": { - "email": "some.email@gmail.com", - "timestamp": 1636958573242 - }, - "width": 1234, - "height": 1540, - "name": "t.png", - "projectId": 164988, - "isPredicted": false, - "status": "Completed", - "pinned": false, - "annotatorEmail": null, - "qaEmail": null - }, - "comments": [], - "tags": [], - "instances": [ - { - "type": "bad_type", - "classId": 880080, - "probability": 100, - "points": { - "x1": 148.99, - "x2": 1005.27, - "y1": 301.96, - "y2": 1132.36 - }, - "groupId": 0, - "pointLabels": {}, - "locked": false, - "visible": true, - "attributes": [], - "trackingId": null, - "error": null, - "createdAt": "2021-11-15T06:43:09.812Z", - "createdBy": { - "email": "shab.prog@gmail.com", - "role": "Admin" - }, - "creationType": "Manual", - "updatedAt": "2021-11-15T06:43:13.831Z", - "updatedBy": { - "email": "shab.prog@gmail.com", - "role": "Admin" - }, - "className": "kj" - } - ] - } - ''' - ) - - with open(f"{tmpdir_name}/test_validate_vector_invalid_instace_type_and_attr_annotation.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("vector")(data) - self.assertFalse(validator.is_valid()) - self.assertEqual(len(validator.generate_report()), 148) - - @patch('builtins.print') - def test_validate_video_invalid_instance_type_and_attr_annotation(self, mock_print): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/test_validate_video_invalid_instace_type_and_attr_annotation.json", - "w") as test_validate_video_invalid_instace_type_and_attr_annotation: - test_validate_video_invalid_instace_type_and_attr_annotation.write( - ''' - { - "metadata": { - "name": "video.mp4", - "width": 480, - "height": 270, - "status": "NotStarted", - "url": "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4", - "duration": 30526667, - "projectId": 152038, - "error": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "meta": { - "type": "bbox", - "classId": 859496, - "className": "vid", - "pointLabels": { - "3": "point label bro" - }, - "start": 0, - "end": 30526667 - }, - "parameters": [ - { - "start": 0, - "end": 30526667, - "timestamps": [ - { - "points": { - "x1": 223.32, - "y1": 78.45, - "x2": 312.31, - "y2": 176.66 - }, - "timestamp": 0, - "attributes": [] - }, - { - "points": { - "x1": 182.08, - "y1": 33.18, - "x2": 283.45, - "y2": 131.39 - }, - "timestamp": 17271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.32, - "y1": 36.33, - "x2": 284.01, - "y2": 134.54 - }, - "timestamp": 18271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.49, - "y1": 45.09, - "x2": 283.18, - "y2": 143.3 - }, - "timestamp": 19271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.9, - "y1": 48.35, - "x2": 283.59, - "y2": 146.56 - }, - "timestamp": 19725864, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.49, - "y1": 52.46, - "x2": 283.18, - "y2": 150.67 - }, - "timestamp": 20271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.49, - "y1": 63.7, - "x2": 283.18, - "y2": 161.91 - }, - "timestamp": 21271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.07, - "y1": 72.76, - "x2": 283.76, - "y2": 170.97 - }, - "timestamp": 22271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.07, - "y1": 81.51, - "x2": 283.76, - "y2": 179.72 - }, - "timestamp": 23271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.42, - "y1": 97.19, - "x2": 284.11, - "y2": 195.4 - }, - "timestamp": 24271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.42, - "y1": 97.19, - "x2": 284.11, - "y2": 195.4 - }, - "timestamp": 30526667, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - } - ] - } - ] - }, - { - "meta": { - "type": "bbox", - "classId": 859496, - "className": "vid", - "start": 29713736, - "end": 30526667 - }, - "parameters": [ - { - "start": 29713736, - "end": 30526667, - "timestamps": [ - { - "points": { - "x1": 132.82, - "y1": 129.12, - "x2": 175.16, - "y2": 188 - }, - "timestamp": 29713736, - "attributes": [] - }, - { - "points": { - "x1": 132.82, - "y1": 129.12, - "x2": 175.16, - "y2": 188 - }, - "timestamp": 30526667, - "attributes": [] - } - ] - } - ] - }, - { - "meta": { - "type": "bad_type", - "classId": 859496, - "className": "vid", - "start": 5528212, - "end": 7083022 - }, - "parameters": [ - { - "start": 5528212, - "end": 7083022, - "timestamps": [ - { - "timestamp": 5528212, - "attributes": [] - }, - { - "timestamp": 6702957, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "timestamp": 7083022, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - } - ] - } - ] - } - ], - "tags": [ - "some tag" - ] - } - ''' - ) - - with open(f"{tmpdir_name}/test_validate_video_invalid_instace_type_and_attr_annotation.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("video")(data) - self.assertFalse(validator.is_valid()) - self.assertEqual(validator.generate_report(), - "instances[2].meta.type invalid type, valid types are bbox, event") - - @patch('builtins.print') - def test_validate_video_invalid_instance_without_type_and_attr_annotation(self, mock_print): - json_name = "test.json" - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/{json_name}", "w") as json_file: - json_file.write( - ''' - { - "metadata": { - "name": "video.mp4", - "width": 480, - "height": 270, - "status": "NotStarted", - "url": "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4", - "duration": 30526667, - "projectId": 152038, - "error": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "meta": { - "type": "bbox", - "classId": 859496, - "className": "vid", - "pointLabels": { - "3": "point label bro" - }, - "start": 0, - "end": 30526667 - }, - "parameters": [ - { - "start": 0, - "end": 30526667, - "timestamps": [ - { - "points": { - "x1": 223.32, - "y1": 78.45, - "x2": 312.31, - "y2": 176.66 - }, - "timestamp": 0, - "attributes": [] - }, - { - "points": { - "x1": 182.08, - "y1": 33.18, - "x2": 283.45, - "y2": 131.39 - }, - "timestamp": 17271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.32, - "y1": 36.33, - "x2": 284.01, - "y2": 134.54 - }, - "timestamp": 18271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.49, - "y1": 45.09, - "x2": 283.18, - "y2": 143.3 - }, - "timestamp": 19271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.9, - "y1": 48.35, - "x2": 283.59, - "y2": 146.56 - }, - "timestamp": 19725864, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.49, - "y1": 52.46, - "x2": 283.18, - "y2": 150.67 - }, - "timestamp": 20271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.49, - "y1": 63.7, - "x2": 283.18, - "y2": 161.91 - }, - "timestamp": 21271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.07, - "y1": 72.76, - "x2": 283.76, - "y2": 170.97 - }, - "timestamp": 22271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.07, - "y1": 81.51, - "x2": 283.76, - "y2": 179.72 - }, - "timestamp": 23271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.42, - "y1": 97.19, - "x2": 284.11, - "y2": 195.4 - }, - "timestamp": 24271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.42, - "y1": 97.19, - "x2": 284.11, - "y2": 195.4 - }, - "timestamp": 30526667, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - } - ] - } - ] - }, - { - "meta": { - "type": "bbox", - "classId": 859496, - "className": "vid", - "start": 29713736, - "end": 30526667 - }, - "parameters": [ - { - "start": 29713736, - "end": 30526667, - "timestamps": [ - { - "points": { - "x1": 132.82, - "y1": 129.12, - "x2": 175.16, - "y2": 188 - }, - "timestamp": 29713736, - "attributes": [] - }, - { - "points": { - "x1": 132.82, - "y1": 129.12, - "x2": 175.16, - "y2": 188 - }, - "timestamp": 30526667, - "attributes": [] - } - ] - } - ] - }, - { - "meta": { - "classId": 859496, - "className": "vid", - "start": 5528212, - "end": 7083022 - }, - "parameters": [ - { - "start": 5528212, - "end": 7083022, - "timestamps": [ - { - "timestamp": 5528212, - "attributes": [] - }, - { - "timestamp": 6702957, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "timestamp": 7083022, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - } - ] - } - ] - } - ], - "tags": [ - "some tag" - ] - } - ''' - ) - - with open(f"{tmpdir_name}/{json_name}", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("video")(data) - self.assertFalse(validator.is_valid()) - self.assertEqual(validator.generate_report(), - "instances[2].meta.type field required") - - def test_validate_video_point_labels(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/test_validate_video_point_labels.json", - "w") as test_validate_video_point_labels: - test_validate_video_point_labels.write( - ''' - { - "metadata": { - "name": "video.mp4", - "width": 480, - "height": 270, - "status": "NotStarted", - "url": "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4", - "duration": 30526667, - "projectId": 152038, - "error": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "meta": { - "type": "bbox", - "classId": 859496, - "className": "vid", - "pointLabels": "bad_point_label", - "start": 0, - "end": 30526667 - }, - "parameters": [ - { - "start": 0, - "end": 30526667, - "timestamps": [ - { - "points": { - "x1": 223.32, - "y1": 78.45, - "x2": 312.31, - "y2": 176.66 - }, - "timestamp": 0, - "attributes": [] - }, - { - "points": { - "x1": 182.08, - "y1": 33.18, - "x2": 283.45, - "y2": 131.39 - }, - "timestamp": 17271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.32, - "y1": 36.33, - "x2": 284.01, - "y2": 134.54 - }, - "timestamp": 18271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.49, - "y1": 45.09, - "x2": 283.18, - "y2": 143.3 - }, - "timestamp": 19271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.9, - "y1": 48.35, - "x2": 283.59, - "y2": 146.56 - }, - "timestamp": 19725864, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.49, - "y1": 52.46, - "x2": 283.18, - "y2": 150.67 - }, - "timestamp": 20271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.49, - "y1": 63.7, - "x2": 283.18, - "y2": 161.91 - }, - "timestamp": 21271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.07, - "y1": 72.76, - "x2": 283.76, - "y2": 170.97 - }, - "timestamp": 22271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.07, - "y1": 81.51, - "x2": 283.76, - "y2": 179.72 - }, - "timestamp": 23271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.42, - "y1": 97.19, - "x2": 284.11, - "y2": 195.4 - }, - "timestamp": 24271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.42, - "y1": 97.19, - "x2": 284.11, - "y2": 195.4 - }, - "timestamp": 30526667, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - } - ] - } - ] - }, - { - "meta": { - "type": "bbox", - "classId": 859496, - "className": "vid", - "start": 29713736, - "end": 30526667 - }, - "parameters": [ - { - "start": 29713736, - "end": 30526667, - "timestamps": [ - { - "points": { - "x1": 132.82, - "y1": 129.12, - "x2": 175.16, - "y2": 188 - }, - "timestamp": 29713736, - "attributes": [] - }, - { - "points": { - "x1": 132.82, - "y1": 129.12, - "x2": 175.16, - "y2": 188 - }, - "timestamp": 30526667, - "attributes": [] - } - ] - } - ] - }, - { - "meta": { - "type": "event", - "classId": 859496, - "className": "vid", - "start": 5528212, - "end": 7083022 - }, - "parameters": [ - { - "start": 5528212, - "end": 7083022, - "timestamps": [ - { - "timestamp": 5528212, - "attributes": [] - }, - { - "timestamp": 6702957, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "timestamp": 7083022, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - } - ] - } - ] - } - ], - "tags": [ - "some tag" - ] - } - ''' - ) - - with open(f"{tmpdir_name}/test_validate_video_point_labels.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("video")(data) - self.assertFalse(validator.is_valid()) - self.assertEqual(validator.generate_report(), - "instances[0].meta.pointLabels value is not a valid dict") - - def test_validate_video_point_labels_bad_keys(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/test_validate_video_point_labels_bad_keys.json", - "w") as test_validate_video_point_labels_bad_keys: - test_validate_video_point_labels_bad_keys.write( - ''' - { - "metadata": { - "name": "video.mp4", - "width": 480, - "height": 270, - "status": "NotStarted", - "url": "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4", - "duration": 30526667, - "projectId": 152038, - "error": null, - "annotatorEmail": null, - "qaEmail": null - }, - "instances": [ - { - "meta": { - "type": "bbox", - "classId": 859496, - "className": "vid", - "pointLabels": { - "bad_key_1" : "a", - "bad_key_2" : "b", - " " : "afsd", - "1" : ["fasdf","sdfsdf"] - }, - "start": 0, - "end": 30526667 - }, - "parameters": [ - { - "start": 0, - "end": 30526667, - "timestamps": [ - { - "points": { - "x1": 223.32, - "y1": 78.45, - "x2": 312.31, - "y2": 176.66 - }, - "timestamp": 0, - "attributes": [] - }, - { - "points": { - "x1": 182.08, - "y1": 33.18, - "x2": 283.45, - "y2": 131.39 - }, - "timestamp": 17271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.32, - "y1": 36.33, - "x2": 284.01, - "y2": 134.54 - }, - "timestamp": 18271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.49, - "y1": 45.09, - "x2": 283.18, - "y2": 143.3 - }, - "timestamp": 19271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.9, - "y1": 48.35, - "x2": 283.59, - "y2": 146.56 - }, - "timestamp": 19725864, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.49, - "y1": 52.46, - "x2": 283.18, - "y2": 150.67 - }, - "timestamp": 20271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 181.49, - "y1": 63.7, - "x2": 283.18, - "y2": 161.91 - }, - "timestamp": 21271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.07, - "y1": 72.76, - "x2": 283.76, - "y2": 170.97 - }, - "timestamp": 22271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.07, - "y1": 81.51, - "x2": 283.76, - "y2": 179.72 - }, - "timestamp": 23271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.42, - "y1": 97.19, - "x2": 284.11, - "y2": 195.4 - }, - "timestamp": 24271058, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "points": { - "x1": 182.42, - "y1": 97.19, - "x2": 284.11, - "y2": 195.4 - }, - "timestamp": 30526667, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - } - ] - } - ] - }, - { - "meta": { - "type": "bbox", - "classId": 859496, - "className": "vid", - "start": 29713736, - "end": 30526667 - }, - "parameters": [ - { - "start": 29713736, - "end": 30526667, - "timestamps": [ - { - "points": { - "x1": 132.82, - "y1": 129.12, - "x2": 175.16, - "y2": 188 - }, - "timestamp": 29713736, - "attributes": [] - }, - { - "points": { - "x1": 132.82, - "y1": 129.12, - "x2": 175.16, - "y2": 188 - }, - "timestamp": 30526667, - "attributes": [] - } - ] - } - ] - }, - { - "meta": { - "type": "event", - "classId": 859496, - "className": "vid", - "start": 5528212, - "end": 7083022, - "pointLabels": {} - }, - "parameters": [ - { - "start": 5528212, - "end": 7083022, - "timestamps": [ - { - "timestamp": 5528212, - "attributes": [] - }, - { - "timestamp": 6702957, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "timestamp": "7083022", - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - } - ] - } - ] - }, - { - "parameters": [ - { - "start": 5528212, - "end": 7083022, - "timestamps": [ - { - "timestamp": 5528212, - "attributes": [] - }, - { - "timestamp": 6702957, - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - }, - { - "timestamp": "7083022", - "attributes": [ - { - "id": 1175876, - "groupId": 338357, - "name": "attr", - "groupName": "attr g" - } - ] - } - ] - } - ] - }, - { - "meta": "afsdfadsf" - }, - { - "meta" : [] - } - ], - "tags": [ - 123 - ] - } - ''' - ) - - with open(f"{tmpdir_name}/test_validate_video_point_labels_bad_keys.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("video")(data) - self.assertFalse(validator.is_valid()) - self.assertEqual(len(validator.generate_report()), 409) - - def test_validate_vector_empty_annotation(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/vector_empty.json", "w") as vector_empty: - vector_empty.write( - ''' - { - "metadata":{ - "lastAction":{ - "email":"test@test.com", - "timestamp":1641910273710 - }, - "width":480, - "height":270, - "name":"1 copy_001.jpg", - "projectId":181302, - "isPredicted":false, - "status":"Completed", - "pinned":false, - "annotatorEmail":null, - "qaEmail":null - }, - "comments":[ - - ], - "tags":[ - - ], - "instances":[ - { - "type":"rbbox", - "classId":901659, - "probability":100, - "points":{ - "x1":213.57, - "y1":74.08, - "x2":275.65, - "y2":128.66, - "x3":238.51, - "y3":170.92, - "x4":176.43, - "y4":116.34 - }, - "groupId":0, - "pointLabels":{ - - }, - "locked":false, - "visible":true, - "attributes":[ - - ], - "trackingId":null, - "error":null, - "createdAt":"2022-01-11T14:11:30.772Z", - "createdBy":{ - "email":"test@test.com", - "role":"Admin" - }, - "creationType":"Manual", - "updatedAt":"2022-01-11T14:11:35.852Z", - "updatedBy":{ - "email":"test@test.com", - "role":"Admin" - }, - "className":"df" - } - ] - } - ''' - ) - - with open(f"{tmpdir_name}/vector_empty.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("vector")(data) - self.assertTrue(validator.is_valid()) - - def test_validate_pixel_annotation_instance_without_class_name(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/pixel.json", "w") as pix_json: - pix_json.write( - ''' - { - "metadata": { - "name": "example_image_1.jpg", - "lastAction": { - "email": "shab.prog@gmail.com", - "timestamp": 1637306216 - } - }, - "instances": [ - { - "creationType": "Preannotation", - "classId": 887060, - "visible": true, - "probability": 100, - "attributes": [ - { - "id": 1223660, - "groupId": 358141, - "name": "no", - "groupName": "small" - } - ], - "parts": [ - { - "color": "#000447" - } - ] - } - ], - "tags": [], - "comments": [] - } - ''' - ) - - with open(f"{tmpdir_name}/pixel.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("pixel")(data) - self.assertTrue(validator.is_valid()) - - def test_validate_vector_empty_annotation_bad_role(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/vector_empty.json", "w") as vector_empty: - vector_empty.write( - ''' - - - { - "metadata":{ - "lastAction":{ - "email":"test@test.com", - "timestamp":1641910273710 - }, - "width":480, - "height":270, - "pinned": [ "fasdf" ], - "name":"1 copy_001.jpg", - "projectId":181302, - "isPredicted":false, - "status":"Completed", - "annotatorEmail":null, - "qaEmail":null - }, - "comments":[ - - ], - "tags":[ - - ], - "instances":[ - { "type": "tag", - "classId": 530982, - "className": "Weather", - "probability": 100, - "attributes": [ - { - "id": 94853, - "groupId": 23650, - "name": "Winter", - "groupName": "Season" - } - ], - "createdAt": "2021-12-24T12:12:07.324Z", - "createdBy": { - "email": "annotator@superannotate.com", - "role": "Annotator" - }, - "creationType": "Manual", - "updatedAt": "2021-12-24T12:12:58.011Z", - "updatedBy": { - "email": "qa@superannotate.com", - "role": "QA" - } - }, - { "type": "tags", - "classId": 530982, - "className": "Weather", - "probability": 100, - "attributes": [ - { - "id": 94853, - "groupId": 23650, - "name": "Winter", - "groupName": "Season" - } - ], - "createdAt": "2021-12-24T12:12:07.324Z", - "createdBy": { - "email": "annotator@superannotate.com", - "role": "Annotator" - }, - "creationType": "Manual", - "updatedAt": "2021-12-24T12:12:58.011Z", - "updatedBy": { - "email": "qa@superannotate.com", - "role": "QA" - } - }, - { - "type":"rbbox", - "classId":901659, - "probability":100, - "points":{ - "x1":213.57, - "y1":74.08, - "x2":275.65, - "y2":128.66, - "x3":238.51, - "y3":170.92, - "x4":176.43, - "y4":116.34 - }, - "groupId":0, - "pointLabels":{ - - }, - "locked":false, - "visible":true, - "attributes":[ - - ], - "trackingId":null, - "error":null, - "createdAt":"2022-01-11T14:11:30.772Z", - "createdBy":{ - "email":"test@test.com", - "role":"bad_role" - }, - "creationType":"Manual", - "updatedAt":"2022-01-11T14:11:35.852Z", - "updatedBy":{ - "email":"test@test.com", - "role":"Admin" - }, - "className":"df" - } - ] - } - ''' - ) - - with open(f"{tmpdir_name}/vector_empty.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("vector")(data) - self.assertFalse(validator.is_valid()) - print(validator.generate_report()) - self.assertEqual(len(validator.generate_report()), 340) - - def test_validate_tag_without_class_name(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - with open(f"{tmpdir_name}/tag.json", "w") as vector_empty: - vector_empty.write( - ''' - - - { - "metadata":{ - "lastAction":{ - "email":"test@test.com", - "timestamp":1641910273710 - }, - "width":480, - "height":270, - "pinned": true, - "name":"1 copy_001.jpg", - "projectId":181302, - "isPredicted":false, - "status":"Completed", - "annotatorEmail":null, - "qaEmail":null - }, - "comments":[ - - ], - "tags":[ - - ], - "instances":[ - { "type": "tag", - "classId": 530982, - "probability": 100, - "attributes": [ - { - "id": 94853, - "groupId": 23650 - } - ], - "createdAt": "2021-12-24T12:12:07.324Z", - "createdBy": { - "email": "annotator@superannotate.com", - "role": "Annotator" - }, - "creationType": "Manual", - "updatedAt": "2021-12-24T12:12:58.011Z", - "updatedBy": { - "email": "qa@superannotate.com", - "role": "QA" - } - } - ] - } - ''' - ) - - with open(f"{tmpdir_name}/tag.json", "r") as f: - data = json.loads(f.read()) - validator = AnnotationValidators.get_validator("vector")(data) - self.assertFalse(validator.is_valid()) - print(validator.generate_report()) - self.assertEqual(len(validator.generate_report()), 191) - - def test_strict_points(self): - try: - Polygon(points=["asd", 1, 1.0, 3], type="polygon") - except Exception as e: - print(333, e) \ No newline at end of file