Remove old UI (#1964)

* Removed outdated code

* Revert changes in .gitignore

* Fix docker build

* Remove karma + some dummy code

* Fix a type in Dockerfile.ci

* Fix swagger and remove dispatch_request.

* Update CHANGELOG.md
main
Nikita Manovich 6 years ago committed by GitHub
parent 0d0749f4ec
commit 634ca17958
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
- Removed OpenVINO and CUDA components because they are not necessary anymore (<https://github.com/opencv/cvat/pull/1767>)
- Removed the old UI code (<https://github.com/opencv/cvat/pull/1964>)
### Fixed
- Some objects aren't shown on canvas sometimes. For example after propagation on of objects is invisible (<https://github.com/opencv/cvat/pull/1834>)

@ -106,9 +106,6 @@ COPY datumaro/ ${HOME}/datumaro
RUN python3 -m pip install --no-cache-dir -r ${HOME}/datumaro/requirements.txt
# Binary option is necessary to correctly apply the patch on Windows platform.
# https://unix.stackexchange.com/questions/239364/how-to-fix-hunk-1-failed-at-1-different-line-endings-message
RUN patch --binary -p1 < ${HOME}/cvat/apps/engine/static/engine/js/3rdparty.patch
RUN chown -R ${USER}:${USER} .
# RUN all commands below as 'django' user

@ -27,17 +27,7 @@ COPY .coveragerc .
# RUN all commands below as 'django' user
USER ${USER}
RUN mkdir -p tests && cd tests && npm install \
eslint \
eslint-detailed-reporter \
karma \
karma-chrome-launcher \
karma-coveralls \
karma-coverage \
karma-junit-reporter \
karma-qunit \
qunit; \
CI=true npm install cypress; \
RUN mkdir -p tests && cd tests && CI=true npm install cypress; \
echo "export PATH=~/tests/node_modules/.bin:${PATH}" >> ~/.bashrc;
ENTRYPOINT []

@ -54,15 +54,6 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element {
>
{`Job #${id}`}
</Button>
|
<Tooltip
title='Old version of UI is deprecated and will be removed from
new versions of UI. We still recomend it only if you use
specific features from it like cuboids annotation.'
mouseLeaveDelay={0}
>
<Button type='link' href={`${baseURL}/?id=${id}`}>Old UI</Button>
</Tooltip>
</div>
),
}, {

@ -1,28 +0,0 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.urls import path
from django.conf import settings
from rest_auth.views import (
LoginView, LogoutView, PasswordChangeView,
PasswordResetView, PasswordResetConfirmView)
from rest_auth.registration.views import RegisterView
from .views import SigningView
urlpatterns = [
path('login', LoginView.as_view(), name='rest_login'),
path('logout', LogoutView.as_view(), name='rest_logout'),
path('signing', SigningView.as_view(), name='signing')
]
if settings.DJANGO_AUTH_TYPE == 'BASIC':
urlpatterns += [
path('register', RegisterView.as_view(), name='rest_register'),
path('password/reset', PasswordResetView.as_view(),
name='rest_password_reset'),
path('password/reset/confirm', PasswordResetConfirmView.as_view(),
name='rest_password_reset_confirm'),
path('password/change', PasswordChangeView.as_view(),
name='rest_password_change'),
]

@ -1,60 +0,0 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.contrib.auth.forms import (
UsernameField,
AuthenticationForm,
UserCreationForm,
)
from django.utils.translation import gettext, gettext_lazy as _
from django.contrib.auth.models import User
from django import forms
class AuthForm(AuthenticationForm):
username = UsernameField(
widget=forms.TextInput(attrs={'autofocus': True, 'placeholder': "Username"}),
)
password = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput(attrs={'placeholder': "Password"}),
)
class NewUserForm(UserCreationForm):
username = UsernameField(
widget=forms.TextInput(attrs={'autofocus': True, 'placeholder': "Username (required)"}),
required=True,
)
first_name = UsernameField(
widget=forms.TextInput(attrs={'placeholder': "First name"}),
required=False,
)
last_name = UsernameField(
widget=forms.TextInput(attrs={'placeholder': "Last name"}),
required=False,
)
email = forms.EmailField(
widget=forms.EmailInput(attrs={'placeholder': "Email (required)"}),
required=True,
)
password1 = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput(attrs={'placeholder': "Password (required)"}),
)
password2 = forms.CharField(
label=_("Password confirmation"),
widget=forms.PasswordInput(attrs={'placeholder': "Password confirmation (required)"}),
strip=False,
)
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', )

@ -1,5 +0,0 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT

@ -1,9 +0,0 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.db import models
# Create your models here.

@ -1,17 +0,0 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
{% extends "auth_base.html" %}
{% block title %}Forbidden{% endblock %}
{% block content %}
<h1>Forbidden</h1>
{% if user.is_authenticated %}
<p align="center">Your account doesn't have access to this page. To proceed,
please login with an account that has access or contact your admin.<br>
<big><a href="{% url 'login' %}">Login</a><big></p>
{% endif %}
{% endblock %}

@ -1,99 +0,0 @@
<!--
Copyright (c) 2018 by Tyler Fry (https://codepen.io/frytyler/pen/EGdtg)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-->
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
<style>
.btn { display: inline-block; *display: inline; *zoom: 1; padding: 4px 10px 4px; margin-bottom: 0; font-size: 13px; line-height: 18px; color: #333333; text-align: center;text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); vertical-align: middle; background-color: #f5f5f5; background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); background-image: linear-gradient(top, #ffffff, #e6e6e6); background-repeat: repeat-x; filter: progid:dximagetransform.microsoft.gradient(startColorstr=#ffffff, endColorstr=#e6e6e6, GradientType=0); border-color: #e6e6e6 #e6e6e6 #e6e6e6; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); border: 1px solid #e6e6e6; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); cursor: pointer; *margin-left: .3em; }
.btn:hover, .btn:active, .btn.active, .btn.disabled, .btn[disabled] { background-color: #e6e6e6; }
.btn-large { padding: 9px 14px; font-size: 15px; line-height: normal; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; }
.btn:hover { color: #333333; text-decoration: none; background-color: #e6e6e6; background-position: 0 -15px; -webkit-transition: background-position 0.1s linear; -moz-transition: background-position 0.1s linear; -ms-transition: background-position 0.1s linear; -o-transition: background-position 0.1s linear; transition: background-position 0.1s linear; }
.btn-primary, .btn-primary:hover { text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); color: #ffffff; }
.btn-primary.active { color: rgba(255, 255, 255, 0.75); }
.btn-primary { background-color: #4a77d4; background-image: -moz-linear-gradient(top, #6eb6de, #4a77d4); background-image: -ms-linear-gradient(top, #6eb6de, #4a77d4); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#6eb6de), to(#4a77d4)); background-image: -webkit-linear-gradient(top, #6eb6de, #4a77d4); background-image: -o-linear-gradient(top, #6eb6de, #4a77d4); background-image: linear-gradient(top, #6eb6de, #4a77d4); background-repeat: repeat-x; filter: progid:dximagetransform.microsoft.gradient(startColorstr=#6eb6de, endColorstr=#4a77d4, GradientType=0); border: 1px solid #3762bc; text-shadow: 1px 1px 1px rgba(0,0,0,0.4); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.5); }
.btn-primary:hover, .btn-primary:active, .btn-primary.active, .btn-primary.disabled, .btn-primary[disabled] { filter: none; background-color: #4a77d4; }
.btn-block { width: 100%; display:block; }
* { -webkit-box-sizing:border-box; -moz-box-sizing:border-box; -ms-box-sizing:border-box; -o-box-sizing:border-box; box-sizing:border-box; }
html { width: 100%; height:100%; overflow:hidden; }
body {
width: 100%;
height:100%;
font-family: 'Open Sans', sans-serif;
background: #092756;
background: -moz-linear-gradient(-45deg, #afcfec 0%, #17458f 100%);
background: -webkit-linear-gradient(-45deg, #afcfec 0%,#17458f 100%);
}
.login {
position: absolute;
top: 50%;
left: 50%;
margin: -150px 0 0 -150px;
width:300px;
height:300px;
}
.login h1 { color: #fff; text-shadow: 0 0 10px rgba(0,0,0,0.3); letter-spacing:1px; text-align:center; }
.note {
color: #fff;
text-shadow: 0 0 10px rgba(0,0,0,0.3);
position: absolute;
bottom: 10%;
width: 100%;
margin: 0 auto;
font-size: large;
text-align: center;
}
input {
width: 100%;
margin-bottom: 10px;
background: rgba(0,0,0,0.3);
border: none;
outline: none;
padding: 10px;
font-size: 13px;
color: #fff;
text-shadow: 1px 1px 1px rgba(0,0,0,0.3);
border: 1px solid rgba(0,0,0,0.3);
border-radius: 4px;
box-shadow: inset 0 -5px 45px rgba(100,100,100,0.2), 0 1px 1px rgba(255,255,255,0.2);
-webkit-transition: box-shadow .5s ease;
-moz-transition: box-shadow .5s ease;
-o-transition: box-shadow .5s ease;
-ms-transition: box-shadow .5s ease;
transition: box-shadow .5s ease;
}
input::placeholder {
color: rgb(168, 168, 168);
}
input:focus { box-shadow: inset 0 -5px 45px rgba(100,100,100,0.4), 0 1px 1px rgba(255,255,255,0.2); }
</style>
</head>
<body>
<div class="login">
{% block content %} {% endblock %}
</div>
<div class="note">
{% block note %} {% endblock %}
</div>
</body>
</html>

@ -1,29 +0,0 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
{% extends "auth_base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<h1>Login</h1>
{% if form.errors %}
<small>Your username and password didn't match. Please try again.</small>
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
{% for field in form %}
{{ field }}
{% endfor %}
<input type="hidden" name="next" value="{{ next }}" />
<button type="submit" class="btn btn-primary btn-block btn-large">Login</button>
</form>
{% endblock %}
{% block note%}
{% autoescape off %}
{{ note }}
{% endautoescape %}
{% endblock %}

@ -1,32 +0,0 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
{% extends "auth_base.html" %}
{% block title %}Create user{% endblock %}
{% block content %}
<div class="login">
<h1>Create user</h1>
<form method="post" action="{% url 'register' %}">
{% csrf_token %}
{% for field in form %}
{{ field }}
{% if field.errors %}
{% for error in field.errors %}
<small>{{ error }}<small>
{% endfor %}
{% endif %}
{% endfor %}
<input type="hidden" name="next" value="{{ next }}" />
<button type="submit" class="btn btn-primary btn-block btn-large">Register</button>
</form>
</div>
<div class="note">
</div>
{% endblock %}

@ -1,21 +0,0 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
<div class='userProfile'>
{% if user.is_authenticated %}
{% if user.ldap_user and 'thumbnailPhoto' in user.ldap_user.attrs %}
{# TODO insert photo from ldap #}
{% endif %}
{% if user.first_name and user.last_name %}
<p>{{ user.first_name }} {{ user.last_name }}</p>
{% else %}
<p>{{ user.username }}</p>
{% endif %}
<form action="{% url 'logout' %}">
<input type="submit" class="menuButton semiBold h2" value="Logout">
</form>
{% endif %}
</div>

@ -1,9 +0,0 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.test import TestCase
# Create your tests here.

@ -1,23 +1,28 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.urls import path
from django.contrib.auth import views as auth_views
from django.conf import settings
from . import forms
from . import views
from rest_auth.views import (
LoginView, LogoutView, PasswordChangeView,
PasswordResetView, PasswordResetConfirmView)
from rest_auth.registration.views import RegisterView
from .views import SigningView
urlpatterns = [
path('login', auth_views.LoginView.as_view(form_class=forms.AuthForm,
template_name='login.html', extra_context={'note': settings.AUTH_LOGIN_NOTE}),
name='login'),
path('logout', auth_views.LogoutView.as_view(next_page='login'), name='logout'),
path('login', LoginView.as_view(), name='rest_login'),
path('logout', LogoutView.as_view(), name='rest_logout'),
path('signing', SigningView.as_view(), name='signing')
]
if settings.DJANGO_AUTH_TYPE == 'BASIC':
urlpatterns += [
path('register', views.register_user, name='register'),
path('register', RegisterView.as_view(), name='rest_register'),
path('password/reset', PasswordResetView.as_view(),
name='rest_password_reset'),
path('password/reset/confirm', PasswordResetConfirmView.as_view(),
name='rest_password_reset_confirm'),
path('password/change', PasswordChangeView.as_view(),
name='rest_password_change'),
]

@ -2,35 +2,17 @@
#
# SPDX-License-Identifier: MIT
from django.shortcuts import render, redirect
from django.conf import settings
from django.contrib.auth import login, authenticate
from rest_framework import views
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
from furl import furl
from . import forms
from . import signature
from django.utils.decorators import method_decorator
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
def register_user(request):
if request.method == 'POST':
form = forms.NewUserForm(request.POST)
if form.is_valid():
form.save()
username = form.cleaned_data.get('username')
raw_password = form.cleaned_data.get('password1')
user = authenticate(username=username, password=raw_password)
login(request, user)
return redirect(settings.LOGIN_REDIRECT_URL)
else:
form = forms.NewUserForm()
return render(request, 'register.html', {'form': form})
@method_decorator(name='post', decorator=swagger_auto_schema(
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,

@ -1,9 +0,0 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.contrib import admin
# Register your models here.

@ -1,5 +0,0 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT

@ -1,9 +0,0 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.db import models
# Create your models here.

@ -1,9 +0,0 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.test import TestCase
# Create your tests here.

@ -1,181 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
html {
background-color: #FFFFFF;
margin: 0px auto;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@font-face {
font-family: BarlowSemiCondensed-Regular;
src: url(fonts/BarlowSemiCondensed-Regular.ttf);
}
@font-face {
font-family: BarlowSemiCondensed-SemiBold;
src: url(fonts/BarlowSemiCondensed-SemiBold.ttf);
}
@font-face {
font-family: BarlowSemiCondensed-Bold;
src: url(fonts/BarlowSemiCondensed-Bold.ttf);
}
.regular {
font-family: BarlowSemiCondensed-Regular;
}
.semiBold {
font-family: BarlowSemiCondensed-SemiBold;
}
.bold {
font-family: BarlowSemiCondensed-Bold;
}
.h2 {
font-size: 1.2em;
}
.h1 {
font-size: 1.4em;
}
.h3 {
font-size: 1.1em;
}
.overlay {
position: fixed;
display: none;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: white;
z-index: 100;
}
.modal {
position: fixed;
z-index: 10;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #FFFFFF;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
}
.close {
position: absolute;
right: 5px;
top: 5px;
width: 32px;
height: 32px;
opacity: 0.3;
}
.close:hover {
opacity: 1;
}
.close:before, .close:after {
position: absolute;
left: 15px;
content: ' ';
height: 22px;
width: 2px;
background-color: #333;
}
.close:before {
transform: rotate(45deg);
}
.close:after {
transform: rotate(-45deg);
}
.tab {
overflow: hidden;
border: 1px solid black;
background-color: #B0C4DE;
}
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 3px 0px;
transition: 0.3s;
}
.tab button:hover {
background-color: #81aee8;
}
.tab button.active {
background-color: #ccc;
}
.activeTabButton {
background-color: #81aee8 !important;
}
.hidden {
display: none;
}
.selectable {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.dropdown {
position: relative;
}
.dropdown-content {
position: absolute;
background-color: #f1f1f1;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
list-style-type: none;
padding-inline-start: 0;
margin-left: 20%;
margin-top: -1%;
}
.dropdown-content li {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
.dropdown-content li:hover {background-color: #ddd}

@ -1,95 +0,0 @@
Fonts was downloaded from: https://fonts.google.com/specimen/Barlow+Semi+Condensed
Copyright 2017 The Barlow Project Authors (https://github.com/jpt/barlow)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

@ -1,5 +0,0 @@
Icons was downloaded from: https://icomoon.io/#preview-free [GitHub](https://github.com/Keyamoon/IcoMoon-Free)
[License](https://github.com/Keyamoon/IcoMoon-Free/blob/master/License.txt):
You can use this package under one of these two licenses: [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/) or [GPL](http://www.gnu.org/licenses/gpl.html).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

@ -1,184 +0,0 @@
/**
* @preserve jquery.fullscreen 1.1.5
* https://github.com/kayahr/jquery-fullscreen-plugin
* Copyright (C) 2012-2013 Klaus Reimer <k@ailis.de>
* Licensed under the MIT license
* (See http://www.opensource.org/licenses/mit-license)
*/
(function(jQuery) {
/**
* Sets or gets the fullscreen state.
*
* @param {boolean=} state
* True to enable fullscreen mode, false to disable it. If not
* specified then the current fullscreen state is returned.
* @return {boolean|Element|jQuery|null}
* When querying the fullscreen state then the current fullscreen
* element (or true if browser doesn't support it) is returned
* when browser is currently in full screen mode. False is returned
* if browser is not in full screen mode. Null is returned if
* browser doesn't support fullscreen mode at all. When setting
* the fullscreen state then the current jQuery selection is
* returned for chaining.
* @this {jQuery}
*/
function fullScreen(state)
{
var e, func, doc;
// Do nothing when nothing was selected
if (!this.length) return this;
// We only use the first selected element because it doesn't make sense
// to fullscreen multiple elements.
e = (/** @type {Element} */ this[0]);
// Find the real element and the document (Depends on whether the
// document itself or a HTML element was selected)
if (e.ownerDocument)
{
doc = e.ownerDocument;
}
else
{
doc = e;
e = doc.documentElement;
}
// When no state was specified then return the current state.
if (state == null)
{
// When fullscreen mode is not supported then return null
if (!((/** @type {?Function} */ doc["exitFullscreen"])
|| (/** @type {?Function} */ doc["webkitExitFullscreen"])
|| (/** @type {?Function} */ doc["webkitCancelFullScreen"])
|| (/** @type {?Function} */ doc["msExitFullscreen"])
|| (/** @type {?Function} */ doc["mozCancelFullScreen"])))
{
return null;
}
// Check fullscreen state
state = !!doc["fullscreenElement"]
|| !!doc["msFullscreenElement"]
|| !!doc["webkitIsFullScreen"]
|| !!doc["mozFullScreen"];
if (!state) return state;
// Return current fullscreen element or "true" if browser doesn't
// support this
return (/** @type {?Element} */ doc["fullscreenElement"])
|| (/** @type {?Element} */ doc["webkitFullscreenElement"])
|| (/** @type {?Element} */ doc["webkitCurrentFullScreenElement"])
|| (/** @type {?Element} */ doc["msFullscreenElement"])
|| (/** @type {?Element} */ doc["mozFullScreenElement"])
|| state;
}
// When state was specified then enter or exit fullscreen mode.
if (state)
{
// Enter fullscreen
func = (/** @type {?Function} */ e["requestFullscreen"])
|| (/** @type {?Function} */ e["webkitRequestFullscreen"])
|| (/** @type {?Function} */ e["webkitRequestFullScreen"])
|| (/** @type {?Function} */ e["msRequestFullscreen"])
|| (/** @type {?Function} */ e["mozRequestFullScreen"]);
if (func)
{
func.call(e);
}
return this;
}
else
{
// Exit fullscreen
func = (/** @type {?Function} */ doc["exitFullscreen"])
|| (/** @type {?Function} */ doc["webkitExitFullscreen"])
|| (/** @type {?Function} */ doc["webkitCancelFullScreen"])
|| (/** @type {?Function} */ doc["msExitFullscreen"])
|| (/** @type {?Function} */ doc["mozCancelFullScreen"]);
if (func) func.call(doc);
return this;
}
}
/**
* Toggles the fullscreen mode.
*
* @return {!jQuery}
* The jQuery selection for chaining.
* @this {jQuery}
*/
function toggleFullScreen()
{
return (/** @type {!jQuery} */ fullScreen.call(this,
!fullScreen.call(this)));
}
/**
* Handles the browser-specific fullscreenchange event and triggers
* a jquery event for it.
*
* @param {?Event} event
* The fullscreenchange event.
*/
function fullScreenChangeHandler(event)
{
jQuery(document).trigger(new jQuery.Event("fullscreenchange"));
}
/**
* Handles the browser-specific fullscreenerror event and triggers
* a jquery event for it.
*
* @param {?Event} event
* The fullscreenerror event.
*/
function fullScreenErrorHandler(event)
{
jQuery(document).trigger(new jQuery.Event("fullscreenerror"));
}
/**
* Installs the fullscreenchange event handler.
*/
function installFullScreenHandlers()
{
var e, change, error;
// Determine event name
e = document;
if (e["webkitCancelFullScreen"])
{
change = "webkitfullscreenchange";
error = "webkitfullscreenerror";
}
else if (e["msExitFullscreen"])
{
change = "MSFullscreenChange";
error = "MSFullscreenError";
}
else if (e["mozCancelFullScreen"])
{
change = "mozfullscreenchange";
error = "mozfullscreenerror";
}
else
{
change = "fullscreenchange";
error = "fullscreenerror";
}
// Install the event handlers
jQuery(document).bind(change, fullScreenChangeHandler);
jQuery(document).bind(error, fullScreenErrorHandler);
}
jQuery.fn["fullScreen"] = fullScreen;
jQuery.fn["toggleFullScreen"] = toggleFullScreen;
installFullScreenHandlers();
})(jQuery);

@ -1,165 +0,0 @@
/*!
* JavaScript Cookie v2.2.0
* https://github.com/js-cookie/js-cookie
*
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
* Released under the MIT license
*/
;(function (factory) {
var registeredInModuleLoader = false;
if (typeof define === 'function' && define.amd) {
define(factory);
registeredInModuleLoader = true;
}
if (typeof exports === 'object') {
module.exports = factory();
registeredInModuleLoader = true;
}
if (!registeredInModuleLoader) {
var OldCookies = window.Cookies;
var api = window.Cookies = factory();
api.noConflict = function () {
window.Cookies = OldCookies;
return api;
};
}
}(function () {
function extend () {
var i = 0;
var result = {};
for (; i < arguments.length; i++) {
var attributes = arguments[ i ];
for (var key in attributes) {
result[key] = attributes[key];
}
}
return result;
}
function init (converter) {
function api (key, value, attributes) {
var result;
if (typeof document === 'undefined') {
return;
}
// Write
if (arguments.length > 1) {
attributes = extend({
path: '/'
}, api.defaults, attributes);
if (typeof attributes.expires === 'number') {
var expires = new Date();
expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
attributes.expires = expires;
}
// We're using "expires" because "max-age" is not supported by IE
attributes.expires = attributes.expires ? attributes.expires.toUTCString() : '';
try {
result = JSON.stringify(value);
if (/^[\{\[]/.test(result)) {
value = result;
}
} catch (e) {}
if (!converter.write) {
value = encodeURIComponent(String(value))
.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
} else {
value = converter.write(value, key);
}
key = encodeURIComponent(String(key));
key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
key = key.replace(/[\(\)]/g, escape);
var stringifiedAttributes = '';
for (var attributeName in attributes) {
if (!attributes[attributeName]) {
continue;
}
stringifiedAttributes += '; ' + attributeName;
if (attributes[attributeName] === true) {
continue;
}
stringifiedAttributes += '=' + attributes[attributeName];
}
return (document.cookie = key + '=' + value + stringifiedAttributes);
}
// Read
if (!key) {
result = {};
}
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling "get()"
var cookies = document.cookie ? document.cookie.split('; ') : [];
var rdecode = /(%[0-9A-Z]{2})+/g;
var i = 0;
for (; i < cookies.length; i++) {
var parts = cookies[i].split('=');
var cookie = parts.slice(1).join('=');
if (!this.json && cookie.charAt(0) === '"') {
cookie = cookie.slice(1, -1);
}
try {
var name = parts[0].replace(rdecode, decodeURIComponent);
cookie = converter.read ?
converter.read(cookie, name) : converter(cookie, name) ||
cookie.replace(rdecode, decodeURIComponent);
if (this.json) {
try {
cookie = JSON.parse(cookie);
} catch (e) {}
}
if (key === name) {
result = cookie;
break;
}
if (!key) {
result[name] = cookie;
}
} catch (e) {}
}
return result;
}
api.set = api;
api.get = function (key) {
return api.call(api, key);
};
api.getJSON = function () {
return api.apply({
json: true
}, [].slice.call(arguments));
};
api.defaults = {};
api.remove = function (key, attributes) {
api(key, '', extend(attributes, {
expires: -1
}));
};
api.withConverter = init;
return api;
}
return init(function () {});
}));

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,227 +0,0 @@
/*! svg.draggable.js - v2.2.1 - 2016-08-25
* https://github.com/wout/svg.draggable.js
* Copyright (c) 2016 Wout Fierens; Licensed MIT */
;(function() {
// creates handler, saves it
function DragHandler(el){
el.remember('_draggable', this)
this.el = el
}
// Sets new parameter, starts dragging
DragHandler.prototype.init = function(constraint, val){
var _this = this
this.constraint = constraint
this.value = val
this.el.on('mousedown.drag', function(e){ _this.start(e) })
this.el.on('touchstart.drag', function(e){ _this.start(e) })
}
// transforms one point from screen to user coords
DragHandler.prototype.transformPoint = function(event, offset){
event = event || window.event
var touches = event.changedTouches && event.changedTouches[0] || event
this.p.x = touches.pageX - (offset || 0)
this.p.y = touches.pageY
return this.p.matrixTransform(this.m)
}
// gets elements bounding box with special handling of groups, nested and use
DragHandler.prototype.getBBox = function(){
var box = this.el.bbox()
if(this.el instanceof SVG.Nested) box = this.el.rbox()
if (this.el instanceof SVG.G || this.el instanceof SVG.Use || this.el instanceof SVG.Nested) {
box.x = this.el.x()
box.y = this.el.y()
}
return box
}
// start dragging
DragHandler.prototype.start = function(e){
// check for left button
if(e.type == 'click'|| e.type == 'mousedown' || e.type == 'mousemove'){
if((e.which || e.buttons) != 1){
return
}
}
var _this = this
// fire beforedrag event
this.el.fire('beforedrag', { event: e, handler: this })
// search for parent on the fly to make sure we can call
// draggable() even when element is not in the dom currently
this.parent = this.parent || this.el.parent(SVG.Nested) || this.el.parent(SVG.Doc)
this.p = this.parent.node.createSVGPoint()
// save current transformation matrix
this.m = this.el.node.getScreenCTM().inverse()
var box = this.getBBox()
var anchorOffset;
// fix text-anchor in text-element (#37)
if(this.el instanceof SVG.Text){
anchorOffset = this.el.node.getComputedTextLength();
switch(this.el.attr('text-anchor')){
case 'middle':
anchorOffset /= 2;
break
case 'start':
anchorOffset = 0;
break;
}
}
this.startPoints = {
// We take absolute coordinates since we are just using a delta here
point: this.transformPoint(e, anchorOffset),
box: box,
transform: this.el.transform()
}
// add drag and end events to window
SVG.on(window, 'mousemove.drag', function(e){ _this.drag(e) })
SVG.on(window, 'touchmove.drag', function(e){ _this.drag(e) })
SVG.on(window, 'mouseup.drag', function(e){ _this.end(e) })
SVG.on(window, 'touchend.drag', function(e){ _this.end(e) })
// fire dragstart event
this.el.fire('dragstart', {event: e, p: this.startPoints.point, m: this.m, handler: this})
// prevent browser drag behavior
e.preventDefault()
// prevent propagation to a parent that might also have dragging enabled
e.stopPropagation();
}
// while dragging
DragHandler.prototype.drag = function(e){
var box = this.getBBox()
, p = this.transformPoint(e)
, x = this.startPoints.box.x + p.x - this.startPoints.point.x
, y = this.startPoints.box.y + p.y - this.startPoints.point.y
, c = this.constraint
, gx = p.x - this.startPoints.point.x
, gy = p.y - this.startPoints.point.y
var event = new CustomEvent('dragmove', {
detail: {
event: e
, p: p
, m: this.m
, handler: this
}
, cancelable: true
})
this.el.fire(event)
if(event.defaultPrevented) return p
// move the element to its new position, if possible by constraint
if (typeof c == 'function') {
var coord = c.call(this.el, x, y, this.m)
// bool, just show us if movement is allowed or not
if (typeof coord == 'boolean') {
coord = {
x: coord,
y: coord
}
}
// if true, we just move. If !false its a number and we move it there
if (coord.x === true) {
this.el.x(x)
} else if (coord.x !== false) {
this.el.x(coord.x)
}
if (coord.y === true) {
this.el.y(y)
} else if (coord.y !== false) {
this.el.y(coord.y)
}
} else if (typeof c == 'object') {
// keep element within constrained box
if (c.minX != null && x < c.minX)
x = c.minX
else if (c.maxX != null && x > c.maxX - box.width){
x = c.maxX - box.width
}if (c.minY != null && y < c.minY)
y = c.minY
else if (c.maxY != null && y > c.maxY - box.height)
y = c.maxY - box.height
if(this.el instanceof SVG.G)
this.el.matrix(this.startPoints.transform).transform({x:gx, y: gy}, true)
else
this.el.move(x, y)
}
// so we can use it in the end-method, too
return p
}
DragHandler.prototype.end = function(e){
// final drag
var p = this.drag(e);
// fire dragend event
this.el.fire('dragend', { event: e, p: p, m: this.m, handler: this })
// unbind events
SVG.off(window, 'mousemove.drag')
SVG.off(window, 'touchmove.drag')
SVG.off(window, 'mouseup.drag')
SVG.off(window, 'touchend.drag')
}
SVG.extend(SVG.Element, {
// Make element draggable
// Constraint might be an object (as described in readme.md) or a function in the form "function (x, y)" that gets called before every move.
// The function can return a boolean or an object of the form {x, y}, to which the element will be moved. "False" skips moving, true moves to raw x, y.
draggable: function(value, constraint) {
// Check the parameters and reassign if needed
if (typeof value == 'function' || typeof value == 'object') {
constraint = value
value = true
}
var dragHandler = this.remember('_draggable') || new DragHandler(this)
// When no parameter is given, value is true
value = typeof value === 'undefined' ? true : value
if(value) dragHandler.init(constraint || {}, value)
else {
this.off('mousedown.drag')
this.off('touchstart.drag')
}
return this
}
})
}).call(this);

@ -1,442 +0,0 @@
/*! svg.draw.js - v2.0.3 - 2017-06-19
* https://github.com/svgdotjs/svg.draw.js
* Copyright (c) 2017 Ulrich-Matthias Schäfer; Licensed MIT */
;(function () {
// Our Object which manages drawing
function PaintHandler(el, event, options) {
this.el = el;
el.remember('_paintHandler', this);
var _this = this,
plugin = this.getPlugin();
this.parent = el.parent(SVG.Nested) || el.parent(SVG.Doc);
this.p = this.parent.node.createSVGPoint(); // Helping point for coord transformation
this.m = null; // transformation matrix. We get it when drawing starts
this.startPoint = null;
this.lastUpdateCall = null;
this.options = {};
// Merge options and defaults
for (var i in this.el.draw.defaults) {
this.options[i] = this.el.draw.defaults[i];
if (typeof options[i] !== 'undefined') {
this.options[i] = options[i];
}
}
if(plugin.point) {
plugin['pointPlugin'] = plugin.point;
delete plugin.point;
}
// Import all methods from plugin into object
for (var i in plugin){
this[i] = plugin[i];
}
// When we got an event, we use this for start, otherwise we use the click-event as default
if (!event) {
this.parent.on('click.draw', function (e) {
_this.start(e);
});
}
}
PaintHandler.prototype.transformPoint = function(x, y){
this.p.x = x - (this.offset.x - window.pageXOffset);
this.p.y = y - (this.offset.y - window.pageYOffset);
return this.p.matrixTransform(this.m);
}
PaintHandler.prototype.start = function (event) {
var _this = this;
// get the current transform matrix from screen to element (offset corrected)
this.m = this.el.node.getScreenCTM().inverse();
// we save the current scrolling-offset here
this.offset = { x: window.pageXOffset, y: window.pageYOffset };
// we want to snap in screen-coords, so we have to scale the snapToGrid accordingly
this.options.snapToGrid *= Math.sqrt(this.m.a * this.m.a + this.m.b * this.m.b)
// save the startpoint
this.startPoint = this.snapToGrid(this.transformPoint(event.clientX, event.clientY));
// the plugin may do some initial work
if(this.init){ this.init(event); }
// Fire our `drawstart`-event. We send the offset-corrected cursor-position along
this.el.fire('drawstart', {event:event, p:this.p, m:this.m});
// We need to bind the update-function to the mousemove event to keep track of the cursor
SVG.on(window, 'mousemove.draw', function (e) {
_this.update(e);
});
// Every consecutive call to start should map to point now
this.start = this.point;
};
// This function draws a point if the element is a polyline or polygon
// Otherwise it will just stop drawing the shape cause we are done
PaintHandler.prototype.point = function (event) {
if (this.point != this.start) return this.start(event);
if (this.pointPlugin) {
return this.pointPlugin(event);
}
// If this function is not overwritten we just call stop
this.stop(event);
};
// The stop-function does the cleanup work
PaintHandler.prototype.stop = function (event) {
if (event) {
this.update(event);
}
// Plugin may want to clean something
if(this.clean){ this.clean(); }
// Unbind from all events
SVG.off(window, 'mousemove.draw');
this.parent.off('click.draw');
// remove Refernce to PaintHandler
this.el.forget('_paintHandler');
// overwrite draw-function since we never need it again for this element
this.el.draw = function () {
};
// Fire the `drawstop`-event
this.el.fire('drawstop');
};
// Updates the element while moving the cursor
PaintHandler.prototype.update = function (event) {
if(!event && this.lastUpdateCall){
event = this.lastUpdateCall;
}
this.lastUpdateCall = event;
// Call the calc-function which calculates the new position and size
this.calc(event);
// Fire the `drawupdate`-event
this.el.fire('drawupdate', {event:event, p:this.p, m:this.m});
};
// Called from outside. Finishs a poly-element
PaintHandler.prototype.done = function () {
this.calc();
this.stop();
this.el.fire('drawdone');
};
// Called from outside. Cancels a poly-element
PaintHandler.prototype.cancel = function () {
// stop drawing and remove the element
this.stop();
this.el.remove();
this.el.fire('drawcancel');
};
// Calculate the corrected position when using `snapToGrid`
PaintHandler.prototype.snapToGrid = function (draw) {
var temp = null;
// An array was given. Loop through every element
if (draw.length) {
temp = [draw[0] % this.options.snapToGrid, draw[1] % this.options.snapToGrid];
draw[0] -= temp[0] < this.options.snapToGrid / 2 ? temp[0] : temp[0] - this.options.snapToGrid;
draw[1] -= temp[1] < this.options.snapToGrid / 2 ? temp[1] : temp[1] - this.options.snapToGrid;
return draw;
}
// Properties of element were given. Snap them all
for (var i in draw) {
temp = draw[i] % this.options.snapToGrid;
draw[i] -= (temp < this.options.snapToGrid / 2 ? temp : temp - this.options.snapToGrid) + (temp < 0 ? this.options.snapToGrid : 0);
}
return draw;
};
PaintHandler.prototype.param = function (key, value) {
this.options[key] = value === null ? this.el.draw.defaults[key] : value;
this.update();
};
// Returns the plugin
PaintHandler.prototype.getPlugin = function () {
return this.el.draw.plugins[this.el.type];
};
SVG.extend(SVG.Element, {
// Draw element with mouse
draw: function (event, options, value) {
// sort the parameters
if (!(event instanceof Event || typeof event === 'string')) {
options = event;
event = null;
}
// get the old Handler or create a new one from event and options
var paintHandler = this.remember('_paintHandler') || new PaintHandler(this, event, options || {});
// When we got an event we have to start/continue drawing
if (event instanceof Event) {
paintHandler['start'](event);
}
// if event is located in our PaintHandler we handle it as method
if (paintHandler[event]) {
paintHandler[event](options, value);
}
return this;
}
});
// Default values. Can be changed for the whole project if needed
SVG.Element.prototype.draw.defaults = {
snapToGrid: 1 // Snaps to a grid of `snapToGrid` px
};
SVG.Element.prototype.draw.extend = function(name, obj){
var plugins = {};
if(typeof name === 'string'){
plugins[name] = obj;
}else{
plugins = name;
}
for(var shapes in plugins){
var shapesArr = shapes.trim().split(/\s+/);
for(var i in shapesArr){
SVG.Element.prototype.draw.plugins[shapesArr[i]] = plugins[shapes];
}
}
};
// Container for all types not specified here
SVG.Element.prototype.draw.plugins = {};
SVG.Element.prototype.draw.extend('rect image', {
init:function(e){
var p = this.startPoint;
this.el.attr({ x: p.x, y: p.y, height: 0, width: 0 });
},
calc:function (e) {
var rect = {
x: this.startPoint.x,
y: this.startPoint.y
}, p = this.transformPoint(e.clientX, e.clientY);
rect.width = p.x - rect.x;
rect.height = p.y - rect.y;
// Snap the params to the grid we specified
this.snapToGrid(rect);
// When width is less than zero, we have to draw to the left
// which means we have to move the start-point to the left
if (rect.width < 0) {
rect.x = rect.x + rect.width;
rect.width = -rect.width;
}
// ...same with height
if (rect.height < 0) {
rect.y = rect.y + rect.height;
rect.height = -rect.height;
}
// draw the element
this.el.attr(rect);
}
});
SVG.Element.prototype.draw.extend('line polyline polygon', {
init:function(e){
// When we draw a polygon, we immediately need 2 points.
// One start-point and one point at the mouse-position
this.set = new SVG.Set();
var p = this.startPoint,
arr = [
[p.x, p.y],
[p.x, p.y]
];
this.el.plot(arr);
// We draw little circles around each point
// This is absolutely not needed and maybe removed in a later release
this.drawCircles();
},
// The calc-function sets the position of the last point to the mouse-position (with offset ofc)
calc:function (e) {
var arr = this.el.array().valueOf();
arr.pop();
if (e) {
var p = this.transformPoint(e.clientX, e.clientY);
arr.push(this.snapToGrid([p.x, p.y]));
}
this.el.plot(arr);
},
point:function(e){
if (this.el.type.indexOf('poly') > -1) {
// Add the new Point to the point-array
var p = this.transformPoint(e.clientX, e.clientY),
arr = this.el.array().valueOf();
arr.push(this.snapToGrid([p.x, p.y]));
this.el.plot(arr);
this.drawCircles();
// Fire the `drawpoint`-event, which holds the coords of the new Point
this.el.fire('drawpoint', {event:e, p:{x:p.x, y:p.y}, m:this.m});
return;
}
// We are done, if the element is no polyline or polygon
this.stop(e);
},
clean:function(){
// Remove all circles
this.set.each(function () {
this.remove();
});
this.set.clear();
delete this.set;
},
drawCircles:function () {
var array = this.el.array().valueOf()
this.set.each(function () {
this.remove();
});
this.set.clear();
for (var i = 0; i < array.length; ++i) {
this.p.x = array[i][0]
this.p.y = array[i][1]
var p = this.p.matrixTransform(this.parent.node.getScreenCTM().inverse().multiply(this.el.node.getScreenCTM()));
this.set.add(this.parent.circle(5).stroke({width: 1}).fill('#ccc').center(p.x, p.y));
}
}
});
SVG.Element.prototype.draw.extend('circle', {
init:function(e){
var p = this.startPoint;
this.el.attr({ cx: p.x, cy: p.y, r: 1 });
},
// We determine the radius by the cursor position
calc:function (e) {
var p = this.transformPoint(e.clientX, e.clientY),
circle = {
cx: this.startPoint.x,
cy: this.startPoint.y,
// calculating the radius
r: Math.sqrt(
(p.x - this.startPoint.x) * (p.x - this.startPoint.x) +
(p.y - this.startPoint.y) * (p.y - this.startPoint.y)
)
};
this.snapToGrid(circle);
this.el.attr(circle);
}
});
SVG.Element.prototype.draw.extend('ellipse', {
init:function(e){
// We start with a circle with radius 1 at the position of the cursor
var p = this.startPoint;
this.el.attr({ cx: p.x, cy: p.y, rx: 1, ry: 1 });
},
calc:function (e) {
var p = this.transformPoint(e.clientX, e.clientY);
var ellipse = {
cx: this.startPoint.x,
cy: this.startPoint.y,
rx: Math.abs(p.x - this.startPoint.x),
ry: Math.abs(p.y - this.startPoint.y)
};
this.snapToGrid(ellipse);
this.el.attr(ellipse);
}
});
}).call(this);

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,417 +0,0 @@
/*!
* svg.select.js - An extension of svg.js which allows to select elements with mouse
* @version 3.0.0
* https://github.com/svgdotjs/svg.select.js
*
* @copyright Ulrich-Matthias Schäfer
* @license MIT
*/;
;(function() {
"use strict";
function SelectHandler(el) {
this.el = el;
el.remember('_selectHandler', this);
this.pointSelection = {isSelected: false};
this.rectSelection = {isSelected: false};
// helper list with position settings of each type of point
this.pointsList = {
lt: [ 0, 0 ],
rt: [ 'width', 0 ],
rb: [ 'width', 'height' ],
lb: [ 0, 'height' ],
t: [ 'width / 2', 0 ],
r: [ 'width', 'height / 2' ],
b: [ 'width / 2', 'height' ],
l: [ 0, 'height / 2' ]
};
// helper function to evaluate point coordinates based on settings above and an object (bbox in our case)
this.pointCoord = function (setting, object) {
return typeof setting !== 'string' ? setting : eval('object.' + setting)
}
this.pointCoords = function (point, object) {
var settings = this.pointsList[point];
return {
x: this.pointCoord(settings[0], object),
y: this.pointCoord(settings[1], object)
}
}
}
SelectHandler.prototype.init = function (value, options) {
var bbox = this.el.bbox();
this.options = {};
// store defaults list of points in order to verify users config
var points = this.el.selectize.defaults.points;
// Merging the defaults and the options-object together
for (var i in this.el.selectize.defaults) {
this.options[i] = this.el.selectize.defaults[i];
if (options[i] !== undefined) {
this.options[i] = options[i];
}
}
// prepare & validate list of points to be added (or excluded)
var pointsLists = ['points', 'pointsExclude'];
for (var i in pointsLists) {
var option = this.options[pointsLists[i]];
if (typeof option === 'string') {
if (option.length > 0) {
// if set as comma separated string list => convert it into an array
option = option.split(/\s*,\s*/i);
} else {
option = [];
}
} else if (typeof option === 'boolean' && pointsLists[i] === 'points') {
// this is not needed, but let's have it for legacy support
option = option ? points : [];
}
this.options[pointsLists[i]] = option;
}
// intersect correct all points options with users config (exclude unwanted points)
// ES5 -> NO arrow functions nor Array.includes()
this.options.points = [ points, this.options.points ].reduce(
function (a, b) {
return a.filter(
function (c) {
return b.indexOf(c) > -1;
}
)
}
);
// exclude pointsExclude, if wanted
this.options.points = [ this.options.points, this.options.pointsExclude ].reduce(
function (a, b) {
return a.filter(
function (c) {
return b.indexOf(c) < 0;
}
)
}
);
this.parent = this.el.parent();
this.nested = (this.nested || this.parent.group());
this.nested.matrix(new SVG.Matrix(this.el).translate(bbox.x, bbox.y));
// When deepSelect is enabled and the element is a line/polyline/polygon, draw only points for moving
if (this.options.deepSelect && ['line', 'polyline', 'polygon'].indexOf(this.el.type) !== -1) {
this.selectPoints(value);
} else {
this.selectRect(value);
}
this.observe();
this.cleanup();
};
SelectHandler.prototype.selectPoints = function (value) {
this.pointSelection.isSelected = value;
// When set is already there we dont have to create one
if (this.pointSelection.set) {
return this;
}
// Create our set of elements
this.pointSelection.set = this.parent.set();
// draw the points and mark the element as selected
this.drawPoints();
return this;
};
// create the point-array which contains the 2 points of a line or simply the points-array of polyline/polygon
SelectHandler.prototype.getPointArray = function () {
var bbox = this.el.bbox();
return this.el.array().valueOf().map(function (el) {
return [el[0] - bbox.x, el[1] - bbox.y];
});
};
// Draws a points
SelectHandler.prototype.drawPoints = function () {
var _this = this, array = this.getPointArray();
// go through the array of points
for (var i = 0, len = array.length; i < len; ++i) {
var curriedEvent = (function (k) {
return function (ev) {
ev = ev || window.event;
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
ev.stopPropagation();
var x = ev.pageX || ev.touches[0].pageX;
var y = ev.pageY || ev.touches[0].pageY;
_this.el.fire('point', {x: x, y: y, i: k, event: ev});
};
})(i);
// add every point to the set
// add css-classes and a touchstart-event which fires our event for moving points
var point = this.drawPoint(array[i][0], array[i][1])
.addClass(this.options.classPoints)
.addClass(this.options.classPoints + '_point')
.on('touchstart', curriedEvent)
.on('mousedown', curriedEvent)
this.pointSelection.set.add(point);
}
};
// The function to draw single point
SelectHandler.prototype.drawPoint = function (cx, cy) {
var pointType = this.options.pointType;
switch (pointType) {
case 'circle':
return this.drawCircle(cx, cy);
case 'rect':
return this.drawRect(cx, cy);
default:
if (typeof pointType === 'function') {
return pointType.call(this, cx, cy);
}
throw new Error('Unknown ' + pointType + ' point type!');
}
};
// The function to draw the circle point
SelectHandler.prototype.drawCircle = function (cx, cy) {
return this.nested.circle(this.options.pointSize)
.center(cx, cy);
};
// The function to draw the rect point
SelectHandler.prototype.drawRect = function (cx, cy) {
return this.nested.rect(this.options.pointSize, this.options.pointSize)
.center(cx, cy);
};
// every time a point is moved, we have to update the positions of our point
SelectHandler.prototype.updatePointSelection = function () {
var array = this.getPointArray();
this.pointSelection.set.each(function (i) {
if (this.cx() === array[i][0] && this.cy() === array[i][1]) {
return;
}
this.center(array[i][0], array[i][1]);
});
};
SelectHandler.prototype.updateRectSelection = function () {
var _this = this, bbox = this.el.bbox();
this.rectSelection.set.get(0).attr({
width: bbox.width,
height: bbox.height
});
// set.get(1) is always in the upper left corner. no need to move it
if (this.options.points.length) {
this.options.points.map(function (point, index) {
var coords = _this.pointCoords(point, bbox);
_this.rectSelection.set.get(index + 1).center(coords.x, coords.y);
});
}
if (this.options.rotationPoint) {
var length = this.rectSelection.set.length();
this.rectSelection.set.get(length - 1).center(bbox.width / 2, 20);
}
};
SelectHandler.prototype.selectRect = function (value) {
var _this = this, bbox = this.el.bbox();
this.rectSelection.isSelected = value;
// when set is already p
this.rectSelection.set = this.rectSelection.set || this.parent.set();
// helperFunction to create a mouse-down function which triggers the event specified in `eventName`
function getMoseDownFunc(eventName) {
return function (ev) {
ev = ev || window.event;
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
ev.stopPropagation();
var x = ev.pageX || ev.touches[0].pageX;
var y = ev.pageY || ev.touches[0].pageY;
_this.el.fire(eventName, {x: x, y: y, event: ev});
};
}
// create the selection-rectangle and add the css-class
if (!this.rectSelection.set.get(0)) {
this.rectSelection.set.add(this.nested.rect(bbox.width, bbox.height).addClass(this.options.classRect));
}
// Draw Points at the edges, if enabled
if (this.options.points.length && this.rectSelection.set.length() < 2) {
var ename ="touchstart", mname = "mousedown";
this.options.points.map(function (point, index) {
var coords = _this.pointCoords(point, bbox);
var pointElement = _this.drawPoint(coords.x, coords.y)
.attr('class', _this.options.classPoints + '_' + point)
.on(mname, getMoseDownFunc(point))
.on(ename, getMoseDownFunc(point));
_this.rectSelection.set.add(pointElement);
});
this.rectSelection.set.each(function () {
this.addClass(_this.options.classPoints);
});
}
// draw rotationPint, if enabled
if (this.options.rotationPoint && ((this.options.points && !this.rectSelection.set.get(9)) || (!this.options.points && !this.rectSelection.set.get(1)))) {
var curriedEvent = function (ev) {
ev = ev || window.event;
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
ev.stopPropagation();
var x = ev.pageX || ev.touches[0].pageX;
var y = ev.pageY || ev.touches[0].pageY;
_this.el.fire('rot', {x: x, y: y, event: ev});
};
var pointElement = this.drawPoint(bbox.width / 2, 20)
.attr('class', this.options.classPoints + '_rot')
.on("touchstart", curriedEvent)
.on("mousedown", curriedEvent);
this.rectSelection.set.add(pointElement);
}
};
SelectHandler.prototype.handler = function () {
var bbox = this.el.bbox();
this.nested.matrix(new SVG.Matrix(this.el).translate(bbox.x, bbox.y));
if (this.rectSelection.isSelected) {
this.updateRectSelection();
}
if (this.pointSelection.isSelected) {
this.updatePointSelection();
}
};
SelectHandler.prototype.observe = function () {
var _this = this;
if (MutationObserver) {
if (this.rectSelection.isSelected || this.pointSelection.isSelected) {
this.observerInst = this.observerInst || new MutationObserver(function () {
_this.handler();
});
this.observerInst.observe(this.el.node, {attributes: true});
} else {
try {
this.observerInst.disconnect();
delete this.observerInst;
} catch (e) {
}
}
} else {
this.el.off('DOMAttrModified.select');
if (this.rectSelection.isSelected || this.pointSelection.isSelected) {
this.el.on('DOMAttrModified.select', function () {
_this.handler();
});
}
}
};
SelectHandler.prototype.cleanup = function () {
//var _this = this;
if (!this.rectSelection.isSelected && this.rectSelection.set) {
// stop watching the element, remove the selection
this.rectSelection.set.each(function () {
this.remove();
});
this.rectSelection.set.clear();
delete this.rectSelection.set;
}
if (!this.pointSelection.isSelected && this.pointSelection.set) {
// Remove all points, clear the set, stop watching the element
this.pointSelection.set.each(function () {
this.remove();
});
this.pointSelection.set.clear();
delete this.pointSelection.set;
}
if (!this.pointSelection.isSelected && !this.rectSelection.isSelected) {
this.nested.remove();
delete this.nested;
}
};
SVG.extend(SVG.Element, {
// Select element with mouse
selectize: function (value, options) {
// Check the parameters and reassign if needed
if (typeof value === 'object') {
options = value;
value = true;
}
var selectHandler = this.remember('_selectHandler') || new SelectHandler(this);
selectHandler.init(value === undefined ? true : value, options || {});
return this;
}
});
SVG.Element.prototype.selectize.defaults = {
points: ['lt', 'rt', 'rb', 'lb', 't', 'r', 'b', 'l'], // which points to draw, default all
pointsExclude: [], // easier option if to exclude few than rewrite all
classRect: 'svg_select_boundingRect', // Css-class added to the rect
classPoints: 'svg_select_points', // Css-class added to the points
pointSize: 7, // size of point
rotationPoint: true, // If true, rotation point is drawn. Needed for rotation!
deepSelect: false, // If true, moving of single points is possible (only line, polyline, polyon)
pointType: 'circle' // Point type: circle or rect, default circle
};
}());

@ -1,439 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported AnnotationParser */
/* global
PolyShapeModel:false
LabelsInfo:false
*/
class AnnotationParser {
constructor(job, labelsInfo) {
this._parser = new DOMParser();
this._startFrame = job.start;
this._stopFrame = job.stop;
this._im_meta = job.image_meta_data;
this._labelsInfo = labelsInfo;
}
_xmlParseError(parsedXML) {
return parsedXML.getElementsByTagName('parsererror');
}
_getBoxPosition(box, frame) {
frame = Math.min(frame - this._startFrame, this._im_meta.length - 1);
const imWidth = this._im_meta[frame].width;
const imHeight = this._im_meta[frame].height;
const xtl = +box.getAttribute("xtl");
const ytl = +box.getAttribute("ytl");
const xbr = +box.getAttribute("xbr");
const ybr = +box.getAttribute("ybr");
if (xtl < 0 || ytl < 0 || xbr < 0 || ybr < 0
|| xtl > imWidth || ytl > imHeight || xbr > imWidth || ybr > imHeight) {
const message = `Incorrect bb found in annotation file: xtl=${xtl} `
+ `ytl=${ytl} xbr=${xbr} ybr=${ybr}. `
+ `Box out of range: ${imWidth}x${imHeight}`;
throw Error(message);
}
const occluded = box.getAttribute('occluded');
const zOrder = box.getAttribute('z_order') || '0';
return [[xtl, ytl, xbr, ybr], +occluded, +zOrder];
}
_getPolyPosition(shape, frame) {
frame = Math.min(frame - this._startFrame, this._im_meta.length - 1);
const imWidth = this._im_meta[frame].width;
const imHeight = this._im_meta[frame].height;
let points = shape.getAttribute('points').split(';').join(' ');
points = PolyShapeModel.convertStringToNumberArray(points);
for (const point of points) {
if (point.x < 0 || point.y < 0 || point.x > imWidth || point.y > imHeight) {
const message = `Incorrect point found in annotation file x=${point.x} `
+ `y=${point.y}. Point out of range ${imWidth}x${imHeight}`;
throw Error(message);
}
}
points = points.reduce((acc, el) => {
acc.push(el.x, el.y);
return acc;
}, []);
const occluded = shape.getAttribute('occluded');
const zOrder = shape.getAttribute('z_order') || '0';
return [points, +occluded, +zOrder];
}
_getAttribute(labelId, attrTag) {
const name = attrTag.getAttribute('name');
const attrId = this._labelsInfo.attrIdOf(labelId, name);
if (attrId === null) {
throw Error(`An unknown attribute found in the annotation file: ${name}`);
}
const attrInfo = this._labelsInfo.attrInfo(attrId);
const value = LabelsInfo.normalize(attrInfo.type, attrTag.textContent);
if (['select', 'radio'].includes(attrInfo.type) && !attrInfo.values.includes(value)) {
throw Error(`Incorrect attribute value found for "${name}" + attribute: "${value}"`);
} else if (attrInfo.type === 'number') {
if (Number.isNaN(+value)) {
throw Error(`Incorrect attribute value found for "${name}" attribute: "${value}". Value must be a number.`);
} else {
const min = +attrInfo.values[0];
const max = +attrInfo.values[1];
if (+value < min || +value > max) {
throw Error(`Number attribute value out of range for "${name}" attribute: "${value}"`);
}
}
}
return [attrId, value];
}
_getAttributeList(shape, labelId) {
const attributeDict = {};
const attributes = shape.getElementsByTagName('attribute');
for (const attribute of attributes) {
const [id, value] = this._getAttribute(labelId, attribute);
attributeDict[id] = value;
}
const attributeList = [];
for (const attrId in attributeDict) {
if (Object.prototype.hasOwnProperty.call(attributeDict, attrId)) {
attributeList.push({
spec_id: attrId,
value: attributeDict[attrId],
});
}
}
return attributeList;
}
_getShapeFromPath(shapeType, tracks) {
const result = [];
for (const track of tracks) {
const label = track.getAttribute('label');
const group = track.getAttribute('group_id') || '0';
const labelId = this._labelsInfo.labelIdOf(label);
if (labelId === null) {
throw Error(`An unknown label found in the annotation file: ${label}`);
}
const shapes = Array.from(track.getElementsByTagName(shapeType));
shapes.sort((a, b) => +a.getAttribute('frame') - +b.getAttribute('frame'));
while (shapes.length && +shapes[0].getAttribute('outside')) {
shapes.shift();
}
if (shapes.length === 2) {
if (shapes[1].getAttribute('frame') - shapes[0].getAttribute('frame') === 1
&& !+shapes[0].getAttribute('outside') && +shapes[1].getAttribute('outside')) {
shapes[0].setAttribute('label', label);
shapes[0].setAttribute('group_id', group);
result.push(shapes[0]);
}
}
}
return result;
}
_parseAnnotationData(xml) {
const data = {
boxes: [],
polygons: [],
polylines: [],
points: [],
cuboids: [],
};
const tracks = xml.getElementsByTagName('track');
const parsed = {
box: this._getShapeFromPath('box', tracks),
polygon: this._getShapeFromPath('polygon', tracks),
polyline: this._getShapeFromPath('polyline', tracks),
points: this._getShapeFromPath('points', tracks),
cuboid: this._getShapeFromPath('cuboid', tracks),
};
const shapeTarget = {
box: 'boxes',
polygon: 'polygons',
polyline: 'polylines',
points: 'points',
cuboid: 'cuboids',
};
const images = xml.getElementsByTagName('image');
for (const image of images) {
const frame = image.getAttribute('id');
for (const box of image.getElementsByTagName('box')) {
box.setAttribute('frame', frame);
parsed.box.push(box);
}
for (const polygon of image.getElementsByTagName('polygon')) {
polygon.setAttribute('frame', frame);
parsed.polygon.push(polygon);
}
for (const polyline of image.getElementsByTagName('polyline')) {
polyline.setAttribute('frame', frame);
parsed.polyline.push(polyline);
}
for (const points of image.getElementsByTagName('points')) {
points.setAttribute('frame', frame);
parsed.points.push(points);
}
for (const cuboid of image.getElementsByTagName('cuboid')) {
cuboid.setAttribute('frame', frame);
parsed.cuboid.push(cuboid);
}
}
for (const shapeType in parsed) {
if (Object.prototype.hasOwnProperty.call(parsed, shapeType)) {
for (const shape of parsed[shapeType]) {
const frame = +shape.getAttribute('frame');
if (frame < this._startFrame || frame > this._stopFrame) {
continue;
}
const labelId = this._labelsInfo.labelIdOf(shape.getAttribute('label'));
const group = shape.getAttribute('group_id') || '0';
if (labelId === null) {
throw Error(`An unknown label found in the annotation file: "${shape.getAttribute('label')}"`);
}
const attributeList = this._getAttributeList(shape, labelId);
if (shapeType === 'box') {
const [points, occluded, zOrder] = this._getBoxPosition(shape, frame);
data[shapeTarget[shapeType]].push({
label_id: labelId,
group: +group,
attributes: attributeList,
type: 'rectangle',
z_order: zOrder,
frame,
occluded,
points,
});
} else if (shapeType === 'cuboid') {
const [points, occluded, zOrder] = this._getPolyPosition(shape, frame);
data[shapeTarget[shapeType]].push({
label_id: labelId,
group: +group,
attributes: attributeList,
type: 'cuboid',
z_order: zOrder,
frame,
occluded,
points,
});
} else {
const [points, occluded, zOrder] = this._getPolyPosition(shape, frame);
data[shapeTarget[shapeType]].push({
label_id: labelId,
group: +group,
attributes: attributeList,
type: shapeType,
z_order: zOrder,
frame,
points,
occluded,
});
}
}
}
}
return data;
}
_parseInterpolationData(xml) {
const data = {
box_paths: [],
polygon_paths: [],
polyline_paths: [],
points_paths: [],
};
const tracks = xml.getElementsByTagName('track');
for (const track of tracks) {
const labelId = this._labelsInfo.labelIdOf(track.getAttribute('label'));
const group = track.getAttribute('group_id') || '0';
if (labelId === null) {
throw Error(`An unknown label found in the annotation file: "${track.getAttribute('label')}"`);
}
const parsed = {
box: Array.from(track.getElementsByTagName('box')),
polygon: Array.from(track.getElementsByTagName('polygon')),
polyline: Array.from(track.getElementsByTagName('polyline')),
points: Array.from(track.getElementsByTagName('points')),
};
for (const shapeType in parsed) {
if (Object.prototype.hasOwnProperty.call(parsed, shapeType)) {
const shapes = parsed[shapeType];
shapes.sort((a, b) => +a.getAttribute('frame') - +b.getAttribute('frame'));
while (shapes.length && +shapes[0].getAttribute('outside')) {
shapes.shift();
}
if (shapes.length === 2) {
if (shapes[1].getAttribute('frame') - shapes[0].getAttribute('frame') === 1
&& !+shapes[0].getAttribute('outside') && +shapes[1].getAttribute('outside')) {
// pseudo interpolation track (actually is annotation)
parsed[shapeType] = [];
}
}
}
}
let type = null;
let target = null;
if (parsed.box.length) {
type = 'box';
target = 'box_paths';
} else if (parsed.polygon.length) {
type = 'polygon';
target = 'polygon_paths';
} else if (parsed.polyline.length) {
type = 'polyline';
target = 'polyline_paths';
} else if (parsed.points.length) {
type = 'points';
target = 'points_paths';
} else {
continue;
}
const path = {
label_id: labelId,
group: +group,
frame: +parsed[type][0].getAttribute('frame'),
attributes: [],
shapes: [],
};
if (path.frame > this._stopFrame) {
continue;
}
for (const shape of parsed[type]) {
const keyFrame = +shape.getAttribute('keyframe');
const outside = +shape.getAttribute('outside');
const frame = +shape.getAttribute('frame');
/*
All keyframes are significant.
All shapes on first segment frame also significant.
Ignore all frames less then start.
Ignore all frames more then stop.
*/
const significant = (keyFrame || frame === this._startFrame)
&& frame >= this._startFrame && frame <= this._stopFrame;
if (significant) {
const attributeList = this._getAttributeList(shape, labelId);
const shapeAttributes = [];
const pathAttributes = [];
for (const attr of attributeList) {
const attrInfo = this._labelsInfo.attrInfo(attr.spec_id);
if (attrInfo.mutable) {
shapeAttributes.push({
spec_id: attr.spec_id,
value: attr.value,
});
} else {
pathAttributes.push({
spec_id: attr.spec_id,
value: attr.value,
});
}
}
path.attributes = pathAttributes;
if (type === 'box') {
const [points, occluded, zOrder] = this._getBoxPosition(shape,
Math.clamp(frame, this._startFrame, this._stopFrame));
path.shapes.push({
attributes: shapeAttributes,
type: 'rectangle',
frame,
occluded,
outside,
points,
zOrder,
});
} else {
const [points, occluded, zOrder] = this._getPolyPosition(shape,
Math.clamp(frame, this._startFrame, this._stopFrame));
path.shapes.push({
attributes: shapeAttributes,
type,
frame,
occluded,
outside,
points,
zOrder,
});
}
}
}
if (path.shapes.length) {
data[target].push(path);
}
}
return data;
}
parse(text) {
const xml = this._parser.parseFromString(text, 'text/xml');
const parseerror = this._xmlParseError(xml);
if (parseerror.length) {
throw Error(`Annotation page parsing error. ${parseerror[0].innerText}`);
}
const interpolationData = this._parseInterpolationData(xml);
const annotationData = this._parseAnnotationData(xml);
const data = {
shapes: [],
tracks: [],
};
for (const type in interpolationData) {
if (Object.prototype.hasOwnProperty.call(interpolationData, type)) {
Array.prototype.push.apply(data.tracks, interpolationData[type]);
}
}
for (const type in annotationData) {
if (Object.prototype.hasOwnProperty.call(annotationData, type)) {
Array.prototype.push.apply(data.shapes, annotationData[type]);
}
}
return data;
}
}

@ -1,452 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported buildAnnotationSaver */
/* global
showOverlay:false
showMessage:false
Listener:false
Logger:false
Mousetrap:false
*/
class AnnotationSaverModel extends Listener {
constructor(initialData, shapeCollection) {
super('onAnnotationSaverUpdate', () => this._state);
this._state = {
status: null,
message: null,
};
this._version = initialData.version;
this._shapeCollection = shapeCollection;
this._initialObjects = {};
this._resetState();
this.update();
// We need use data from export instead of initialData
// Otherwise we have differ keys order and JSON comparison code incorrect
const data = this._shapeCollection.export()[0];
for (const shape of data.shapes) {
this._initialObjects.shapes[shape.id] = shape;
}
for (const track of data.tracks) {
this._initialObjects.tracks[track.id] = track;
}
}
_resetState() {
this._initialObjects = {
shapes: {},
tracks: {},
};
}
update() {
this._hash = this._getHash();
}
async _request(data, action) {
return new Promise((resolve, reject) => {
$.ajax({
url: `/api/v1/jobs/${window.cvat.job.id}/annotations?action=${action}`,
type: 'PATCH',
data: JSON.stringify(data),
contentType: 'application/json',
}).done((savedData) => {
resolve(savedData);
}).fail((errorData) => {
const message = `Could not make ${action} annotations. Code: ${errorData.status}. `
+ `Message: ${errorData.responseText || errorData.statusText}`;
reject(new Error(message));
});
});
}
async _put(data) {
return new Promise((resolve, reject) => {
$.ajax({
url: `/api/v1/jobs/${window.cvat.job.id}/annotations`,
type: 'PUT',
data: JSON.stringify(data),
contentType: 'application/json',
}).done((savedData) => {
resolve(savedData);
}).fail((errorData) => {
const message = `Could not put annotations. Code: ${errorData.status}. `
+ `Message: ${errorData.responseText || errorData.statusText}`;
reject(new Error(message));
});
});
}
async _create(created) {
return this._request(created, 'create');
}
async _update(updated) {
return this._request(updated, 'update');
}
async _delete(deleted) {
return this._request(deleted, 'delete');
}
async _logs() {
Logger.addEvent(Logger.EventType.saveJob);
const totalStat = this._shapeCollection.collectStatistic()[1];
Logger.addEvent(Logger.EventType.sendTaskInfo, {
'track count': totalStat.boxes.annotation + totalStat.boxes.interpolation
+ totalStat.polygons.annotation + totalStat.polygons.interpolation
+ totalStat.polylines.annotation + totalStat.polylines.interpolation
+ totalStat.points.annotation + totalStat.points.interpolation,
'frame count': window.cvat.player.frames.stop - window.cvat.player.frames.start + 1,
'object count': totalStat.total,
'box count': totalStat.boxes.annotation + totalStat.boxes.interpolation,
'polygon count': totalStat.polygons.annotation + totalStat.polygons.interpolation,
'polyline count': totalStat.polylines.annotation + totalStat.polylines.interpolation,
'points count': totalStat.points.annotation + totalStat.points.interpolation,
});
const annotationLogs = Logger.getLogs();
return new Promise((resolve, reject) => {
$.ajax({
url: '/api/v1/server/logs',
type: 'POST',
data: JSON.stringify(annotationLogs.export()),
contentType: 'application/json',
}).done(() => {
resolve();
}).fail((errorData) => {
annotationLogs.save();
const message = `Could not send logs. Code: ${errorData.status}. `
+ `Message: ${errorData.responseText || errorData.statusText}`;
reject(new Error(message));
});
});
}
_split(exported) {
const created = {
version: this._version,
shapes: [],
tracks: [],
tags: [],
};
const updated = {
version: this._version + 1,
shapes: [],
tracks: [],
tags: [],
};
const deleted = {
version: this._version + 2,
shapes: [],
tracks: [],
tags: [],
};
const keys = ['id', 'label_id', 'group', 'frame',
'occluded', 'z_order', 'points', 'type', 'shapes',
'attributes', 'value', 'spec_id', 'outside'];
// Compare initial state objects and export state objects
// in order to get updated and created objects
for (const type of Object.keys(this._initialObjects)) {
for (const obj of exported[type]) {
if (obj.id in this._initialObjects[type]) {
const exportedHash = JSON.stringify(obj, keys);
const initialHash = JSON.stringify(this._initialObjects[type][obj.id], keys);
if (exportedHash !== initialHash) {
updated[type].push(obj);
}
} else if (typeof obj.id === 'undefined') {
created[type].push(obj);
} else {
throw Error(`Bad object ID found: ${obj.id}. `
+ 'It is not contained in initial state and have server ID');
}
}
}
const indexes = {
shapes: exported.shapes.map((object) => +object.id),
tracks: exported.tracks.map((object) => +object.id),
};
// Compare initial state indexes and export state indexes
// in order to get removed objects
for (const type of Object.keys(this._initialObjects)) {
for (const shapeID in this._initialObjects[type]) {
if (!indexes[type].includes(+shapeID)) {
const object = this._initialObjects[type][shapeID];
deleted[type].push(object);
}
}
}
return [created, updated, deleted];
}
_getHash() {
const exported = this._shapeCollection.export()[0];
return JSON.stringify(exported);
}
_updateCreatedObjects(objectsToSave, savedObjects, mapping) {
// Method setups IDs of created objects after saving on a server
const allSavedObjects = savedObjects.shapes.concat(savedObjects.tracks);
const allObjectsToSave = objectsToSave.shapes.concat(objectsToSave.tracks);
if (allSavedObjects.length !== allObjectsToSave.length) {
throw Error('Number of saved objects and objects to save is not match');
}
for (let idx = 0; idx < allSavedObjects.length; idx += 1) {
const objectModel = mapping.filter(el => el[0] === allObjectsToSave[idx])[0][1];
const { id } = allSavedObjects[idx];
objectModel.serverID = id;
allObjectsToSave[idx].id = id;
}
this._shapeCollection.update();
}
notify(status, message = null) {
this._state.status = status;
this._state.message = message;
Listener.prototype.notify.call(this);
}
hasUnsavedChanges() {
return this._getHash() !== this._hash;
}
async save() {
this.notify('saveStart');
try {
const [exported, mapping] = this._shapeCollection.export();
const { flush } = this._shapeCollection;
if (flush) {
const data = Object.assign({}, exported, {
version: this._version,
tags: [],
});
this._version += 1;
this.notify('saveCreated');
const savedObjects = await this._put(data);
this._updateCreatedObjects(exported, savedObjects, mapping);
this._shapeCollection.flush = false;
this._version = savedObjects.version;
this._resetState();
for (const type of Object.keys(this._initialObjects)) {
for (const object of savedObjects[type]) {
if (object.shapes) {
for (const shape of object.shapes) {
delete shape.id;
}
}
this._initialObjects[type][object.id] = object;
}
}
this._version = savedObjects.version;
} else {
const [created, updated, deleted] = this._split(exported);
this.notify('saveCreated');
const savedCreated = await this._create(created);
this._updateCreatedObjects(created, savedCreated, mapping);
this._version = savedCreated.version;
for (const type of Object.keys(this._initialObjects)) {
for (const object of savedCreated[type]) {
if (object.shapes) {
for (const shape of object.shapes) {
delete shape.id;
}
}
this._initialObjects[type][object.id] = object;
}
}
this.notify('saveUpdated');
const savedUpdated = await this._update(updated);
this._version = savedUpdated.version;
for (const type of Object.keys(this._initialObjects)) {
for (const object of savedUpdated[type]) {
if (object.shapes) {
for (const shape of object.shapes) {
delete shape.id;
}
}
this._initialObjects[type][object.id] = object;
}
}
this.notify('saveDeleted');
const savedDeleted = await this._delete(deleted);
this._version = savedDeleted.version;
for (const type of Object.keys(this._initialObjects)) {
for (const object of savedDeleted[type]) {
delete this._initialObjects[type][object.id];
}
}
this._version = savedDeleted.version;
}
await this._logs();
} catch (error) {
this.notify('saveUnlocked');
this.notify('saveError', error.message);
this._state = {
status: null,
message: null,
};
throw Error(error);
}
this._hash = this._getHash();
this.notify('saveDone');
setTimeout(() => {
this.notify('saveUnlocked');
this._state = {
status: null,
message: null,
};
}, 1000);
}
get state() {
return JSON.parse(JSON.stringify(this._state));
}
}
class AnnotationSaverController {
constructor(model) {
this._model = model;
this._autoSaveInterval = null;
const { shortkeys } = window.cvat.config;
Mousetrap.bind(shortkeys.save_work.value, Logger.shortkeyLogDecorator(
() => {
this.save();
return false;
},
), 'keydown');
}
autoSave(enabled, time) {
if (this._autoSaveInterval) {
clearInterval(this._autoSaveInterval);
this._autoSaveInterval = null;
}
if (enabled) {
this._autoSaveInterval = setInterval(() => {
this.save();
}, time * 1000 * 60);
}
}
hasUnsavedChanges() {
return this._model.hasUnsavedChanges();
}
save() {
if (this._model.state.status === null) {
this._model.save().catch((error) => {
setTimeout(() => {
throw error;
});
});
}
}
}
class AnnotationSaverView {
constructor(model, controller) {
model.subscribe(this);
this._controller = controller;
this._overlay = null;
const { shortkeys } = window.cvat.config;
const saveHelp = `${shortkeys.save_work.view_value} - ${shortkeys.save_work.description}`;
this._saveButton = $('#saveButton').on('click', () => {
this._controller.save();
}).attr('title', saveHelp);
this._autoSaveBox = $('#autoSaveBox').on('change', (e) => {
const enabled = e.target.checked;
const time = +this._autoSaveTime.prop('value');
this._controller.autoSave(enabled, time);
});
this._autoSaveTime = $('#autoSaveTime').on('change', (e) => {
e.target.value = Math.clamp(+e.target.value, +e.target.min, +e.target.max);
this._autoSaveBox.trigger('change');
});
window.onbeforeunload = (e) => {
if (this._controller.hasUnsavedChanges()) {
const message = 'You have unsaved changes. Leave this page?';
e.returnValue = message;
return message;
}
return null;
};
}
onAnnotationSaverUpdate(state) {
if (state.status === 'saveStart') {
this._overlay = showOverlay('Annotations are being saved..');
this._saveButton.prop('disabled', true).text('Saving..');
} else if (state.status === 'saveDone') {
this._saveButton.text('Successful save');
this._overlay.remove();
} else if (state.status === 'saveError') {
this._saveButton.prop('disabled', false).text('Save Work');
const message = `Couldn't to save the job. Errors occured: ${state.message}. `
+ 'Please report the problem to support team immediately.';
showMessage(message);
this._overlay.remove();
} else if (state.status === 'saveCreated') {
this._overlay.setMessage(`${this._overlay.getMessage()} <br /> - Created objects are being saved..`);
} else if (state.status === 'saveUpdated') {
this._overlay.setMessage(`${this._overlay.getMessage()} <br /> - Updated objects are being saved..`);
} else if (state.status === 'saveDeleted') {
this._overlay.setMessage(`${this._overlay.getMessage()} <br /> - Deleted objects are being saved..`);
} else if (state.status === 'saveUnlocked') {
this._saveButton.prop('disabled', false).text('Save Work');
} else {
const message = `Unknown state has been reached during annotation saving: ${state.status} `
+ 'Please report the problem to support team immediately.';
showMessage(message);
}
}
}
function buildAnnotationSaver(initialData, shapeCollection) {
const model = new AnnotationSaverModel(initialData, shapeCollection);
const controller = new AnnotationSaverController(model);
new AnnotationSaverView(model, controller);
return model;
}

@ -1,761 +0,0 @@
/*
* Copyright (C) 2018-2019 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported callAnnotationUI blurAllElements drawBoxSize copyToClipboard */
/* global
AAMController:false
AAMModel:false
AAMView:false
AnnotationParser:false
Config:false
userConfirm:false
CoordinateTranslator:false
dumpAnnotationRequest:false
HistoryController:false
HistoryModel:false
HistoryView:false
Logger:false
Mousetrap:false
PlayerController:false
PlayerModel:false
PlayerView:false
PolyshapeEditorController:false
PolyshapeEditorModel:false
PolyshapeEditorView:false
PolyShapeView:false
ShapeBufferController:false
ShapeBufferModel:false
ShapeBufferView:false
ShapeCollectionController:false
ShapeCollectionModel:false
ShapeCollectionView:false
ShapeCreatorController:false
ShapeCreatorModel:false
ShapeCreatorView:false
ShapeGrouperController:false
ShapeGrouperModel:false
ShapeGrouperView:false
ShapeMergerController:false
ShapeMergerModel:false
ShapeMergerView:false
showMessage:false
buildAnnotationSaver:false
LabelsInfo:false
uploadJobAnnotationRequest:false
isDefaultFormat:false
*/
async function initLogger(jobID) {
if (!Logger.initializeLogger(jobID)) {
const message = 'Logger has been already initialized';
console.error(message);
showMessage(message);
return;
}
Logger.setTimeThreshold(Logger.EventType.zoomImage);
}
function blurAllElements() {
document.activeElement.blur();
}
function uploadAnnotation(jobId, shapeCollectionModel, historyModel, annotationSaverModel,
uploadAnnotationButton, format) {
$('#annotationFileSelector').attr('accept',
format.ext.split(',').map(x => '.' + x.trimStart()).join(', '));
$('#annotationFileSelector').one('change', async (changedFileEvent) => {
const file = changedFileEvent.target.files['0'];
changedFileEvent.target.value = '';
if (!file) return;
uploadAnnotationButton.prop('disabled', true);
const annotationData = new FormData();
annotationData.append('annotation_file', file);
try {
await uploadJobAnnotationRequest(jobId, annotationData, format.name);
historyModel.empty();
shapeCollectionModel.empty();
const data = await $.get(`/api/v1/jobs/${jobId}/annotations`);
shapeCollectionModel.import(data);
shapeCollectionModel.update();
annotationSaverModel.update();
} catch (error) {
showMessage(error.message);
} finally {
uploadAnnotationButton.prop('disabled', false);
}
}).click();
}
function setupFrameFilters() {
const brightnessRange = $('#playerBrightnessRange');
const contrastRange = $('#playerContrastRange');
const saturationRange = $('#playerSaturationRange');
const canvasBackground = $('#canvasBackground');
const reset = $('#resetPlayerFilterButton');
let brightness = 100;
let contrast = 100;
let saturation = 100;
const { shortkeys } = window.cvat.config;
function updateFilterParameters() {
canvasBackground.css('filter', `contrast(${contrast}%) brightness(${brightness}%) saturate(${saturation}%)`);
}
brightnessRange.attr('title', `
${shortkeys.change_player_brightness.view_value} - ${shortkeys.change_player_brightness.description}`);
contrastRange.attr('title', `
${shortkeys.change_player_contrast.view_value} - ${shortkeys.change_player_contrast.description}`);
saturationRange.attr('title', `
${shortkeys.change_player_saturation.view_value} - ${shortkeys.change_player_saturation.description}`);
const changeBrightnessHandler = Logger.shortkeyLogDecorator((e) => {
if (e.shiftKey) {
brightnessRange.prop('value', brightness + 10).trigger('input');
} else {
brightnessRange.prop('value', brightness - 10).trigger('input');
}
});
const changeContrastHandler = Logger.shortkeyLogDecorator((e) => {
if (e.shiftKey) {
contrastRange.prop('value', contrast + 10).trigger('input');
} else {
contrastRange.prop('value', contrast - 10).trigger('input');
}
});
const changeSaturationHandler = Logger.shortkeyLogDecorator((e) => {
if (e.shiftKey) {
saturationRange.prop('value', saturation + 10).trigger('input');
} else {
saturationRange.prop('value', saturation - 10).trigger('input');
}
});
Mousetrap.bind(shortkeys.change_player_brightness.value, changeBrightnessHandler, 'keydown');
Mousetrap.bind(shortkeys.change_player_contrast.value, changeContrastHandler, 'keydown');
Mousetrap.bind(shortkeys.change_player_saturation.value, changeSaturationHandler, 'keydown');
reset.on('click', () => {
brightness = 100;
contrast = 100;
saturation = 100;
brightnessRange.prop('value', brightness);
contrastRange.prop('value', contrast);
saturationRange.prop('value', saturation);
updateFilterParameters();
});
brightnessRange.on('input', (e) => {
const value = Math.clamp(+e.target.value, +e.target.min, +e.target.max);
e.target.value = value;
brightness = value;
updateFilterParameters();
});
contrastRange.on('input', (e) => {
const value = Math.clamp(+e.target.value, +e.target.min, +e.target.max);
e.target.value = value;
contrast = value;
updateFilterParameters();
});
saturationRange.on('input', (e) => {
const value = Math.clamp(+e.target.value, +e.target.min, +e.target.max);
e.target.value = value;
saturation = value;
updateFilterParameters();
});
}
function setupShortkeys(shortkeys, models) {
const annotationMenu = $('#annotationMenu');
const settingsWindow = $('#settingsWindow');
const helpWindow = $('#helpWindow');
Mousetrap.prototype.stopCallback = () => false;
const openHelpHandler = Logger.shortkeyLogDecorator(() => {
const helpInvisible = helpWindow.hasClass('hidden');
if (helpInvisible) {
annotationMenu.addClass('hidden');
settingsWindow.addClass('hidden');
helpWindow.removeClass('hidden');
} else {
helpWindow.addClass('hidden');
}
return false;
});
const openSettingsHandler = Logger.shortkeyLogDecorator(() => {
const settingsInvisible = settingsWindow.hasClass('hidden');
if (settingsInvisible) {
annotationMenu.addClass('hidden');
helpWindow.addClass('hidden');
settingsWindow.removeClass('hidden');
} else {
$('#settingsWindow').addClass('hidden');
}
return false;
});
const cancelModeHandler = Logger.shortkeyLogDecorator(() => {
switch (window.cvat.mode) {
case 'aam':
models.aam.switchAAMMode();
break;
case 'creation':
models.shapeCreator.switchCreateMode(true);
break;
case 'merge':
models.shapeMerger.cancel();
break;
case 'groupping':
models.shapeGrouper.cancel();
break;
case 'paste':
models.shapeBuffer.switchPaste();
break;
case 'poly_editing':
models.shapeEditor.finish();
break;
default:
break;
}
return false;
});
Mousetrap.bind(shortkeys.open_help.value, openHelpHandler, 'keydown');
Mousetrap.bind(shortkeys.open_settings.value, openSettingsHandler, 'keydown');
Mousetrap.bind(shortkeys.cancel_mode.value, cancelModeHandler, 'keydown');
}
function setupHelpWindow(shortkeys) {
const closeHelpButton = $('#closeHelpButton');
const helpTable = $('#shortkeyHelpTable');
closeHelpButton.on('click', () => {
$('#helpWindow').addClass('hidden');
});
for (const key in shortkeys) {
if (Object.prototype.hasOwnProperty.call(shortkeys, key)) {
helpTable.append($(`<tr> <td> ${shortkeys[key].view_value} </td> <td> ${shortkeys[key].description} </td> </tr>`));
}
}
}
function setupSettingsWindow() {
const closeSettingsButton = $('#closeSettignsButton');
closeSettingsButton.on('click', () => {
$('#settingsWindow').addClass('hidden');
});
}
function setupMenu(job, task, shapeCollectionModel,
annotationParser, aamModel, playerModel, historyModel,
annotationFormats, annotationSaverModel) {
const annotationMenu = $('#annotationMenu');
const menuButton = $('#menuButton');
const downloadDropdownMenu = $('#downloadDropdownMenu');
function hide() {
annotationMenu.addClass('hidden');
downloadDropdownMenu.addClass('hidden');
}
function setupVisibility() {
let timer = null;
menuButton.on('click', () => {
const [byLabelsStat, totalStat] = shapeCollectionModel.collectStatistic();
const table = $('#annotationStatisticTable');
table.find('.temporaryStatisticRow').remove();
for (const labelId in byLabelsStat) {
if (Object.prototype.hasOwnProperty.call(byLabelsStat, labelId)) {
$(`<tr>
<td class="semiBold"> ${window.cvat.labelsInfo.labels()[labelId].normalize()} </td>
<td> ${byLabelsStat[labelId].boxes.annotation} </td>
<td> ${byLabelsStat[labelId].boxes.interpolation} </td>
<td> ${byLabelsStat[labelId].polygons.annotation} </td>
<td> ${byLabelsStat[labelId].polygons.interpolation} </td>
<td> ${byLabelsStat[labelId].polylines.annotation} </td>
<td> ${byLabelsStat[labelId].polylines.interpolation} </td>
<td> ${byLabelsStat[labelId].points.annotation} </td>
<td> ${byLabelsStat[labelId].points.interpolation} </td>
<td> ${byLabelsStat[labelId].cuboids.annotation} </td>
<td> ${byLabelsStat[labelId].cuboids.interpolation} </td>
<td> ${byLabelsStat[labelId].manually} </td>
<td> ${byLabelsStat[labelId].interpolated} </td>
<td class="semiBold"> ${byLabelsStat[labelId].total} </td>
</tr>`).addClass('temporaryStatisticRow').appendTo(table);
}
}
$(`<tr class="semiBold">
<td> Total: </td>
<td> ${totalStat.boxes.annotation} </td>
<td> ${totalStat.boxes.interpolation} </td>
<td> ${totalStat.polygons.annotation} </td>
<td> ${totalStat.polygons.interpolation} </td>
<td> ${totalStat.polylines.annotation} </td>
<td> ${totalStat.polylines.interpolation} </td>
<td> ${totalStat.points.annotation} </td>
<td> ${totalStat.points.interpolation} </td>
<td> ${totalStat.cuboids.annotation} </td>
<td> ${totalStat.cuboids.interpolation} </td>
<td> ${totalStat.manually} </td>
<td> ${totalStat.interpolated} </td>
<td> ${totalStat.total} </td>
</tr>`).addClass('temporaryStatisticRow').appendTo(table);
});
menuButton.on('click', () => {
annotationMenu.removeClass('hidden');
annotationMenu.css('top', `${menuButton.offset().top - annotationMenu.height() - menuButton.height()}px`);
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(hide, 1000);
});
annotationMenu.on('mouseout', () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(hide, 500);
});
annotationMenu.on('mouseover', () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
});
}
setupVisibility();
$('#statTaskName').text(task.name);
$('#statFrames').text(`[${window.cvat.player.frames.start}-${window.cvat.player.frames.stop}]`);
$('#statOverlap').text(task.overlap);
$('#statZOrder').text(task.z_order);
$('#statTaskStatus').prop('value', job.status).on('change', async (e) => {
try {
const jobCopy = JSON.parse(JSON.stringify(job));
jobCopy.status = e.target.value;
await $.ajax({
url: `/api/v1/jobs/${window.cvat.job.id}`,
type: 'PATCH',
data: JSON.stringify(jobCopy),
contentType: 'application/json',
});
} catch (errorData) {
const message = `Can not update a job status. Code: ${errorData.status}. `
+ `Message: ${errorData.responseText || errorData.statusText}`;
showMessage(message);
}
});
const { shortkeys } = window.cvat.config;
$('#helpButton').on('click', () => {
hide();
$('#helpWindow').removeClass('hidden');
});
$('#helpButton').attr('title', `
${shortkeys.open_help.view_value} - ${shortkeys.open_help.description}`);
$('#settingsButton').on('click', () => {
hide();
$('#settingsWindow').removeClass('hidden');
});
$('#openTaskButton').on('click', () => {
const win = window.open(
`${window.UI_URL}/tasks/${window.cvat.job.task_id}`, '_blank'
);
win.focus();
});
$('#settingsButton').attr('title', `
${shortkeys.open_settings.view_value} - ${shortkeys.open_settings.description}`);
const downloadButton = $('#downloadAnnotationButton');
const uploadButton = $('#uploadAnnotationButton');
const loaders = {};
for (const dumper of annotationFormats.exporters) {
const item = $(`<option>${dumper.name}</li>`);
if (!isDefaultFormat(dumper.name, window.cvat.job.mode)) {
item.addClass('regular');
}
item.appendTo(downloadButton);
}
for (const loader of annotationFormats.importers) {
loaders[loader.name] = loader;
$(`<option class="regular">${loader.name}</li>`).appendTo(uploadButton);
}
downloadButton.on('change', async (e) => {
const dumper = e.target.value;
downloadButton.prop('value', 'Dump Annotation');
try {
downloadButton.prop('disabled', true);
await dumpAnnotationRequest(task.id, dumper);
} catch (error) {
showMessage(error.message);
} finally {
downloadButton.prop('disabled', false);
}
});
uploadButton.on('change', (e) => {
const loader = loaders[e.target.value];
uploadButton.prop('value', 'Upload Annotation');
userConfirm('Current annotation will be removed from the client. Continue?',
async () => {
try {
await uploadAnnotation(
job.id,
shapeCollectionModel,
historyModel,
annotationSaverModel,
$('#uploadAnnotationButton'),
loader,
);
} catch (error) {
showMessage(error.message);
}
});
});
$('#removeAnnotationButton').on('click', () => {
if (!window.cvat.mode) {
hide();
userConfirm('Do you want to remove all annotations? The action cannot be undone!',
() => {
historyModel.empty();
shapeCollectionModel.empty();
});
}
});
// JS function cancelFullScreen don't work after pressing
// and it is famous problem.
$('#fullScreenButton').on('click', () => {
$('#playerFrame').toggleFullScreen();
});
$('#playerFrame').on('fullscreenchange webkitfullscreenchange mozfullscreenchange', () => {
playerModel.updateGeometry({
width: $('#playerFrame').width(),
height: $('#playerFrame').height(),
});
playerModel.fit();
});
$('#switchAAMButton').on('click', () => {
hide();
aamModel.switchAAMMode();
});
$('#switchAAMButton').attr('title', `
${shortkeys.switch_aam_mode.view_value} - ${shortkeys.switch_aam_mode.description}`);
}
function buildAnnotationUI(
jobData, taskData, imageMetaData,
annotationData, annotationFormats, loadJobEvent,
) {
// Setup some API
window.cvat = {
labelsInfo: new LabelsInfo(taskData.labels),
translate: new CoordinateTranslator(),
frozen: true,
player: {
geometry: {
scale: 1,
},
frames: {
current: jobData.start_frame,
start: jobData.start_frame,
stop: jobData.stop_frame,
},
},
mode: null,
job: {
z_order: taskData.z_order,
id: jobData.id,
task_id: taskData.id,
mode: taskData.mode,
images: imageMetaData,
chunk_size: taskData.data_chunk_size,
},
search: {
value: window.location.search,
set(name, value) {
const searchParams = new URLSearchParams(this.value);
if (typeof value === 'undefined' || value === null) {
if (searchParams.has(name)) {
searchParams.delete(name);
}
} else {
searchParams.set(name, value);
}
this.value = `${searchParams.toString()}`;
},
get(name) {
try {
const decodedURI = decodeURIComponent(this.value);
const urlSearchParams = new URLSearchParams(decodedURI);
if (urlSearchParams.has(name)) {
return urlSearchParams.get(name);
}
return null;
} catch (error) {
showMessage('Bad URL has been received');
this.value = window.location.href;
return null;
}
},
toString() {
return `${window.location.origin}/?${this.value}`;
},
},
};
// Remove external search parameters from url
window.history.replaceState(null, null, `${window.location.origin}/?id=${jobData.id}`);
window.cvat.config = new Config();
// Setup components
const annotationParser = new AnnotationParser({
start: window.cvat.player.frames.start,
stop: window.cvat.player.frames.stop,
flipped: taskData.flipped,
image_meta_data: imageMetaData,
}, window.cvat.labelsInfo);
const shapeCollectionModel = new ShapeCollectionModel().import(annotationData);
const shapeCollectionController = new ShapeCollectionController(shapeCollectionModel);
const shapeCollectionView = new ShapeCollectionView(shapeCollectionModel,
shapeCollectionController);
const annotationSaverModel = buildAnnotationSaver(annotationData, shapeCollectionModel);
window.cvat.data = {
get: () => shapeCollectionModel.export()[0],
set: (data) => {
shapeCollectionModel.import(data);
shapeCollectionModel.update();
},
clear: () => shapeCollectionModel.empty(),
};
const shapeBufferModel = new ShapeBufferModel(shapeCollectionModel);
const shapeBufferController = new ShapeBufferController(shapeBufferModel);
const shapeBufferView = new ShapeBufferView(shapeBufferModel, shapeBufferController);
$('#shapeModeSelector').prop('value', taskData.mode);
const shapeCreatorModel = new ShapeCreatorModel(shapeCollectionModel);
const shapeCreatorController = new ShapeCreatorController(shapeCreatorModel);
const shapeCreatorView = new ShapeCreatorView(shapeCreatorModel, shapeCreatorController);
const polyshapeEditorModel = new PolyshapeEditorModel(shapeCollectionModel);
const polyshapeEditorController = new PolyshapeEditorController(polyshapeEditorModel);
const polyshapeEditorView = new PolyshapeEditorView(polyshapeEditorModel,
polyshapeEditorController);
// Add static member for class. It will be used by all polyshapes.
PolyShapeView.editor = polyshapeEditorModel;
const shapeMergerModel = new ShapeMergerModel(shapeCollectionModel);
const shapeMergerController = new ShapeMergerController(shapeMergerModel);
new ShapeMergerView(shapeMergerModel, shapeMergerController);
const shapeGrouperModel = new ShapeGrouperModel(shapeCollectionModel);
const shapeGrouperController = new ShapeGrouperController(shapeGrouperModel);
const shapeGrouperView = new ShapeGrouperView(shapeGrouperModel, shapeGrouperController);
const playerGeometry = {
width: $('#playerFrame').width(),
height: $('#playerFrame').height(),
};
const playerModel = new PlayerModel(taskData, playerGeometry);
const playerController = new PlayerController(playerModel,
() => shapeCollectionModel.activeShape,
direction => shapeCollectionModel.find(direction),
Object.assign({}, playerGeometry, {
left: $('#playerFrame').offset().left,
top: $('#playerFrame').offset().top,
}));
new PlayerView(playerModel, playerController);
const aamModel = new AAMModel(shapeCollectionModel, (xtl, xbr, ytl, ybr) => {
playerModel.focus(xtl, xbr, ytl, ybr);
}, () => {
playerModel.fit();
});
const aamController = new AAMController(aamModel);
new AAMView(aamModel, aamController);
shapeCreatorModel.subscribe(shapeCollectionModel);
shapeGrouperModel.subscribe(shapeCollectionView);
shapeCollectionModel.subscribe(shapeGrouperModel);
$('#playerProgress').css('width', $('#player')['0'].clientWidth - 420);
const historyModel = new HistoryModel(playerModel);
const historyController = new HistoryController(historyModel);
new HistoryView(historyController, historyModel);
playerModel.subscribe(shapeCollectionModel);
playerModel.subscribe(shapeCollectionView);
playerModel.subscribe(shapeCreatorView);
playerModel.subscribe(shapeBufferView);
playerModel.subscribe(shapeGrouperView);
playerModel.subscribe(polyshapeEditorView);
playerModel.shift(window.cvat.search.get('frame') || 0, true);
const { shortkeys } = window.cvat.config;
setupHelpWindow(shortkeys);
setupSettingsWindow();
setupMenu(jobData, taskData, shapeCollectionModel,
annotationParser, aamModel, playerModel, historyModel,
annotationFormats, annotationSaverModel);
setupFrameFilters();
setupShortkeys(shortkeys, {
aam: aamModel,
shapeCreator: shapeCreatorModel,
shapeMerger: shapeMergerModel,
shapeGrouper: shapeGrouperModel,
shapeBuffer: shapeBufferModel,
shapeEditor: polyshapeEditorModel,
});
$(window).on('click', (event) => {
Logger.updateUserActivityTimer();
if (event.target.classList.contains('modal') && !event.target.classList.contains('force-modal')) {
event.target.classList.add('hidden');
}
});
const totalStat = shapeCollectionModel.collectStatistic()[1];
loadJobEvent.addValues({
'track count': totalStat.boxes.annotation + totalStat.boxes.interpolation
+ totalStat.polygons.annotation + totalStat.polygons.interpolation
+ totalStat.polylines.annotation + totalStat.polylines.interpolation
+ totalStat.points.annotation + totalStat.points.interpolation
+ totalStat.cuboids.annotation + totalStat.cuboids.interpolation,
'frame count': window.cvat.player.frames.stop - window.cvat.player.frames.start + 1,
'object count': totalStat.total,
'box count': totalStat.boxes.annotation + totalStat.boxes.interpolation,
'polygon count': totalStat.polygons.annotation + totalStat.polygons.interpolation,
'polyline count': totalStat.polylines.annotation + totalStat.polylines.interpolation,
'points count': totalStat.points.annotation + totalStat.points.interpolation,
'cuboid count': totalStat.cuboids.annotation + totalStat.cuboids.interpolation,
});
loadJobEvent.close();
$('#player').on('click', (e) => {
if (e.target.tagName.toLowerCase() !== 'input') {
blurAllElements();
}
});
}
function callAnnotationUI(jid) {
function onError(errorData) {
$('body').empty();
const message = `Can not build CVAT annotation UI. Code: ${errorData.status}. `
+ `Message: ${errorData.responseText || errorData.statusText}`;
showMessage(message);
}
initLogger(jid);
const loadJobEvent = Logger.addContinuedEvent(Logger.EventType.loadJob);
$.get(`/api/v1/jobs/${jid}`).done((jobData) => {
$.when(
$.get(`/api/v1/tasks/${jobData.task_id}`),
$.get(`/api/v1/tasks/${jobData.task_id}/data/meta`),
$.get(`/api/v1/jobs/${jid}/annotations`),
$.get('/api/v1/server/annotation/formats'),
).then((taskData, imageMetaData, annotationData, annotationFormats) => {
$('#loadingOverlay').remove();
setTimeout(async () => {
window.cvat.config.backendAPI = `${window.location.origin}/api/v1`;
[window.cvatTask] = (await window.cvat.tasks.get({ id: taskData[0].id }));
buildAnnotationUI(jobData, taskData[0],
imageMetaData[0], annotationData[0], annotationFormats[0], loadJobEvent);
});
}).fail(onError);
}).fail(onError);
}
function copyToClipboard(text) {
const tempInput = $('<input>');
$('body').append(tempInput);
tempInput.prop('value', text).select();
document.execCommand('copy');
tempInput.remove();
}
function drawBoxSize(boxScene, textScene, box) {
const clientBox = window.cvat.translate.box.canvasToClient(boxScene.node, box);
const text = `${box.width.toFixed(1)}x${box.height.toFixed(1)}`;
const obj = this && this.textUI && this.rm ? this : {
textUI: textScene.text('').font({
weight: 'bolder',
}).fill('white'),
rm() {
if (this.textUI) {
this.textUI.remove();
}
},
};
const textPoint = window.cvat.translate.point.clientToCanvas(textScene.node,
clientBox.x, clientBox.y);
obj.textUI.clear().plain(text);
obj.textUI.addClass('shapeText');
obj.textUI.move(textPoint.x, textPoint.y);
return obj;
}

@ -1,453 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported AAMModel AAMController AAMView AAMUndefinedKeyword */
/* global
Listener:false
Logger:false
Mousetrap:false
PolyShapeModel:false
SVG:false
*/
const AAMUndefinedKeyword = '__undefined__';
class AAMModel extends Listener {
constructor(shapeCollection, focus, fit) {
super('onAAMUpdate', () => this);
this._shapeCollection = shapeCollection;
this._focus = focus;
this._fit = fit;
this._activeAAM = false;
this._activeIdx = null;
this._active = null;
this._margin = 100;
this._currentShapes = [];
this._attrNumberByLabel = {};
this._helps = {};
function getHelp(attrId) {
const attrInfo = window.cvat.labelsInfo.attrInfo(attrId);
const help = [];
switch (attrInfo.type) {
case 'checkbox':
help.push(`0 - ${attrInfo.values[0]}`);
help.push(`1 - ${!attrInfo.values[0]}`);
break;
default:
for (let idx = 0; idx < attrInfo.values.length; idx += 1) {
if (idx > 9) {
break;
}
if (attrInfo.values[0] === AAMUndefinedKeyword) {
if (!idx) {
continue;
}
help.push(`${idx - 1} - ${attrInfo.values[idx]}`);
} else {
help.push(`${idx} - ${attrInfo.values[idx]}`);
}
}
}
return help;
}
const labels = window.cvat.labelsInfo.labels();
for (const labelId in labels) {
if (Object.prototype.hasOwnProperty.call(labels, labelId)) {
const labelAttributes = window.cvat.labelsInfo.labelAttributes(labelId);
if (Object.keys(labelAttributes).length) {
this._attrNumberByLabel[labelId] = {
current: 0,
end: Object.keys(labelAttributes).length,
};
for (const attrId in labelAttributes) {
if (Object.prototype.hasOwnProperty.call(labelAttributes, attrId)) {
this._helps[attrId] = {
title: `${window.cvat.labelsInfo.labels()[labelId]}, ${window.cvat.labelsInfo.attributes()[attrId]}`,
help: getHelp(attrId),
};
}
}
}
}
}
shapeCollection.subscribe(this);
}
_bbRect(pos) {
if ('points' in pos) {
const points = PolyShapeModel.convertStringToNumberArray(pos.points);
let xtl = Number.MAX_SAFE_INTEGER;
let ytl = Number.MAX_SAFE_INTEGER;
let xbr = Number.MIN_SAFE_INTEGER;
let ybr = Number.MIN_SAFE_INTEGER;
for (const point of points) {
xtl = Math.min(xtl, point.x);
ytl = Math.min(ytl, point.y);
xbr = Math.max(xbr, point.x);
ybr = Math.max(ybr, point.y);
}
return [xtl, ytl, xbr, ybr];
}
return [pos.xtl, pos.ytl, pos.xbr, pos.ybr];
}
_updateCollection() {
this._currentShapes = [];
for (const shape of this._shapeCollection.currentShapes) {
const labelAttributes = window.cvat.labelsInfo.labelAttributes(shape.model.label);
if (Object.keys(labelAttributes).length
&& !shape.model.removed && !shape.interpolation.position.outside) {
this._currentShapes.push({
model: shape.model,
interpolation: shape.model.interpolate(window.cvat.player.frames.current),
});
}
}
if (this._currentShapes.length) {
this._activeIdx = 0;
this._active = this._currentShapes[0].model;
} else {
this._activeIdx = null;
this._active = null;
}
}
_attrIdByIdx(labelId, attrIdx) {
return Object.keys(window.cvat.labelsInfo.labelAttributes(labelId))[attrIdx];
}
_activate() {
if (this._activeAAM && this._active) {
const { label } = this._active;
const [xtl, ytl, xbr, ybr] = this._bbRect(this._currentShapes[this._activeIdx]
.interpolation.position);
this._focus(xtl - this._margin, xbr + this._margin,
ytl - this._margin, ybr + this._margin);
this.notify();
if (typeof (this._attrNumberByLabel[label]) !== 'undefined') {
const attrId = +this._attrIdByIdx(label, this._attrNumberByLabel[label].current);
this._active.activeAttribute = attrId;
}
} else {
this.notify();
}
}
_deactivate() {
if (this._activeAAM && this._active) {
this._active.activeAttribute = null;
}
}
_enable() {
if (window.cvat.mode === null) {
window.cvat.mode = 'aam';
this._shapeCollection.resetActive();
this._activeAAM = true;
this._updateCollection();
this._activate();
}
}
_disable() {
if (this._activeAAM && window.cvat.mode === 'aam') {
this._deactivate();
window.cvat.mode = null;
this._activeAAM = false;
this._activeIdx = null;
this._active = null;
// Notify for remove aam UI
this.notify();
this._fit();
}
}
switchAAMMode() {
if (this._activeAAM) {
this._disable();
} else {
this._enable();
}
}
moveShape(direction) {
if (!this._activeAAM || this._currentShapes.length < 2) {
return;
}
this._deactivate();
if (Math.sign(direction) < 0) {
// next
this._activeIdx += 1;
if (this._activeIdx >= this._currentShapes.length) {
this._activeIdx = 0;
}
} else {
// prev
this._activeIdx -= 1;
if (this._activeIdx < 0) {
this._activeIdx = this._currentShapes.length - 1;
}
}
this._active = this._currentShapes[this._activeIdx].model;
this._activate();
}
moveAttr(direction) {
if (!this._activeAAM || !this._active) {
return;
}
const curAttr = this._attrNumberByLabel[this._active.label];
if (typeof (curAttr) === 'undefined') {
return;
}
if (curAttr.end < 2) {
return;
}
if (Math.sign(direction) > 0) {
// next
curAttr.current += 1;
if (curAttr.current >= curAttr.end) {
curAttr.current = 0;
}
} else {
// prev
curAttr.current -= 1;
if (curAttr.current < 0) {
curAttr.current = curAttr.end - 1;
}
}
this._activate();
}
setupAttributeValue(key) {
if (!this._activeAAM || !this._active) {
return;
}
const { label } = this._active;
const frame = window.cvat.player.frames.current;
if (typeof (this._attrNumberByLabel[label]) === 'undefined') {
return;
}
const attrId = this._attrIdByIdx(label, this._attrNumberByLabel[label].current);
const attrInfo = window.cvat.labelsInfo.attrInfo(attrId);
if (key >= attrInfo.values.length) {
if (attrInfo.type === 'checkbox' && key < 2) {
this._active.updateAttribute(frame, attrId, !attrInfo.values[0]);
}
return;
}
if (attrInfo.values[0] === AAMUndefinedKeyword) {
if (key >= attrInfo.values.length - 1) {
return;
}
key += 1;
}
this._active.updateAttribute(frame, attrId, attrInfo.values[key]);
}
onCollectionUpdate() {
if (this._activeAAM) {
// No need deactivate active view because all listeners already unsubscribed
this._updateCollection();
this._activate();
}
}
generateHelps() {
if (this._active) {
const { label } = this._active;
if (typeof (this._attrNumberByLabel[label]) !== 'undefined') {
const attrId = +this._attrIdByIdx(label, this._attrNumberByLabel[label].current);
return [this._helps[attrId].title, this._helps[attrId].help, `${this._activeIdx + 1}/${this._currentShapes.length}`];
}
return ['No Attributes Found', '', `${this._activeIdx + 1}/${this._currentShapes.length}`];
}
return ['No Shapes Found', '', '0/0'];
}
get activeAAM() {
return this._activeAAM;
}
get active() {
return this._active;
}
set margin(value) {
this._margin = value;
}
}
class AAMController {
constructor(aamModel) {
this._model = aamModel;
function setupAAMShortkeys() {
const switchAAMHandler = Logger.shortkeyLogDecorator(() => {
this._model.switchAAMMode();
});
const nextAttributeHandler = Logger.shortkeyLogDecorator((e) => {
this._model.moveAttr(1);
e.preventDefault();
});
const prevAttributeHandler = Logger.shortkeyLogDecorator((e) => {
this._model.moveAttr(-1);
e.preventDefault();
});
const nextShapeHandler = Logger.shortkeyLogDecorator((e) => {
this._model.moveShape(1);
e.preventDefault();
});
const prevShapeHandler = Logger.shortkeyLogDecorator((e) => {
this._model.moveShape(-1);
e.preventDefault();
});
const selectAttributeHandler = Logger.shortkeyLogDecorator((e) => {
let key = e.keyCode;
if (key >= 48 && key <= 57) {
key -= 48; // 0 and 9
} else if (key >= 96 && key <= 105) {
key -= 96; // num 0 and 9
} else {
return;
}
this._model.setupAttributeValue(key);
});
const { shortkeys } = window.cvat.config;
Mousetrap.bind(shortkeys.switch_aam_mode.value, switchAAMHandler, 'keydown');
Mousetrap.bind(shortkeys.aam_next_attribute.value, nextAttributeHandler, 'keydown');
Mousetrap.bind(shortkeys.aam_prev_attribute.value, prevAttributeHandler, 'keydown');
Mousetrap.bind(shortkeys.aam_next_shape.value, nextShapeHandler, 'keydown');
Mousetrap.bind(shortkeys.aam_prev_shape.value, prevShapeHandler, 'keydown');
Mousetrap.bind(shortkeys.select_i_attribute.value, selectAttributeHandler, 'keydown');
}
setupAAMShortkeys.call(this);
}
setMargin(value) {
this._model.margin = value;
}
}
class AAMView {
constructor(aamModel, aamController) {
this._trackManagement = $('#trackManagement');
this._aamMenu = $('#aamMenu');
this._aamTitle = $('#aamTitle');
this._aamCounter = $('#aamCounter');
this._aamHelpContainer = $('#aamHelpContainer');
this._zoomMargin = $('#aamZoomMargin');
this._frameContent = SVG.adopt($('#frameContent')[0]);
this._controller = aamController;
this._zoomMargin.on('change', (e) => {
const value = +e.target.value;
this._controller.setMargin(value);
}).trigger('change');
aamModel.subscribe(this);
}
_setupAAMView(active, type, pos) {
const oldRect = $('#outsideRect');
const oldMask = $('#outsideMask');
if (active) {
if (oldRect.length) {
oldRect.remove();
oldMask.remove();
}
const size = window.cvat.translate.box.actualToCanvas({
x: 0,
y: 0,
width: window.cvat.player.geometry.frameWidth,
height: window.cvat.player.geometry.frameHeight,
});
const excludeField = this._frameContent.rect(size.width, size.height).move(size.x, size.y).fill('#666');
let includeField = null;
if (type === 'box') {
pos = window.cvat.translate.box.actualToCanvas(pos);
includeField = this._frameContent.rect(pos.xbr - pos.xtl,
pos.ybr - pos.ytl).move(pos.xtl, pos.ytl);
} else {
pos.points = window.cvat.translate.points.actualToCanvas(pos.points);
includeField = this._frameContent.polygon(pos.points);
}
this._frameContent.mask().add(excludeField)
.add(includeField).fill('black')
.attr('id', 'outsideMask');
this._frameContent.rect(size.width, size.height)
.move(size.x, size.y).attr({
mask: 'url(#outsideMask)',
id: 'outsideRect',
});
const content = $(this._frameContent.node);
const texts = content.find('.shapeText');
for (const text of texts) {
content.append(text);
}
} else {
oldRect.remove();
oldMask.remove();
}
}
onAAMUpdate(aam) {
this._setupAAMView(Boolean(aam.active),
aam.active ? aam.active.type.split('_')[1] : '',
aam.active ? aam.active.interpolate(window.cvat.player.frames.current).position : 0);
if (aam.activeAAM) {
if (this._aamMenu.hasClass('hidden')) {
this._trackManagement.addClass('hidden');
this._aamMenu.removeClass('hidden');
}
const [title, help, counter] = aam.generateHelps();
this._aamHelpContainer.empty();
this._aamCounter.text(counter);
this._aamTitle.text(title);
for (const helpRow of help) {
$(`<label> ${helpRow} <label> <br>`).appendTo(this._aamHelpContainer);
}
} else if (this._trackManagement.hasClass('hidden')) {
this._aamMenu.addClass('hidden');
this._trackManagement.removeClass('hidden');
}
}
}

@ -1,227 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported
userConfirm
dumpAnnotationRequest
showMessage
showOverlay
uploadJobAnnotationRequest
uploadTaskAnnotationRequest
isDefaultFormat
*/
/* global
Cookies:false
*/
Math.clamp = (x, min, max) => Math.min(Math.max(x, min), max);
String.customSplit = (string, separator) => {
const regex = /"/gi;
const occurences = [];
let occurence = regex.exec(string);
while (occurence) {
occurences.push(occurence.index);
occurence = regex.exec(string);
}
if (occurences.length % 2) {
occurences.pop();
}
let copy = '';
if (occurences.length) {
let start = 0;
for (let idx = 0; idx < occurences.length; idx += 2) {
copy += string.substr(start, occurences[idx] - start);
copy += string.substr(occurences[idx], occurences[idx + 1] - occurences[idx] + 1)
.replace(new RegExp(separator, 'g'), '\0');
start = occurences[idx + 1] + 1;
}
copy += string.substr(occurences[occurences.length - 1] + 1);
} else {
copy = string;
}
return copy.split(new RegExp(separator, 'g')).map(x => x.replace(/\0/g, separator));
};
function userConfirm(message, onagree, ondisagree) {
const template = $('#confirmTemplate');
const confirmWindow = $(template.html()).css('display', 'block');
const annotationConfirmMessage = confirmWindow.find('.templateMessage');
const agreeConfirm = confirmWindow.find('.templateAgreeButton');
const disagreeConfirm = confirmWindow.find('.templateDisagreeButton');
function hideConfirm() {
agreeConfirm.off('click');
disagreeConfirm.off('click');
confirmWindow.remove();
}
annotationConfirmMessage.text(message);
$('body').append(confirmWindow);
agreeConfirm.on('click', () => {
hideConfirm();
if (onagree) {
onagree();
}
});
disagreeConfirm.on('click', () => {
hideConfirm();
if (ondisagree) {
ondisagree();
}
});
disagreeConfirm.focus();
confirmWindow.on('keydown', (e) => {
e.stopPropagation();
});
}
function showMessage(message) {
const template = $('#messageTemplate');
const messageWindow = $(template.html()).css('display', 'block');
const messageText = messageWindow.find('.templateMessage');
const okButton = messageWindow.find('.templateOKButton');
messageText.text(message);
$('body').append(messageWindow);
messageWindow.on('keydown', (e) => {
e.stopPropagation();
});
okButton.on('click', () => {
okButton.off('click');
messageWindow.remove();
});
okButton.focus();
return messageWindow;
}
function showOverlay(message) {
const template = $('#overlayTemplate');
const overlayWindow = $(template.html()).css('display', 'block');
const overlayText = overlayWindow.find('.templateMessage');
overlayWindow[0].getMessage = () => overlayText.html();
overlayWindow[0].remove = () => overlayWindow.remove();
overlayWindow[0].setMessage = (msg) => {
overlayText.html(msg);
};
$('body').append(overlayWindow);
overlayWindow[0].setMessage(message);
return overlayWindow[0];
}
async function dumpAnnotationRequest(tid, format) {
// URL Router on the server doesn't work correctly with slashes.
// So, we have to replace them on the client side
return new Promise((resolve, reject) => {
const url = `/api/v1/tasks/${tid}/annotations`;
let queryString = `format=${encodeURIComponent(format)}`;
async function request() {
$.get(`${url}?${queryString}`)
.done((...args) => {
if (args[2].status === 202) {
setTimeout(request, 3000);
} else {
const a = document.createElement('a');
queryString = `${queryString}&action=download`;
a.href = `${url}?${queryString}`;
document.body.appendChild(a);
a.click();
a.remove();
resolve();
}
}).fail((errorData) => {
const message = `Can not dump annotations for the task. Code: ${errorData.status}. `
+ `Message: ${errorData.responseText || errorData.statusText}`;
reject(new Error(message));
});
}
setTimeout(request);
});
}
async function uploadAnnoRequest(url, formData, format) {
return new Promise((resolve, reject) => {
const queryString = `format=${format}`;
async function request(data) {
try {
await $.ajax({
url: `${url}?${queryString}`,
type: 'PUT',
data,
contentType: false,
processData: false,
}).done((...args) => {
if (args[2].status === 202) {
setTimeout(() => request(''), 3000);
} else {
resolve();
}
});
} catch (errorData) {
const message = `Can not upload annotations for the job. Code: ${errorData.status}. `
+ `Message: ${errorData.responseText || errorData.statusText}`;
reject(new Error(message));
}
}
setTimeout(() => request(formData));
});
}
async function uploadJobAnnotationRequest(jid, formData, format) {
return uploadAnnoRequest(`/api/v1/jobs/${jid}/annotations`, formData, format);
}
async function uploadTaskAnnotationRequest(tid, formData, format) {
return uploadAnnoRequest(`/api/v1/tasks/${tid}/annotations`, formData, format);
}
/* These HTTP methods do not require CSRF protection */
function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader('X-CSRFToken', Cookies.get('csrftoken'));
}
},
});
$(document).ready(() => {
$('body').css({
width: `${window.screen.width}px`,
height: `${window.screen.height * 0.95}px`,
});
});
function isDefaultFormat(dumperName, taskMode) {
return (dumperName === 'CVAT for video 1.1' && taskMode === 'interpolation')
|| (dumperName === 'CVAT for images 1.1' && taskMode === 'annotation');
}

@ -1,34 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* global
callAnnotationUI:false
Logger:false
platform:false
*/
String.normalize = () => {
let target = this;
target = target.charAt(0).toUpperCase() + target.substr(1);
return target;
};
window.onload = function boot() {
window.onerror = function exception(errorMsg, url, lineNumber, colNumber, error) {
Logger.sendException(
errorMsg,
url,
lineNumber,
colNumber ? String(colNumber) : '',
error && error.stack ? error.stack : '',
`${platform.name} ${platform.version}`,
platform.os.toString(),
).catch(() => {});
};
const id = window.location.href.match('id=[0-9]+')[0].slice(3);
callAnnotationUI(id);
};

@ -1,213 +0,0 @@
/*
* Copyright (C) 2019 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported BorderSticker */
class BorderSticker {
constructor(currentShape, frameContent, shapes, scale) {
this._currentShape = currentShape;
this._frameContent = frameContent;
this._enabled = false;
this._groups = null;
this._scale = scale;
this._accounter = {
clicks: [],
shapeID: null,
};
const transformedShapes = shapes
.filter((shape) => !shape.model.removed)
.map((shape) => {
const pos = shape.interpolation.position;
// convert boxes to point sets
if (!('points' in pos)) {
return {
points: window.cvat.translate.points
.actualToCanvas(`${pos.xtl},${pos.ytl} ${pos.xbr},${pos.ytl}`
+ ` ${pos.xbr},${pos.ybr} ${pos.xtl},${pos.ybr}`),
color: shape.model.color.shape,
};
}
return {
points: window.cvat.translate.points
.actualToCanvas(pos.points),
color: shape.model.color.shape,
};
});
this._drawBorderMarkers(transformedShapes);
}
_addRawPoint(x, y) {
this._currentShape.array().valueOf().pop();
this._currentShape.array().valueOf().push([x, y]);
// not error, specific of the library
this._currentShape.array().valueOf().push([x, y]);
const paintHandler = this._currentShape.remember('_paintHandler');
paintHandler.drawCircles();
paintHandler.set.members.forEach((el) => {
el.attr('stroke-width', 1 / this._scale).attr('r', 2.5 / this._scale);
});
this._currentShape.plot(this._currentShape.array().valueOf());
}
_drawBorderMarkers(shapes) {
const namespace = 'http://www.w3.org/2000/svg';
this._groups = shapes.reduce((acc, shape, shapeID) => {
// Group all points by inside svg groups
const group = window.document.createElementNS(namespace, 'g');
shape.points.split(/\s/).map((point, pointID, points) => {
const [x, y] = point.split(',').map((coordinate) => +coordinate);
const circle = window.document.createElementNS(namespace, 'circle');
circle.classList.add('shape-creator-border-point');
circle.setAttribute('fill', shape.color);
circle.setAttribute('stroke', 'black');
circle.setAttribute('stroke-width', 1 / this._scale);
circle.setAttribute('cx', +x);
circle.setAttribute('cy', +y);
circle.setAttribute('r', 5 / this._scale);
circle.doubleClickListener = (e) => {
// Just for convenience (prevent screen fit feature)
e.stopPropagation();
};
circle.clickListener = (e) => {
e.stopPropagation();
// another shape was clicked
if (this._accounter.shapeID !== null && this._accounter.shapeID !== shapeID) {
this.reset();
}
this._accounter.shapeID = shapeID;
if (this._accounter.clicks[1] === pointID) {
// the same point repeated two times
const [_x, _y] = point.split(',').map((coordinate) => +coordinate);
this._addRawPoint(_x, _y);
this.reset();
return;
}
// the first point can not be clicked twice
if (this._accounter.clicks[0] !== pointID) {
this._accounter.clicks.push(pointID);
} else {
return;
}
// up clicked group for convenience
this._frameContent.node.appendChild(group);
// the first click
if (this._accounter.clicks.length === 1) {
// draw and remove initial point just to initialize data structures
if (!this._currentShape.remember('_paintHandler').startPoint) {
this._currentShape.draw('point', e);
this._currentShape.draw('undo');
}
const [_x, _y] = point.split(',').map((coordinate) => +coordinate);
this._addRawPoint(_x, _y);
// the second click
} else if (this._accounter.clicks.length === 2) {
circle.classList.add('shape-creator-border-point-direction');
// the third click
} else {
// sign defines bypass direction
const landmarks = this._accounter.clicks;
const sign = Math.sign(landmarks[2] - landmarks[0])
* Math.sign(landmarks[1] - landmarks[0])
* Math.sign(landmarks[2] - landmarks[1]);
// go via a polygon and get vertexes
// the first vertex has been already drawn
const way = [];
for (let i = landmarks[0] + sign; ; i += sign) {
if (i < 0) {
i = points.length - 1;
} else if (i === points.length) {
i = 0;
}
way.push(points[i]);
if (i === this._accounter.clicks[this._accounter.clicks.length - 1]) {
// put the last element twice
// specific of svg.draw.js
// way.push(points[i]);
break;
}
}
// remove the latest cursor position from drawing array
for (const wayPoint of way) {
const [_x, _y] = wayPoint.split(',').map((coordinate) => +coordinate);
this._addRawPoint(_x, _y);
}
this.reset();
}
};
circle.addEventListener('click', circle.clickListener);
circle.addEventListener('dblclick', circle.doubleClickListener);
return circle;
}).forEach((circle) => group.appendChild(circle));
acc.push(group);
return acc;
}, []);
this._groups
.forEach((group) => this._frameContent.node.appendChild(group));
}
reset() {
if (this._accounter.shapeID !== null) {
while (this._accounter.clicks.length > 0) {
const resetID = this._accounter.clicks.pop();
this._groups[this._accounter.shapeID]
.children[resetID].classList.remove('shape-creator-border-point-direction');
}
}
this._accounter = {
clicks: [],
shapeID: null,
};
}
disable() {
if (this._groups) {
this._groups.forEach((group) => {
Array.from(group.children).forEach((circle) => {
circle.removeEventListener('click', circle.clickListener);
circle.removeEventListener('dblclick', circle.doubleClickListener);
});
group.remove();
});
this._groups = null;
}
}
scale(scale) {
this._scale = scale;
if (this._groups) {
this._groups.forEach((group) => {
Array.from(group.children).forEach((circle) => {
circle.setAttribute('r', 5 / scale);
circle.setAttribute('stroke-width', 1 / scale);
});
});
}
}
}

@ -1,22 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* global
platform:false
*/
// legacy syntax for IE support
var supportedPlatforms = ['Chrome'];
if (supportedPlatforms.indexOf(platform.name) === -1) {
try {
document.documentElement.innerHTML = "<center><h1> Your browser is detected as " + platform.name +
". This tool does not support it. Please use the latest version of Google Chrome.</h1></center>";
window.stop();
} catch (err) {
document.execCommand('Stop');
}
}

@ -1,177 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported CoordinateTranslator */
"use strict";
class CoordinateTranslator {
constructor() {
this._boxTranslator = {
_playerOffset: 0,
_convert(box, sign) {
for (const prop of ['xtl', 'ytl', 'xbr', 'ybr', 'x', 'y']) {
if (prop in box) {
box[prop] += this._playerOffset * sign;
}
}
return box;
},
actualToCanvas(actualBox) {
const canvasBox = {};
for (const key in actualBox) {
canvasBox[key] = actualBox[key];
}
return this._convert(canvasBox, 1);
},
canvasToActual(canvasBox) {
const actualBox = {};
for (const key in canvasBox) {
actualBox[key] = canvasBox[key];
}
return this._convert(actualBox, -1);
},
canvasToClient(sourceCanvas, canvasBox) {
const points = [
[canvasBox.x, canvasBox.y],
[canvasBox.x + canvasBox.width, canvasBox.y],
[canvasBox.x, canvasBox.y + canvasBox.height],
[canvasBox.x + canvasBox.width, canvasBox.y + canvasBox.height],
].map(el => window.cvat.translate.point.canvasToClient(sourceCanvas, ...el));
const xes = points.map(el => el.x);
const yes = points.map(el => el.y);
const xmin = Math.min(...xes);
const xmax = Math.max(...xes);
const ymin = Math.min(...yes);
const ymax = Math.max(...yes);
return {
x: xmin,
y: ymin,
width: xmax - xmin,
height: ymax - ymin,
};
},
serverToClient(shape) {
return {
xtl: shape.points[0],
ytl: shape.points[1],
xbr: shape.points[2],
ybr: shape.points[3],
};
},
clientToServer(clientObject) {
return {
points: [clientObject.xtl, clientObject.ytl,
clientObject.xbr, clientObject.ybr],
};
},
};
this._pointsTranslator = {
_playerOffset: 0,
_convert(points, sign) {
if (typeof (points) === 'string') {
return points.split(' ').map(coord => coord.split(',')
.map(x => +x + this._playerOffset * sign).join(',')).join(' ');
}
if (typeof (points) === 'object') {
return points.map(point => ({
x: point.x + this._playerOffset * sign,
y: point.y + this._playerOffset * sign,
}));
}
throw Error('Unknown points type was found');
},
actualToCanvas(actualPoints) {
return this._convert(actualPoints, 1);
},
canvasToActual(canvasPoints) {
return this._convert(canvasPoints, -1);
},
serverToClient(shape) {
return {
points: shape.points.reduce((acc, el, idx) => {
if (idx % 2) {
acc.slice(-1)[0].push(el);
} else {
acc.push([el]);
}
return acc;
}, []).map(point => point.join(',')).join(' '),
};
},
clientToServer(clientPoints) {
return {
points: clientPoints.points.split(' ').join(',').split(',').map(x => +x),
};
},
};
this._pointTranslator = {
_rotation: 0,
clientToCanvas(targetCanvas, clientX, clientY) {
let pt = targetCanvas.createSVGPoint();
pt.x = clientX;
pt.y = clientY;
pt = pt.matrixTransform(targetCanvas.getScreenCTM().inverse());
return pt;
},
canvasToClient(sourceCanvas, canvasX, canvasY) {
let pt = sourceCanvas.createSVGPoint();
pt.x = canvasX;
pt.y = canvasY;
pt = pt.matrixTransform(sourceCanvas.getScreenCTM());
return pt;
},
rotate(x, y, cx, cy) {
cx = (typeof cx === 'undefined' ? 0 : cx);
cy = (typeof cy === 'undefined' ? 0 : cy);
const radians = (Math.PI / 180) * window.cvat.player.rotation;
const cos = Math.cos(radians);
const sin = Math.sin(radians);
return {
x: (cos * (x - cx)) + (sin * (y - cy)) + cx,
y: (cos * (y - cy)) - (sin * (x - cx)) + cy,
};
},
};
}
get box() {
return this._boxTranslator;
}
get points() {
return this._pointsTranslator;
}
get point() {
return this._pointTranslator;
}
set playerOffset(value) {
this._boxTranslator._playerOffset = value;
this._pointsTranslator._playerOffset = value;
}
set rotation(value) {
this._pointTranslator._rotation = value;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,219 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported HistoryModel HistoryController HistoryView */
/* global
Listener:false
Logger:false
Mousetrap:false
*/
"use strict";
class HistoryModel extends Listener {
constructor(playerModel) {
super('onHistoryUpdate', () => this );
this._deep = 128;
this._id = 0;
this._undo_stack = [];
this._redo_stack = [];
this._locked = false;
this._player = playerModel;
window.cvat.addAction = (name, undo, redo, frame) => this.addAction(name, undo, redo, frame);
}
undo() {
let frame = window.cvat.player.frames.current;
let undo = this._undo_stack.pop();
if (undo) {
try {
Logger.addEvent(Logger.EventType.undoAction, {
name: undo.name,
frame: undo.frame,
});
if (undo.frame != frame) {
this._player.shift(undo.frame, true);
}
this._locked = true;
undo.undo();
}
catch(err) {
this.notify();
throw err;
}
finally {
this._locked = false;
}
this._redo_stack.push(undo);
}
this.notify();
}
redo() {
let frame = window.cvat.player.frames.current;
let redo = this._redo_stack.pop();
if (redo) {
try {
Logger.addEvent(Logger.EventType.redoAction, {
name: redo.name,
frame: redo.frame,
});
if (redo.frame != frame) {
this._player.shift(redo.frame, true);
}
this._locked = true;
redo.redo();
}
catch(err) {
this.notify();
throw err;
}
finally {
this._locked = false;
}
this._undo_stack.push(redo);
}
this.notify();
}
addAction(name, undo, redo, frame) {
if (this._locked) return;
if (this._undo_stack.length >= this._deep) {
this._undo_stack.shift();
}
this._undo_stack.push({
name: name,
undo: undo,
redo: redo,
frame: frame,
id: this._id++,
});
this._redo_stack = [];
this.notify();
}
empty() {
this._undo_stack = [];
this._redo_stack = [];
this._id = 0;
this.notify();
}
get undoLength() {
return this._undo_stack.length;
}
get redoLength() {
return this._redo_stack.length;
}
get lastUndoText() {
let lastUndo = this._undo_stack[this._undo_stack.length - 1];
if (lastUndo) {
return `${lastUndo.name} [Frame ${lastUndo.frame}] [Id ${lastUndo.id}]`;
}
else return 'None';
}
get lastRedoText() {
let lastRedo = this._redo_stack[this._redo_stack.length - 1];
if (lastRedo) {
return `${lastRedo.name} [Frame ${lastRedo.frame}] [Id ${lastRedo.id}]`;
}
else return 'None';
}
}
class HistoryController {
constructor(model) {
this._model = model;
setupCollectionShortcuts.call(this);
function setupCollectionShortcuts() {
let undoHandler = Logger.shortkeyLogDecorator(function(e) {
this.undo();
e.preventDefault();
}.bind(this));
let redoHandler = Logger.shortkeyLogDecorator(function(e) {
this.redo();
e.preventDefault();
}.bind(this));
let shortkeys = window.cvat.config.shortkeys;
Mousetrap.bind(shortkeys["undo"].value, undoHandler.bind(this), 'keydown');
Mousetrap.bind(shortkeys["redo"].value, redoHandler.bind(this), 'keydown');
}
}
undo() {
if (!window.cvat.mode) {
this._model.undo();
}
}
redo() {
if (!window.cvat.mode) {
this._model.redo();
}
}
}
class HistoryView {
constructor(controller, model) {
this._controller = controller;
this._undoButton = $('#undoButton');
this._redoButton = $('#redoButton');
this._lastUndoText = $('#lastUndoText');
this._lastRedoText = $('#lastRedoText');
let shortkeys = window.cvat.config.shortkeys;
this._undoButton.attr('title', `${shortkeys['undo'].view_value} - ${shortkeys['undo'].description}`);
this._redoButton.attr('title', `${shortkeys['redo'].view_value} - ${shortkeys['redo'].description}`);
this._undoButton.on('click', () => {
this._controller.undo();
});
this._redoButton.on('click', () => {
this._controller.redo();
});
model.subscribe(this);
}
onHistoryUpdate(model) {
if (model.undoLength) {
this._undoButton.prop('disabled', false);
}
else {
this._undoButton.prop('disabled', true);
}
if (model.redoLength) {
this._redoButton.prop('disabled', false);
}
else {
this._redoButton.prop('disabled', true);
}
this._lastUndoText.text(model.lastUndoText);
this._lastRedoText.text(model.lastRedoText);
}
}

@ -1,192 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported LabelsInfo */
class LabelsInfo {
constructor(labels) {
function convertAttribute(attribute) {
return {
mutable: attribute.mutable,
type: attribute.input_type,
name: attribute.name,
values: attribute.input_type === 'checkbox'
? [attribute.values[0].toLowerCase() !== 'false'] : attribute.values,
};
}
this._labels = {};
this._attributes = {};
this._colorIdxs = {};
for (const label of labels) {
this._labels[label.id] = {
name: label.name,
attributes: {},
};
for (const attr of label.attributes) {
this._attributes[attr.id] = convertAttribute(attr);
this._labels[label.id].attributes[attr.id] = this._attributes[attr.id];
}
this._colorIdxs[label.id] = +label.id;
}
return this;
}
labelColorIdx(labelId) {
return this._colorIdxs[labelId];
}
updateLabelColorIdx(labelId) {
if (labelId in this._colorIdxs) {
this._colorIdxs[labelId] += 1;
}
}
labels() {
const labels = {};
for (const labelId in this._labels) {
if (Object.prototype.hasOwnProperty.call(this._labels, labelId)) {
labels[labelId] = this._labels[labelId].name;
}
}
return labels;
}
labelAttributes(labelId) {
if (labelId in this._labels) {
const attributes = {};
const labelAttributes = this._labels[labelId].attributes;
for (const attrId in labelAttributes) {
if (Object.prototype.hasOwnProperty.call(labelAttributes, attrId)) {
attributes[attrId] = labelAttributes[attrId].name;
}
}
return attributes;
}
throw Error('Unknown label ID');
}
attributes() {
const attributes = {};
for (const attrId in this._attributes) {
if (Object.prototype.hasOwnProperty.call(this._attributes, attrId)) {
attributes[attrId] = this._attributes[attrId].name;
}
}
return attributes;
}
attrInfo(attrId) {
if (attrId in this._attributes) {
return JSON.parse(JSON.stringify(this._attributes[attrId]));
}
throw Error('Unknown attribute ID');
}
labelIdOf(name) {
for (const labelId in this._labels) {
if (this._labels[labelId].name === name) {
return +labelId;
}
}
throw Error('Unknown label name');
}
attrIdOf(labelId, name) {
const attributes = this.labelAttributes(labelId);
for (const attrId in attributes) {
if (this._attributes[attrId].name === name) {
return +attrId;
}
}
throw Error('Unknown attribute name');
}
static normalize(type, attrValue) {
const value = String(attrValue);
if (type === 'checkbox') {
return value !== '0' && value.toLowerCase() !== 'false';
}
if (type === 'text') {
return value;
}
if (type === 'number') {
if (Number.isNaN(+value)) {
throw Error(`Can not convert ${value} to number.`);
} else {
return +value;
}
}
return value;
}
static serialize(deserialized) {
let serialized = '';
for (const label of deserialized) {
serialized += ` ${label.name}`;
for (const attr of label.attributes) {
serialized += ` ${attr.mutable ? '~' : '@'}`;
serialized += `${attr.input_type}=${attr.name}:`;
serialized += attr.values.join(',');
}
}
return serialized.trim();
}
static deserialize(serialized) {
const normalized = serialized.replace(/'+/g, '\'').replace(/\s+/g, ' ').trim();
const fragments = String.customSplit(normalized, ' ');
const deserialized = [];
let latest = null;
for (let fragment of fragments) {
fragment = fragment.trim();
if ((fragment.startsWith('~')) || (fragment.startsWith('@'))) {
const regex = /(@|~)(checkbox|select|number|text|radio)=(.+):(.+)/g;
const result = regex.exec(fragment);
if (result === null || latest === null) {
throw Error('Bad labels format');
}
const values = String.customSplit(result[4], ',');
latest.attributes.push({
name: result[3].replace(/^"/, '').replace(/"$/, ''),
mutable: result[1] === '~',
input_type: result[2],
default_value: values[0].replace(/^"/, '').replace(/"$/, ''),
values: values.map(val => val.replace(/^"/, '').replace(/"$/, '')),
});
} else {
latest = {
name: fragment.replace(/^"/, '').replace(/"$/, ''),
attributes: [],
};
deserialized.push(latest);
}
}
return deserialized;
}
}

@ -1,51 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported Listener */
"use strict";
class Listener {
constructor(notifyCallbackName, getStateCallback) {
this._listeners = [];
this._notifyCallbackName = notifyCallbackName;
this._getStateCallback = getStateCallback;
}
subscribe(listener) {
if (typeof(listener) != 'object') {
throw Error('Bad listener for subscribe found. Listener is not object.');
}
if (typeof(listener[this._notifyCallbackName]) != 'function') {
throw Error('Bad listener for subscribe found. Listener does not have a callback function ' + this._notifyCallbackName);
}
if (this._listeners.indexOf(listener) === -1) {
this._listeners.push(listener);
}
}
unsubscribeAll() {
this._listeners = [];
}
unsubscribe(listener) {
let idx = this._listeners.indexOf(listener);
if (idx != -1) {
this._listeners.splice(idx,1);
}
else {
throw Error('Unknown listener for unsubscribe');
}
}
notify() {
let state = this._getStateCallback();
for (let listener of this._listeners) {
listener[this._notifyCallbackName](state);
}
}
}

@ -1,564 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported Logger */
/* global
Cookies:false
*/
"use strict";
class UserActivityHandler {
constructor() {
this._TIME_TRESHHOLD = 100000; //ms
this._prevEventTime = Date.now();
this._workingTime = 0;
}
updateTimer() {
if (document.hasFocus()) {
let now = Date.now();
let diff = now - this._prevEventTime;
this._prevEventTime = now;
this._workingTime += diff < this._TIME_TRESHHOLD ? diff : 0;
}
}
resetTimer() {
this._prevEventTime = Date.now();
this._workingTime = 0;
}
getWorkingTime() {
return this._workingTime;
}
}
class LogCollection extends Array {
constructor(logger, items) {
super(items.length);
for (let i = 0; i < items.length; i++) {
super[i] = items[i];
}
this._loggerHandler = logger;
}
save() {
this._loggerHandler.pushLogs(this);
}
export() {
return Array.from(this, log => log.serialize());
}
}
class LoggerHandler {
constructor(jobId) {
this._clientID = Date.now().toString().substr(-6);
this._jobId = jobId;
this._logEvents = [];
this._userActivityHandler = new UserActivityHandler();
this._timeThresholds = {};
}
addEvent(event) {
this._pushEvent(event);
}
addContinuedEvent(event) {
this._userActivityHandler.updateTimer();
event.onCloseCallback = this._closeCallback.bind(this);
return event;
}
sendExceptions(exception) {
this._extendEvent(exception);
return new Promise((resolve, reject) => {
let retries = 3;
let makeRequest = () => {
let xhr = new XMLHttpRequest();
xhr.open('POST', '/api/v1/server/exception');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader("X-CSRFToken", Cookies.get('csrftoken'));
let onreject = () => {
if (retries--) {
setTimeout(() => makeRequest(), 30000); //30 sec delay
} else {
let payload = exception.serialize();
delete Object.assign(payload, {origin_message: payload.message }).message;
this.addEvent(new Logger.LogEvent(
Logger.EventType.sendException,
payload,
"Can't send exception",
));
reject({
status: xhr.status,
statusText: xhr.statusText,
});
}
};
xhr.onload = () => {
switch (xhr.status) {
case 201:
case 403: // ignore forbidden response
resolve(xhr.response);
break;
default:
onreject();
}
};
xhr.onerror = () => {
// if status === 0 a request is a failure on the network level, ignore it
if (xhr.status === 0) {
resolve(xhr.response);
} else {
onreject();
}
};
xhr.send(JSON.stringify(exception.serialize()));
};
makeRequest();
});
}
getLogs() {
let logs = new LogCollection(this, this._logEvents);
this._logEvents.length = 0;
return logs;
}
pushLogs(logEvents) {
Array.prototype.push.apply(this._logEvents, logEvents);
}
_extendEvent(event) {
event._jobId = this._jobId;
event._clientId = this._clientID;
}
_pushEvent(event) {
this._extendEvent(event);
if (event._type in this._timeThresholds) {
this._timeThresholds[event._type].wait(event);
}
else {
this._logEvents.push(event);
}
this._userActivityHandler.updateTimer();
}
_closeCallback(event) {
this._pushEvent(event);
}
updateTimer() {
this._userActivityHandler.updateTimer();
}
resetTimer() {
this._userActivityHandler.resetTimer();
}
getWorkingTime() {
return this._userActivityHandler.getWorkingTime();
}
setTimeThreshold(eventType, threshold) {
this._timeThresholds[eventType] = {
_threshold: threshold,
_timeoutHandler: null,
_timestamp: 0,
_event: null,
_logEvents: this._logEvents,
wait: function(event) {
if (this._event) {
if (this._timeoutHandler) {
clearTimeout(this._timeoutHandler);
}
}
else {
this._timestamp = event._timestamp;
}
this._event = event;
this._timeoutHandler = setTimeout(() => {
if ('duration' in this._event._values) {
this._event._values.duration += this._event._timestamp - this._timestamp;
}
this._event._timestamp = this._timestamp;
this._logEvents.push(this._event);
this._event = null;
}, threshold);
},
};
}
}
/*
Log message has simple json format - each message is set of "key" : "value"
pairs inside curly braces - {"key1" : "string_value", "key2" : number_value,
...} Value may be string or number (see json spec) required fields for all event
types:
NAME TYPE DESCRIPTION
"event" string see EventType enum description of possible values.
"timestamp" number timestamp in UNIX format - the number of seconds
or milliseconds that have elapsed since 00:00:00
Thursday, 1 January 1970
"application" string application name
"userid" string Unique userid
"task" string Unique task id. (Is expected corresponding Jira task id)
"count" is requiered field for "Add object", "Delete object", "Copy track",
"Propagate object", "Merge objecrs", "Undo action" and "Redo action" events with
number value.
Example : { "event" : "Add object", "timestamp" : 1486040342867, "application" :
"CVAT", "duration" : 4200, "userid" : "ESAZON1X-MOBL", "count" : 1, "type" :
"bounding box" }
Types of supported events. Minimum subset of events to generate simple report
are Logger.EventType.addObject, Logger.EventType.deleteObject and
Logger.EventType.sendTaskInfo. Value of "count" property should be a number.
*/
class LoggerEvent {
constructor(type, message) {
this._time = new Date().toISOString();
this._clientId = null;
this._jobId = null;
this._type = type;
this._message = message;
}
serialize() {
let serializedObj = {
job_id: this._jobId,
client_id: this._clientId,
name: Logger.eventTypeToString(this._type),
time: this._time,
};
if (this._message) {
Object.assign(serializedObj, { message: this._message,});
}
return serializedObj;
}
}
var Logger = {
/**
* @private
*/
_logger: null,
_userActivityHandler: null,
/**
* Logger.LogEvent class declaration
* @param {Logger.EventType} type Type of event
* @param {Object} values Any event values, for example {count: 1, label: 'vehicle'}
* @param {Function} closeCallback callback function which will be called by close method. Setted by
*/
LogEvent: class extends LoggerEvent {
constructor(type, values, message) {
super(type, message);
this._timestamp = Date.now();
this.onCloseCallback = null;
this._is_active = document.hasFocus();
this._values = values || {};
}
serialize() {
return Object.assign(super.serialize(), {
payload: this._values,
is_active: this._is_active,
});
};
close(endTimestamp) {
if (this.onCloseCallback) {
this.addValues({
duration: endTimestamp ? endTimestamp - this._timestamp : Date.now() - this._timestamp,
});
this.onCloseCallback(this);
}
};
addValues(values) {
Object.assign(this._values, values);
};
},
ExceptionEvent: class extends LoggerEvent {
constructor(message, filename, line, column, stack, client, system) {
super(Logger.EventType.sendException, message);
this._client = client;
this._column = column;
this._filename = filename;
this._line = line;
this._stack = stack;
this._system = system;
}
serialize() {
return Object.assign(super.serialize(), {
client: this._client,
column: this._column,
filename: this._filename,
line: this._line,
stack: this._stack,
system: this._system,
});
};
},
/**
* Logger.EventType Enumeration.
*/
EventType: {
// dumped as "Paste object". There are no additional required fields.
pasteObject: 0,
// dumped as "Change attribute". There are no additional required
// fields.
changeAttribute: 1,
// dumped as "Drag object". There are no additional required fields.
dragObject: 2,
// dumped as "Delete object". "count" is required field, value of
// deleted objects should be positive number.
deleteObject: 3,
// dumped as "Press shortcut". There are no additional required fields.
pressShortcut: 4,
// dumped as "Resize object". There are no additional required fields.
resizeObject: 5,
// dumped as "Send logs". It's expected that event has "duration" field,
// but it isn't necessary.
sendLogs: 6,
// dumped as "Save job". It's expected that event has "duration" field,
// but it isn't necessary.
saveJob: 7,
// dumped as "Jump frame". There are no additional required fields.
jumpFrame: 8,
// dumped as "Draw object". It's expected that event has "duration"
// field, but it isn't necessary.
drawObject: 9,
// dumped as "Change label".
changeLabel: 10,
// dumped as "Send task info". "track count", "frame count", "object
// count" are required fields. It's expected that event has
// "current_frame" field.
sendTaskInfo: 11,
// dumped as "Load job". "track count", "frame count", "object count"
// are required fields. It's expected that event has "duration" field,
// but it isn't necessary.
loadJob: 12,
// dumped as "Move image". It's expected that event has "duration"
// field, but it isn't necessary.
moveImage: 13,
// dumped as "Zoom image". It's expected that event has "duration"
// field, but it isn't necessary.
zoomImage: 14,
// dumped as "Lock object". There are no additional required fields.
lockObject: 15,
// dumped as "Merge objects". "count" is required field with positive or
// negative number value.
mergeObjects: 16,
// dumped as "Copy object". "count" is required field with number value.
copyObject: 17,
// dumped as "Propagate object". "count" is required field with number
// value.
propagateObject: 18,
// dumped as "Undo action". "count" is required field with positive or
// negative number value.
undoAction: 19,
// dumped as "Redo action". "count" is required field with positive or
// negative number value.
redoAction: 20,
// dumped as "Send user activity". "working_time" is required field with
// positive number value.
sendUserActivity: 21,
// dumped as "Send exception". Use to send any exception events to the
// server. "message", "filename", "line" are mandatory fields. "stack"
// and "column" are optional.
sendException: 22,
// dumped as "Change frame". There are no additional required fields.
changeFrame: 23,
// dumped as "Debug info". There are no additional required fields.
debugInfo: 24,
// dumped as "Fit image". There are no additional required fields.
fitImage: 25,
// dumped as "Rotate image". There are no additional required fields.
rotateImage: 26,
},
/**
* Logger.initializeLogger
* @param {String} applicationName application name
* @param {String} taskId Task identificator (i.e. link to Jira)
* @param {String} serverURL server url to recive logs
* @return {Bool} true if initialization was succeed
* @static
*/
initializeLogger: function(jobId) {
if (!this._logger)
{
this._logger = new LoggerHandler(jobId);
return true;
}
return false;
},
/**
* Logger.addEvent Use this method to add a log event without duration field.
* @param {Logger.EventType} type Event Type
* @param {Object} values Any event values, for example {count: 1, label: 'vehicle'}
* @param {String} message Any string message. Empty by default.
* @static
*/
addEvent: function(type, values, message='') {
this._logger.addEvent(new Logger.LogEvent(type, values, message));
},
/**
* Logger.addContinuedEvent Use to add log event with duration field.
* Duration will be calculated automatically when LogEvent.close() method of
* returned Object will be called. Note: in case of LogEvent.close() method
* will not be callsed event will not be sent to server
* @param {Logger.EventType} type Event Type
* @param {Object} values Any event values, for example {count: 1, label:
* 'vehicle'}
* @param {String} message Any string message. Empty by default.
* @return {LogEvent} instance of LogEvent
* @static
*/
addContinuedEvent: function(type, values, message='') {
return this._logger.addContinuedEvent(new Logger.LogEvent(type, values, message));
},
/**
* Logger.shortkeyLogDecorator use for decorating the shortkey handlers.
* This decorator just create appropriate log event and close it when
* decored function will performed.
* @param {Function} decoredFunc is function for decorating
* @return {Function} is decorated decoredFunc
* @static
*/
shortkeyLogDecorator: function(decoredFunc) {
let self = this;
return function(e, combo) {
if (window.cvat.frozen) {
return;
}
let pressKeyEvent = self.addContinuedEvent(self.EventType.pressShortcut, {key: combo});
let returnValue = decoredFunc(e, combo);
pressKeyEvent.close();
return returnValue;
};
},
/**
* Logger.sendLogs Try to send exception logs to the server immediately.
* @return {Promise}
* @param {LogEvent} exceptionEvent
* @static
*/
sendException: function(message, filename, line, column, stack, client, system) {
return this._logger.sendExceptions(
new Logger.ExceptionEvent(
message,
filename,
line,
column,
stack,
client,
system
)
);
},
/**
* Logger.getLogs Remove and return collected Array of LogEvents from Logger
* @return {Array}
* @static
*/
getLogs: function(appendUserActivity=true)
{
if (appendUserActivity)
{
this.addEvent(Logger.EventType.sendUserActivity, {'working time': this._logger.getWorkingTime()});
this._logger.resetTimer();
}
return this._logger.getLogs();
},
/** Logger.updateUserActivityTimer method updates internal timer for working
* time calculation logic
* @static
*/
updateUserActivityTimer: function()
{
this._logger.updateTimer();
},
/** Logger.setTimeThreshold set time threshold in ms for EventType. If time
* interval betwwen incoming log events less than threshold events will be
* collapsed. Note that result event will have timestamp of first event, In
* case of time threshold used for continued event duration will be
* difference between first and last event timestamps and other fields from
* last event.
* @static
* @param {Logger.EventType} eventType
* @param {Number} threshold
*/
setTimeThreshold: function(eventType, threshold=500)
{
this._logger.setTimeThreshold(eventType, threshold);
},
/** Logger._eventTypeToString private method to transform Logger.EventType
* to string
* @param {Logger.EventType} type Event Type
* @return {String} string reppresentation of Logger.EventType
* @static
*/
eventTypeToString: function(type)
{
switch(type) {
case this.EventType.pasteObject: return 'Paste object';
case this.EventType.changeAttribute: return 'Change attribute';
case this.EventType.dragObject: return 'Drag object';
case this.EventType.deleteObject: return 'Delete object';
case this.EventType.pressShortcut: return 'Press shortcut';
case this.EventType.resizeObject: return 'Resize object';
case this.EventType.sendLogs: return 'Send logs';
case this.EventType.saveJob: return 'Save job';
case this.EventType.jumpFrame: return 'Jump frame';
case this.EventType.drawObject: return 'Draw object';
case this.EventType.changeLabel: return 'Change label';
case this.EventType.sendTaskInfo: return 'Send task info';
case this.EventType.loadJob: return 'Load job';
case this.EventType.moveImage: return 'Move image';
case this.EventType.zoomImage: return 'Zoom image';
case this.EventType.lockObject: return 'Lock object';
case this.EventType.mergeObjects: return 'Merge objects';
case this.EventType.copyObject: return 'Copy object';
case this.EventType.propagateObject: return 'Propagate object';
case this.EventType.undoAction: return 'Undo action';
case this.EventType.redoAction: return 'Redo action';
case this.EventType.sendUserActivity: return 'Send user activity';
case this.EventType.sendException: return 'Send exception';
case this.EventType.changeFrame: return 'Change frame';
case this.EventType.debugInfo: return 'Debug info';
case this.EventType.fitImage: return 'Fit image';
case this.EventType.rotateImage: return 'Rotate image';
default: return 'Unknown';
}
},
};

File diff suppressed because it is too large Load Diff

@ -1,494 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported PolyshapeEditorModel PolyshapeEditorController PolyshapeEditorView */
/* global
Listener:false
POINT_RADIUS:false
PolyShapeModel:false
STROKE_WIDTH:false
SVG:false
BorderSticker:false
*/
"use strict";
class PolyshapeEditorModel extends Listener {
constructor(shapeCollection) {
super("onPolyshapeEditorUpdate", () => this);
this._modeName = 'poly_editing';
this._active = false;
this._shapeCollection = shapeCollection;
this._data = {
points: null,
color: null,
start: null,
oncomplete: null,
type: null,
event: null,
startPoint: null,
id: null,
};
}
edit(type, points, color, start, startPoint, e, oncomplete, id) {
if (!this._active && !window.cvat.mode) {
window.cvat.mode = this._modeName;
this._active = true;
this._data.points = points;
this._data.color = color;
this._data.start = start;
this._data.oncomplete = oncomplete;
this._data.type = type;
this._data.event = e;
this._data.startPoint = startPoint;
this._data.id = id;
this.notify();
}
else if (this._active) {
throw Error('Polyshape has been being edited already');
}
}
finish(points) {
if (this._active && this._data.oncomplete) {
this._data.oncomplete(points);
}
this.cancel();
}
cancel() {
if (this._active) {
this._active = false;
if (window.cvat.mode != this._modeName) {
throw Error(`Inconsistent behaviour has been detected. Edit mode is activated, but mode variable is '${window.cvat.mode}'`);
}
else {
window.cvat.mode = null;
}
this._data.points = null;
this._data.color = null;
this._data.start = null;
this._data.oncomplete = null;
this._data.type = null;
this._data.event = null;
this._data.startPoint = null;
this.notify();
}
}
get active() {
return this._active;
}
get data() {
return this._data;
}
get currentShapes() {
this._shapeCollection.update();
return this._shapeCollection.currentShapes;
}
}
class PolyshapeEditorController {
constructor(model) {
this._model = model;
}
finish(points) {
this._model.finish(points);
}
cancel() {
this._model.cancel();
}
get currentShapes() {
return this._model.currentShapes;
}
}
class PolyshapeEditorView {
constructor(model, controller) {
this._controller = controller;
this._data = null;
this._frameContent = SVG.adopt($('#frameContent')[0]);
this._commonBordersCheckbox = $('#commonBordersCheckbox');
this._originalShapePointsGroup = null;
this._originalShapePoints = [];
this._originalShape = null;
this._correctLine = null;
this._borderSticker = null;
this._scale = window.cvat.player.geometry.scale;
this._frame = window.cvat.player.frames.current;
this._commonBordersCheckbox.on('change.shapeEditor', (e) => {
if (this._correctLine) {
if (!e.target.checked) {
if (this._borderSticker) {
this._borderSticker.disable();
this._borderSticker = null;
}
} else {
this._borderSticker = new BorderSticker(this._correctLine, this._frameContent,
this._controller.currentShapes
.filter((shape) => shape.model.id !== this._data.id),
this._scale);
}
}
});
model.subscribe(this);
}
_rescaleDrawPoints() {
let scale = this._scale;
$('.svg_draw_point').each(function() {
this.instance.radius(POINT_RADIUS / (2 * scale)).attr('stroke-width', STROKE_WIDTH / (2 * scale));
});
}
// After this method start element will be in begin of the array.
// Array will consist only range elements from start to stop
_resortPoints(points, start, stop) {
let sorted = [];
if (points.indexOf(start) === -1 || points.indexOf(stop) === -1) {
throw Error('Point array must consist both start and stop elements');
}
let idx = points.indexOf(start) + 1;
let condition = true; // constant condition is eslint error
while (condition) {
if (idx >= points.length) idx = 0;
if (points[idx] === stop) condition = false;
else sorted.push(points[idx++]);
}
return sorted;
}
// Method represents array like circle list and find shortest way from source to target
// It returns integer number - distance from source to target.
// It can be negative if shortest way is anti clockwise
_findMinCircleDistance(array, source, target) {
let clockwise_distance = 0;
let anti_clockwise_distance = 0;
let source_idx = array.indexOf(source);
let target_idx = array.indexOf(target);
if (source_idx === -1 || target_idx == -1) {
throw Error('Array should consist both elements');
}
let idx = source_idx;
while (array[idx++] != target) {
clockwise_distance ++;
if (idx >= array.length) idx = 0;
}
idx = source_idx;
while (array[idx--] != target) {
anti_clockwise_distance ++;
if (idx < 0) idx = array.length - 1;
}
let offset = Math.min(clockwise_distance, anti_clockwise_distance);
if (anti_clockwise_distance < clockwise_distance) {
offset = -offset;
}
return offset;
}
_addRawPoint(x, y) {
this._correctLine.array().valueOf().pop();
this._correctLine.array().valueOf().push([x, y]);
// not error, specific of the library
this._correctLine.array().valueOf().push([x, y]);
this._correctLine.remember('_paintHandler').drawCircles();
this._correctLine.plot(this._correctLine.array().valueOf());
this._rescaleDrawPoints();
}
_startEdit() {
this._frame = window.cvat.player.frames.current;
let strokeWidth = this._data.type === 'points' ? 0 : STROKE_WIDTH / this._scale;
// Draw copy of original shape
if (this._data.type === 'polygon') {
this._originalShape = this._frameContent.polygon(this._data.points);
}
else {
this._originalShape = this._frameContent.polyline(this._data.points);
}
this._originalShape.attr({
'stroke-width': strokeWidth,
'stroke': 'white',
'fill': 'none',
});
// Create the correct line
this._correctLine = this._frameContent.polyline().draw({snapToGrid: 0.1}).attr({
'stroke-width': strokeWidth / 2,
'fill': 'none',
'stroke': 'red',
}).on('mouseover', () => false);
// Add points to original shape
let pointRadius = POINT_RADIUS / this._scale;
this._originalShapePointsGroup = this._frameContent.group();
for (let point of PolyShapeModel.convertStringToNumberArray(this._data.points)) {
let uiPoint = this._originalShapePointsGroup.circle(pointRadius * 2)
.move(point.x - pointRadius, point.y - pointRadius)
.attr({
'stroke-width': strokeWidth,
'stroke': 'black',
'fill': 'white',
'z_order': Number.MAX_SAFE_INTEGER,
});
this._originalShapePoints.push(uiPoint);
}
const [x, y] = this._data.startPoint
.split(',').map((el) => +el);
let prevPoint = {
x,
y,
};
// draw and remove initial point just to initialize data structures
this._correctLine.draw('point', this._data.event);
this._correctLine.draw('undo');
this._addRawPoint(x, y);
this._frameContent.on('mousemove.polyshapeEditor', (e) => {
if (e.shiftKey && this._data.type !== 'points') {
const delta = Math.sqrt(Math.pow(e.clientX - prevPoint.x, 2)
+ Math.pow(e.clientY - prevPoint.y, 2));
const deltaTreshold = 15;
if (delta > deltaTreshold) {
this._correctLine.draw('point', e);
prevPoint = {
x: e.clientX,
y: e.clientY
};
}
}
});
this._frameContent.on('contextmenu.polyshapeEditor', (e) => {
if (PolyShapeModel.convertStringToNumberArray(this._correctLine.attr('points')).length > 2) {
this._correctLine.draw('undo');
if (this._borderSticker) {
this._borderSticker.reset();
}
} else {
// Finish without points argument is just cancel
this._controller.finish();
}
e.preventDefault();
e.stopPropagation();
});
this._correctLine.on('drawpoint', (e) => {
prevPoint = {
x: e.detail.event.clientX,
y: e.detail.event.clientY
};
this._rescaleDrawPoints();
if (this._borderSticker) {
this._borderSticker.reset();
}
});
this._correctLine.on('drawstart', () => this._rescaleDrawPoints());
for (let instance of this._originalShapePoints) {
instance.on('mouseover', () => {
instance.attr('stroke-width', STROKE_WIDTH * 2 / this._scale);
}).on('mouseout', () => {
instance.attr('stroke-width', STROKE_WIDTH / this._scale);
}).on('mousedown', (e) => {
if (e.which !== 1) {
return;
}
let currentPoints = PolyShapeModel.convertStringToNumberArray(this._data.points);
// replace the latest point from the event
// (which has not precise coordinates, to precise coordinates)
let correctPoints = this._correctLine
.attr('points')
.split(/\s/)
.slice(0, -1);
correctPoints = correctPoints.concat([`${instance.attr('cx')},${instance.attr('cy')}`]).join(' ');
correctPoints = PolyShapeModel.convertStringToNumberArray(correctPoints);
let resultPoints = [];
if (this._data.type === 'polygon') {
let startPtIdx = this._data.start;
let stopPtIdx = $(instance.node).index();
let offset = this._findMinCircleDistance(currentPoints, currentPoints[startPtIdx], currentPoints[stopPtIdx]);
if (!offset) {
currentPoints = this._resortPoints(currentPoints, currentPoints[startPtIdx], currentPoints[stopPtIdx]);
resultPoints.push(...correctPoints.slice(0, -2));
resultPoints.push(...currentPoints);
}
else {
resultPoints.push(...correctPoints);
if (offset < 0) {
resultPoints = resultPoints.reverse();
currentPoints = this._resortPoints(currentPoints, currentPoints[startPtIdx], currentPoints[stopPtIdx]);
}
else {
currentPoints = this._resortPoints(currentPoints, currentPoints[stopPtIdx], currentPoints[startPtIdx]);
}
resultPoints.push(...currentPoints);
}
}
else if (this._data.type === 'polyline') {
let startPtIdx = this._data.start;
let stopPtIdx = $(instance.node).index();
if (startPtIdx === stopPtIdx) {
resultPoints.push(...correctPoints.slice(1, -1).reverse());
resultPoints.push(...currentPoints);
}
else {
if (startPtIdx > stopPtIdx) {
if (startPtIdx < currentPoints.length - 1) {
resultPoints.push(...currentPoints.slice(startPtIdx + 1).reverse());
}
resultPoints.push(...correctPoints.slice(0, -1));
if (stopPtIdx > 0) {
resultPoints.push(...currentPoints.slice(0, stopPtIdx).reverse());
}
}
else {
if (startPtIdx > 0) {
resultPoints.push(...currentPoints.slice(0, startPtIdx));
}
resultPoints.push(...correctPoints.slice(0, -1));
if (stopPtIdx < currentPoints.length) {
resultPoints.push(...currentPoints.slice(stopPtIdx + 1));
}
}
}
}
else {
resultPoints.push(...currentPoints);
resultPoints.push(...correctPoints.slice(1, -1).reverse());
}
this._correctLine.draw('cancel');
this._controller.finish(PolyShapeModel.convertNumberArrayToString(resultPoints));
});
}
this._commonBordersCheckbox.css('display', '').trigger('change.shapeEditor');
this._commonBordersCheckbox.parent().css('display', '');
$('body').on('keydown.shapeEditor', (e) => {
if (e.ctrlKey && e.keyCode === 17) {
this._commonBordersCheckbox.prop('checked', !this._borderSticker);
this._commonBordersCheckbox.trigger('change.shapeEditor');
}
});
}
_endEdit() {
for (let uiPoint of this._originalShapePoints) {
uiPoint.off();
uiPoint.remove();
}
this._originalShapePoints = [];
this._originalShapePointsGroup.remove();
this._originalShapePointsGroup = null;
this._originalShape.remove();
this._originalShape = null;
this._correctLine.off('drawstart');
this._correctLine.off('drawpoint');
this._correctLine.draw('cancel');
this._correctLine.remove();
this._correctLine = null;
this._data = null;
this._frameContent.off('mousemove.polyshapeEditor');
this._frameContent.off('mousedown.polyshapeEditor');
this._frameContent.off('contextmenu.polyshapeEditor');
$('body').off('keydown.shapeEditor');
this._commonBordersCheckbox.css('display', 'none');
this._commonBordersCheckbox.parent().css('display', 'none');
if (this._borderSticker) {
this._borderSticker.disable();
this._borderSticker = null;
}
}
onPolyshapeEditorUpdate(model) {
if (model.active && !this._data) {
this._data = model.data;
this._startEdit();
}
else if (!model.active) {
this._endEdit();
}
}
onPlayerUpdate(player) {
let scale = player.geometry.scale;
if (this._scale != scale) {
this._scale = scale;
if (this._borderSticker) {
this._borderSticker.scale(this._scale);
}
let strokeWidth = this._data && this._data.type === 'points' ? 0 : STROKE_WIDTH / this._scale;
let pointRadius = POINT_RADIUS / this._scale;
if (this._originalShape) {
this._originalShape.attr('stroke-width', strokeWidth);
}
if (this._correctLine) {
this._correctLine.attr('stroke-width', strokeWidth / 2);
}
for (let uiPoint of this._originalShapePoints) {
uiPoint.attr('stroke-width', strokeWidth);
uiPoint.radius(pointRadius);
}
this._rescaleDrawPoints();
}
// Abort if frame have been changed
if (player.frames.current != this._frame && this._data) {
this._controller.cancel();
}
}
}

@ -1,549 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* global
LabelsInfo:false
Listener:false
PlayerModel:false
*/
const tests = [];
const jobData = {
url: 'http://localhost:7000/api/v1/jobs/3',
id: 3,
assignee: null,
status: 'annotation',
start_frame: 0,
stop_frame: 7,
task_id: 3,
};
window.cvat = {
player: {
frames: {
start: jobData.start_frame,
stop: jobData.stop_frame,
current: 0,
},
geometry: {},
},
translate: {},
};
const taskData = {
url: 'http://localhost:7000/api/v1/tasks/3',
id: 3,
name: 'QUnitTests',
size: 8,
mode: 'annotation',
owner: 1,
assignee: null,
bug_tracker: '',
created_date: '2019-03-27T16:19:24.525806+03:00',
updated_date: '2019-03-27T16:19:24.525858+03:00',
overlap: 0,
segment_size: 0,
z_order: false,
flipped: false,
status: 'annotation',
labels: [
{
id: 17,
name: 'bicycle',
attributes: [
{
id: 28,
name: 'driver',
mutable: false,
input_type: 'radio',
default_value: 'man',
values: [
'man',
'woman',
],
},
{
id: 29,
name: 'sport',
mutable: true,
input_type: 'checkbox',
default_value: 'false',
values: [
'false',
],
},
],
},
{
id: 16,
name: 'car',
attributes: [
{
id: 25,
name: 'model',
mutable: false,
input_type: 'select',
default_value: '__undefined__',
values: [
'__undefined__',
'bmw',
'mazda',
'suzuki',
'kia',
],
},
{
id: 26,
name: 'driver',
mutable: false,
input_type: 'select',
default_value: '__undefined__',
values: [
'__undefined__',
'man',
'woman',
],
},
{
id: 27,
name: 'parked',
mutable: true,
input_type: 'checkbox',
default_value: 'true',
values: [
'true',
],
},
],
},
{
id: 15,
name: 'face',
attributes: [
{
id: 21,
name: 'age',
mutable: false,
input_type: 'select',
default_value: '__undefined__',
values: [
'__undefined__',
'skip',
'baby (0-5)',
'child (6-12)',
'adolescent (13-19)',
'adult (20-45)',
'middle-age (46-64)',
'old (65-)',
],
},
{
id: 22,
name: 'glass',
mutable: false,
input_type: 'select',
default_value: '__undefined__',
values: [
'__undefined__',
'skip',
'no',
'sunglass',
'transparent',
'other',
],
},
{
id: 23,
name: 'beard',
mutable: false,
input_type: 'select',
default_value: '__undefined__',
values: [
'__undefined__',
'skip',
'no',
'yes',
],
},
{
id: 24,
name: 'race',
mutable: false,
input_type: 'select',
default_value: '__undefined__',
values: [
'__undefined__',
'skip',
'asian',
'black',
'caucasian',
'other',
],
},
],
},
{
id: 18,
name: 'motorcycle',
attributes: [
{
id: 30,
name: 'model',
mutable: false,
input_type: 'text',
default_value: 'unknown',
values: [
'unknown',
],
},
],
},
{
id: 14,
name: 'person, pedestrian',
attributes: [
{
id: 16,
name: 'action',
mutable: true,
input_type: 'select',
default_value: '__undefined__',
values: [
'__undefined__',
'sitting',
'raising_hand',
'standing',
],
},
{
id: 17,
name: 'age',
mutable: false,
input_type: 'number',
default_value: '1',
values: [
'1',
'100',
'1',
],
},
{
id: 18,
name: 'gender',
mutable: false,
input_type: 'select',
default_value: 'male',
values: [
'male',
'female',
],
},
{
id: 19,
name: 'false positive',
mutable: false,
input_type: 'checkbox',
default_value: 'false',
values: [
'false',
],
},
{
id: 20,
name: 'clother',
mutable: true,
input_type: 'text',
default_value: 'non, initialized',
values: [
'non, initialized',
],
},
],
},
{
id: 19,
name: 'road',
attributes: [],
},
],
segments: [
{
start_frame: 0,
stop_frame: 7,
jobs: [
{
url: 'http://localhost:7000/api/v1/jobs/3',
id: 3,
assignee: null,
status: 'annotation',
},
],
},
],
image_quality: 95,
};
function makeLabelsInfo() {
return new LabelsInfo(taskData.labels);
}
function makePlayerModel() {
const dummyPlayerGeometry = {
width: 800,
height: 600,
left: 10,
top: 10,
};
return new PlayerModel(taskData, dummyPlayerGeometry);
}
// Run all tests
window.addEventListener('DOMContentLoaded', () => {
for (const test of tests) {
test();
}
});
tests.push(() => {
let labelsInfo = null;
QUnit.module('LabelsInfo', {
before() {
labelsInfo = makeLabelsInfo();
},
});
QUnit.test('labelIdOf', (assert) => {
assert.equal(labelsInfo.labelIdOf('bicycle'), 17);
assert.equal(labelsInfo.labelIdOf('car'), 16);
assert.equal(labelsInfo.labelIdOf('face'), 15);
assert.equal(labelsInfo.labelIdOf('motorcycle'), 18);
assert.equal(labelsInfo.labelIdOf('person, pedestrian'), 14);
assert.equal(labelsInfo.labelIdOf('road'), 19);
assert.throws(labelsInfo.labelIdOf.bind(labelsInfo, 'unknown_label'));
});
QUnit.test('attrIdOf', (assert) => {
assert.equal(labelsInfo.attrIdOf(14, 'action'), labelsInfo.attrIdOf('14', 'action'));
assert.equal(labelsInfo.attrIdOf(18, 'model'), 30);
assert.equal(labelsInfo.attrIdOf(15, 'age'), 21);
assert.throws(labelsInfo.attrIdOf.bind(labelsInfo, 15, 'unknown_attribute'));
assert.throws(labelsInfo.attrIdOf.bind(labelsInfo, 99, 'age'));
assert.throws(labelsInfo.attrIdOf.bind(labelsInfo, undefined, 'driver'));
assert.throws(labelsInfo.attrIdOf.bind(labelsInfo, '15', undefined));
});
QUnit.test('normalize', (assert) => {
assert.equal(LabelsInfo.normalize('checkbox', 'false'), false);
assert.equal(LabelsInfo.normalize('checkbox', 'false,true'), true);
assert.equal(LabelsInfo.normalize('checkbox', '0'), false);
assert.equal(LabelsInfo.normalize('checkbox', false), false);
assert.equal(LabelsInfo.normalize('checkbox', 'abrakadabra'), true);
assert.equal(LabelsInfo.normalize('select', 'value1'), 'value1');
assert.equal(LabelsInfo.normalize('text', 'value1,together value2 and 3'), 'value1,together value2 and 3');
assert.equal(LabelsInfo.normalize('radio', 'value'), 'value');
assert.equal(LabelsInfo.normalize('number', '1'), 1);
assert.equal(LabelsInfo.normalize('number', 1), 1);
assert.throws(LabelsInfo.normalize.bind(LabelsInfo, 'number', 'abrakadabra'));
});
QUnit.test('labels', (assert) => {
const expected = {
14: 'person, pedestrian',
15: 'face',
16: 'car',
17: 'bicycle',
18: 'motorcycle',
19: 'road',
};
assert.deepEqual(labelsInfo.labels(), expected);
});
QUnit.test('attributes', (assert) => {
const expected = {
16: 'action',
17: 'age',
18: 'gender',
19: 'false positive',
20: 'clother',
21: 'age',
22: 'glass',
23: 'beard',
24: 'race',
25: 'model',
26: 'driver',
27: 'parked',
28: 'driver',
29: 'sport',
30: 'model',
};
assert.deepEqual(labelsInfo.attributes(), expected);
});
QUnit.test('labelAttributes', (assert) => {
assert.deepEqual(labelsInfo.labelAttributes(14), {
16: 'action',
17: 'age',
18: 'gender',
19: 'false positive',
20: 'clother',
});
assert.deepEqual(labelsInfo.labelAttributes(15), {
21: 'age',
22: 'glass',
23: 'beard',
24: 'race',
});
assert.deepEqual(labelsInfo.labelAttributes(19), {});
assert.deepEqual(labelsInfo.labelAttributes(14), labelsInfo.labelAttributes('14'));
assert.throws(labelsInfo.labelAttributes.bind(labelsInfo, 100));
assert.throws(labelsInfo.labelAttributes.bind(labelsInfo));
assert.throws(labelsInfo.labelAttributes.bind(labelsInfo, null));
});
QUnit.test('attrInfo', (assert) => {
assert.deepEqual(labelsInfo.attrInfo(21), {
mutable: false,
type: 'select',
name: 'age',
values: [
'__undefined__',
'skip',
'baby (0-5)',
'child (6-12)',
'adolescent (13-19)',
'adult (20-45)',
'middle-age (46-64)',
'old (65-)',
],
});
assert.deepEqual(labelsInfo.attrInfo(29), {
mutable: true,
type: 'checkbox',
name: 'sport',
values: [
false,
],
});
assert.deepEqual(labelsInfo.attrInfo(23), labelsInfo.attrInfo('23'));
assert.throws(labelsInfo.attrInfo.bind(labelsInfo, 100));
assert.throws(labelsInfo.attrInfo.bind(labelsInfo));
assert.throws(labelsInfo.attrInfo.bind(labelsInfo, 'clother'));
assert.throws(labelsInfo.attrInfo.bind(labelsInfo, null));
});
});
tests.push(() => {
QUnit.module('Listener');
QUnit.test('subscribe', (assert) => {
const listenerInterface = new Listener('onUpdate', () => {});
const dummyListener1 = {
onUpdate() {},
};
const dummyListener2 = {
onUpdate: 'someProp',
};
const dummyListener3 = {};
const dummyListener4 = {
onUpdate() {},
};
const dummyListener5 = {
onUpdate() {},
};
listenerInterface.subscribe(dummyListener1); // no exceptions, listener added
listenerInterface.subscribe(dummyListener4); // no exceptions, listener added
listenerInterface.subscribe(dummyListener5); // no exceptions, listener added
assert.throws(listenerInterface.subscribe.bind(listenerInterface, dummyListener2));
assert.throws(listenerInterface.subscribe.bind(listenerInterface, dummyListener3));
});
QUnit.test('unsubscribe', (assert) => {
const listenerInterface = new Listener('onUpdate', () => {});
const dummyListener1 = {
onUpdate() {},
};
const dummyListener2 = {
onUpdate() {},
};
const dummyListener3 = {
onUpdate() {},
};
const dummyListener4 = {
onUpdate() {},
};
listenerInterface.subscribe(dummyListener1);
listenerInterface.subscribe(dummyListener2);
listenerInterface.subscribe(dummyListener3);
listenerInterface.subscribe(dummyListener4);
listenerInterface.unsubscribe(dummyListener2);
listenerInterface.unsubscribe(dummyListener4);
assert.throws(listenerInterface.unsubscribe.bind(listenerInterface, null));
assert.throws(listenerInterface.unsubscribe.bind(listenerInterface));
listenerInterface.unsubscribe(dummyListener1);
listenerInterface.unsubscribe(dummyListener3);
});
QUnit.test('unsubscribeAll', (assert) => {
const listenerInterface = new Listener('onUpdate', () => {});
const dummyListener1 = {
onUpdate() {},
};
const dummyListener2 = {
onUpdate() {},
};
listenerInterface.subscribe(dummyListener1);
listenerInterface.subscribe(dummyListener2);
listenerInterface.unsubscribeAll();
assert.expect(0);
});
});
tests.push(() => {
let playerModel = null;
QUnit.module('PlayerModel', {
before() {
playerModel = makePlayerModel();
},
});
QUnit.test('scale', (assert) => {
// Scale when player is not ready
assert.expect(0);
playerModel.scale(20, 20, 1);
});
QUnit.test('fit', (assert) => {
// Fit when player is not ready
assert.expect(0);
playerModel.fit();
});
});
// TODO TESTS FOR ANNOTATIONS PARSER

@ -1,11 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported encodeFilePathToURI */
function encodeFilePathToURI(path) {
return path.split('/').map(x => encodeURIComponent(x)).join('/');
}

@ -1,563 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported ShapeBufferModel ShapeBufferController ShapeBufferView */
/* global
AREA_TRESHOLD:false
userConfirm:false
Listener:false
Logger:false
Mousetrap:false
POINT_RADIUS:false
PolyShapeModel:false
STROKE_WIDTH:false
SVG:false
*/
"use strict";
class ShapeBufferModel extends Listener {
constructor(collection) {
super('onShapeBufferUpdate', () => this);
this._collection = collection;
this._pasteMode = false;
this._propagateFrames = 50;
this._shape = {
type: null,
mode: null,
position: null,
attributes: null,
label: null,
clear: function() {
this.type = null;
this.mode = null;
this.position = null;
this.attributes = null;
this.label = null;
},
};
}
_startPaste() {
if (!this._pasteMode && this._shape.type) {
if (!window.cvat.mode) {
this._collection.resetActive();
window.cvat.mode = 'paste';
this._pasteMode = true;
this.notify();
}
}
}
_cancelPaste() {
if (this._pasteMode) {
if (window.cvat.mode === 'paste') {
window.cvat.mode = null;
this._pasteMode = false;
this.notify();
}
}
}
_makeObject(box, points, isTracked) {
if (!this._shape.type) {
return null;
}
let attributes = [];
let object = {};
for (let attrId in this._shape.attributes) {
attributes.push({
id: attrId,
value: this._shape.attributes[attrId].value,
});
}
object.label_id = this._shape.label;
object.group = 0;
object.frame = window.cvat.player.frames.current;
object.attributes = attributes;
if (this._shape.type === 'box') {
const position = {
xtl: box.xtl,
ytl: box.ytl,
xbr: box.xbr,
ybr: box.ybr,
occluded: this._shape.position.occluded,
frame: window.cvat.player.frames.current,
z_order: this._collection.zOrder(window.cvat.player.frames.current).max,
};
if (isTracked) {
object.shapes = [];
object.shapes.push(Object.assign(position, {
outside: false,
attributes: [],
}));
} else {
Object.assign(object, position);
}
} else {
const position = {};
position.points = points;
position.occluded = this._shape.position.occluded;
position.frame = window.cvat.player.frames.current;
position.z_order = this._collection.zOrder(position.frame).max;
Object.assign(object, position);
}
return object;
}
switchPaste() {
if (this._pasteMode) {
this._cancelPaste();
}
else {
this._startPaste();
}
}
copyToBuffer() {
let activeShape = this._collection.activeShape;
if (activeShape) {
Logger.addEvent(Logger.EventType.copyObject, {
count: 1,
});
let interpolation = activeShape.interpolate(window.cvat.player.frames.current);
if (!interpolation.position.outsided) {
this._shape.type = activeShape.type.split('_')[1];
this._shape.mode = activeShape.type.split('_')[0];
this._shape.label = activeShape.label;
this._shape.attributes = interpolation.attributes;
this._shape.position = interpolation.position;
}
return true;
}
return false;
}
pasteToFrame(box, polyPoints) {
let object = this._makeObject(box, polyPoints, this._shape.mode === 'interpolation');
if (object) {
if (this._shape.type === 'cuboid'
&& !CuboidModel.isWithinFrame(PolyShapeModel.convertStringToNumberArray(polyPoints))) {
return
}
Logger.addEvent(Logger.EventType.pasteObject);
if (this._shape.type === 'box') {
this._collection.add(object, `${this._shape.mode}_${this._shape.type}`);
}
else {
this._collection.add(object, `annotation_${this._shape.type}`);
}
// Undo/redo code
let model = this._collection.shapes.slice(-1)[0];
window.cvat.addAction('Paste Object', () => {
model.removed = true;
model.unsubscribe(this._collection);
}, () => {
model.subscribe(this._collection);
model.removed = false;
}, window.cvat.player.frames.current);
// End of undo/redo code
this._collection.update();
}
}
propagateToFrames() {
let numOfFrames = this._propagateFrames;
if (this._shape.type && Number.isInteger(numOfFrames)) {
let object = null;
if (this._shape.type === 'box') {
let box = {
xtl: this._shape.position.xtl,
ytl: this._shape.position.ytl,
xbr: this._shape.position.xbr,
ybr: this._shape.position.ybr,
};
object = this._makeObject(box, null, false);
} else {
object = this._makeObject(null, this._shape.position.points, false);
}
if (object) {
Logger.addEvent(Logger.EventType.propagateObject, {
count: numOfFrames,
});
let imageSizes = window.cvat.job.images;
let startFrame = window.cvat.player.frames.start;
let originalImageSize = imageSizes.frames[object.frame - startFrame] || imageSizes.frames[0];
// Getting normalized coordinates [0..1]
let normalized = {};
if (this._shape.type === 'box') {
normalized.xtl = object.xtl / originalImageSize.width;
normalized.ytl = object.ytl / originalImageSize.height;
normalized.xbr = object.xbr / originalImageSize.width;
normalized.ybr = object.ybr / originalImageSize.height;
}
else {
normalized.points = [];
for (let point of PolyShapeModel.convertStringToNumberArray(object.points)) {
normalized.points.push({
x: point.x / originalImageSize.width,
y: point.y / originalImageSize.height,
});
}
}
let addedObjects = [];
while (numOfFrames > 0 && (object.frame + 1 <= window.cvat.player.frames.stop)) {
object.frame ++;
numOfFrames --;
object.z_order = this._collection.zOrder(object.frame).max;
let imageSize = imageSizes.frames[object.frame - startFrame] || imageSizes.frames[0];
let position = {};
if (this._shape.type === 'box') {
position.xtl = normalized.xtl * imageSize.width;
position.ytl = normalized.ytl * imageSize.height;
position.xbr = normalized.xbr * imageSize.width;
position.ybr = normalized.ybr * imageSize.height;
}
else {
position.points = [];
for (let point of normalized.points) {
position.points.push({
x: point.x * imageSize.width,
y: point.y * imageSize.height,
});
}
position.points = PolyShapeModel.convertNumberArrayToString(position.points);
}
Object.assign(object, position);
this._collection.add(object, `annotation_${this._shape.type}`);
addedObjects.push(this._collection.shapes.slice(-1)[0]);
}
if (addedObjects.length) {
// Undo/redo code
window.cvat.addAction('Propagate Object', () => {
for (let object of addedObjects) {
object.removed = true;
object.unsubscribe(this._collection);
}
}, () => {
for (let object of addedObjects) {
object.removed = false;
object.subscribe(this._collection);
}
}, window.cvat.player.frames.current);
// End of undo/redo code
}
}
}
}
get pasteMode() {
return this._pasteMode;
}
get shape() {
return this._shape;
}
set propagateFrames(value) {
this._propagateFrames = value;
}
get propagateFrames() {
return this._propagateFrames;
}
}
class ShapeBufferController {
constructor(model) {
this._model = model;
setupBufferShortkeys.call(this);
function setupBufferShortkeys() {
let copyHandler = Logger.shortkeyLogDecorator(function() {
this._model.copyToBuffer();
}.bind(this));
let switchHandler = Logger.shortkeyLogDecorator(function() {
this._model.switchPaste();
}.bind(this));
let propagateDialogShowed = false;
let propagateHandler = Logger.shortkeyLogDecorator(function() {
if (!propagateDialogShowed) {
blurAllElements();
if (this._model.copyToBuffer()) {
let curFrame = window.cvat.player.frames.current;
let startFrame = window.cvat.player.frames.start;
let endFrame = Math.min(window.cvat.player.frames.stop, curFrame + this._model.propagateFrames);
let imageSizes = window.cvat.job.images;
let message = `Propagate up to ${endFrame} frame. `;
let refSize = imageSizes.frames[curFrame - startFrame] || imageSizes.frames[0];
for (let _frame = curFrame + 1; _frame <= endFrame; _frame ++) {
let size = imageSizes.frames[_frame - startFrame] || imageSizes.frames[0];
if ((size.width != refSize.width) || (size.height != refSize.height) ) {
message += 'Some covered frames have another resolution. Shapes in them can differ from reference. ';
break;
}
}
message += 'Are you sure?';
propagateDialogShowed = true;
userConfirm(message, () => {
this._model.propagateToFrames();
propagateDialogShowed = false;
}, () => propagateDialogShowed = false);
}
}
}.bind(this));
let shortkeys = window.cvat.config.shortkeys;
Mousetrap.bind(shortkeys["copy_shape"].value, copyHandler, 'keydown');
Mousetrap.bind(shortkeys["propagate_shape"].value, propagateHandler, 'keydown');
Mousetrap.bind(shortkeys["switch_paste"].value, switchHandler, 'keydown');
}
}
pasteToFrame(e, bbRect, polyPoints) {
if (this._model.pasteMode) {
if (bbRect || polyPoints) {
this._model.pasteToFrame(bbRect, polyPoints);
}
if (!e.ctrlKey) {
this._model.switchPaste();
}
}
}
set propagateFrames(value) {
this._model.propagateFrames = value;
}
}
class ShapeBufferView {
constructor(model, controller) {
model.subscribe(this);
this._controller = controller;
this._frameContent = SVG.adopt($('#frameContent')[0]);
this._propagateFramesInput = $('#propagateFramesInput');
this._shape = null;
this._shapeView = null;
this._shapeViewGroup = null;
this._controller.propagateFrames = +this._propagateFramesInput.prop('value');
this._propagateFramesInput.on('change', (e) => {
let value = Math.clamp(+e.target.value, +e.target.min, +e.target.max);
e.target.value = value;
this._controller.propagateFrames = value;
});
}
_drawShapeView() {
let scale = window.cvat.player.geometry.scale;
let points = this._shape.position.points ?
window.cvat.translate.points.actualToCanvas(this._shape.position.points) : null;
switch (this._shape.type) {
case 'box': {
let width = this._shape.position.xbr - this._shape.position.xtl;
let height = this._shape.position.ybr - this._shape.position.ytl;
this._shape.position = window.cvat.translate.box.actualToCanvas(this._shape.position);
this._shapeView = this._frameContent.rect(width, height)
.move(this._shape.position.xtl, this._shape.position.ytl).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / scale,
});
break;
}
case 'polygon':
this._shapeView = this._frameContent.polygon(points).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / scale,
});
break;
case 'cuboid':
points = window.cvat.translate.points.canvasToActual(points);
points = PolylineModel.convertStringToNumberArray(points);
let view_model = new Cuboid2PointViewModel(points);
this._shapeView = this._frameContent.polyline(points).addClass('shapeCreation').attr({
'stroke-width': 0,
});
this._shapeViewGroup = this._frameContent.cube(view_model).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / scale,
});
break;
case 'polyline':
this._shapeView = this._frameContent.polyline(points).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / scale,
});
break;
case 'points':
this._shapeView = this._frameContent.polyline(points).addClass('shapeCreation').attr({
'stroke-width': 0,
});
this._shapeViewGroup = this._frameContent.group();
for (let point of PolyShapeModel.convertStringToNumberArray(points)) {
let radius = POINT_RADIUS * 2 / window.cvat.player.geometry.scale;
let scaledStroke = STROKE_WIDTH / window.cvat.player.geometry.scale;
this._shapeViewGroup.circle(radius).move(point.x - radius / 2, point.y - radius / 2)
.fill('white').stroke('black').attr('stroke-width', scaledStroke).addClass('pasteTempMarker');
}
break;
default:
throw Error(`Unknown shape type found: ${this._shape.type}`);
}
this._shapeView.attr({
'z_order': Number.MAX_SAFE_INTEGER,
});
}
_moveShapeView(pos) {
let rect = this._shapeView.node.getBBox();
this._shapeView.move(pos.x - rect.width / 2, pos.y - rect.height / 2);
if (this._shapeViewGroup) {
let rect = this._shapeViewGroup.node.getBBox();
this._shapeViewGroup.move(pos.x - rect.x - rect.width / 2, pos.y - rect.y - rect.height / 2);
}
}
_removeShapeView() {
this._shapeView.remove();
this._shapeView = null;
if (this._shapeViewGroup) {
this._shapeViewGroup.remove();
this._shapeViewGroup = null;
}
}
_enableEvents() {
this._frameContent.on('mousemove.buffer', (e) => {
let pos = window.cvat.translate.point.clientToCanvas(this._frameContent.node, e.clientX, e.clientY);
this._shapeView.style('visibility', '');
this._moveShapeView(pos);
});
this._frameContent.on('mousedown.buffer', (e) => {
if (e.which != 1) return;
if (this._shape.type != 'box') {
let actualPoints = window.cvat.translate.points.canvasToActual(this._shapeView.attr('points'));
let frameWidth = window.cvat.player.geometry.frameWidth;
let frameHeight = window.cvat.player.geometry.frameHeight;
if (this.clipToFrame) {
actualPoints = PolyShapeModel.convertStringToNumberArray(actualPoints);
for (let point of actualPoints) {
point.x = Math.clamp(point.x, 0, frameWidth);
point.y = Math.clamp(point.y, 0, frameHeight);
}
actualPoints = PolyShapeModel.convertNumberArrayToString(actualPoints);
}
// Set clamped points to a view in order to get an updated bounding box for a poly shape
this._shapeView.attr('points', window.cvat.translate.points.actualToCanvas(actualPoints));
// Get an updated bounding box for check it area
let polybox = this._shapeView.node.getBBox();
let w = polybox.width;
let h = polybox.height;
let area = w * h;
let type = this._shape.type;
if (area >= AREA_TRESHOLD || type === 'points' || type === 'polyline' && (w >= AREA_TRESHOLD || h >= AREA_TRESHOLD)) {
this._controller.pasteToFrame(e, null, actualPoints);
}
else {
this._controller.pasteToFrame(e, null, null);
}
}
else {
let frameWidth = window.cvat.player.geometry.frameWidth;
let frameHeight = window.cvat.player.geometry.frameHeight;
let rect = window.cvat.translate.box.canvasToActual(this._shapeView.node.getBBox());
let box = {};
box.xtl = Math.clamp(rect.x, 0, frameWidth);
box.ytl = Math.clamp(rect.y, 0, frameHeight);
box.xbr = Math.clamp(rect.x + rect.width, 0, frameWidth);
box.ybr = Math.clamp(rect.y + rect.height, 0, frameHeight);
if ((box.xbr - box.xtl) * (box.ybr - box.ytl) >= AREA_TRESHOLD) {
this._controller.pasteToFrame(e, box, null);
}
else {
this._controller.pasteToFrame(e, null, null);
}
}
});
this._frameContent.on('mouseleave.buffer', () => {
this._shapeView.style('visibility', 'hidden');
});
}
_disableEvents() {
this._frameContent.off('mousemove.buffer');
this._frameContent.off('mousedown.buffer');
this._frameContent.off('mouseleave.buffer');
}
onShapeBufferUpdate(buffer) {
if (buffer.pasteMode) {
this._shape = buffer.shape;
this._drawShapeView();
this._enableEvents();
}
else {
if (this._shapeView) {
this._disableEvents();
this._removeShapeView();
}
}
}
onBufferUpdate(buffer) {
if (buffer.pasteMode && !this._pasteMode) {
this._pasteMode = true;
this._shape = buffer.shape;
this.enableMouseEvents();
}
else if (!buffer.pasteMode) {
this.disableMouseEvents();
if (this._viewShape) {
this._viewShape.remove();
}
this._viewShape = null;
this._shape = null;
this._pasteMode = false;
}
}
onPlayerUpdate(player) {
if (!player.ready()) return;
if (this._shapeView != null && this._shape.type != 'points') {
this._shapeView.attr('stroke-width', STROKE_WIDTH / player.geometry.scale);
}
}
}

File diff suppressed because it is too large Load Diff

@ -1,963 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported ShapeCreatorModel ShapeCreatorController ShapeCreatorView */
/* global
AREA_TRESHOLD:false
drawBoxSize:false
Listener:false
Logger:false
Mousetrap:false
PolyShapeModel:false
showMessage:false
STROKE_WIDTH:false
SVG:false
BorderSticker: false
CuboidModel:false
Cuboid2PointViewModel:false
*/
class ShapeCreatorModel extends Listener {
constructor(shapeCollection) {
super('onShapeCreatorUpdate', () => this);
this._createMode = false;
this._saveCurrent = false;
this._defaultType = null;
this._defaultMode = null;
this._defaultLabel = null;
this._currentFrame = null;
this._createEvent = null;
this._shapeCollection = shapeCollection;
}
finish(result) {
const data = {};
const frame = window.cvat.player.frames.current;
data.label_id = this._defaultLabel;
data.group = 0;
data.frame = frame;
data.occluded = false;
data.outside = false;
data.z_order = this._shapeCollection.zOrder(frame).max;
data.attributes = [];
if (this._createEvent) {
this._createEvent.addValues({
mode: this._defaultMode,
type: this._defaultType,
label: this._defaultLabel,
frame,
});
}
// FIXME: In the future we have to make some generic solution
if (this._defaultMode === 'interpolation'
&& ['box', 'points', 'box_by_4_points'].includes(this._defaultType)) {
data.shapes = [];
data.shapes.push(Object.assign({}, result, data));
this._shapeCollection.add(data, `interpolation_${this._defaultType}`);
} else {
Object.assign(data, result);
this._shapeCollection.add(data, `annotation_${this._defaultType}`);
}
const model = this._shapeCollection.shapes.slice(-1)[0];
// Undo/redo code
window.cvat.addAction('Draw Object', () => {
model.removed = true;
model.unsubscribe(this._shapeCollection);
}, () => {
model.subscribe(this._shapeCollection);
model.removed = false;
}, window.cvat.player.frames.current);
// End of undo/redo code
this._shapeCollection.update();
}
switchCreateMode(forceClose, usingShortkey) {
this._usingShortkey = usingShortkey;
// if parameter force (bool) setup to true, current result will not save
if (!forceClose) {
this._createMode = !this._createMode && window.cvat.mode == null;
if (this._createMode) {
this._createEvent = Logger.addContinuedEvent(Logger.EventType.drawObject);
window.cvat.mode = 'creation';
} else if (window.cvat.mode === 'creation') {
window.cvat.mode = null;
}
} else {
this._createMode = false;
if (window.cvat.mode === 'creation') {
window.cvat.mode = null;
if (this._createEvent) {
this._createEvent.close();
this._createEvent = null;
}
}
}
this._saveCurrent = !forceClose;
this.notify();
}
get currentShapes() {
this._shapeCollection.update();
return this._shapeCollection.currentShapes;
}
get saveCurrent() {
return this._saveCurrent;
}
get createMode() {
return this._createMode;
}
get usingShortkey() {
return this._usingShortkey;
}
get defaultType() {
return this._defaultType;
}
set defaultType(type) {
if (!['box', 'box_by_4_points', 'points', 'polygon', 'polyline', "cuboid"].includes(type)) {
throw Error(`Unknown shape type found ${type}`);
}
this._defaultType = type;
}
get defaultMode() {
return this._defaultMode;
}
set defaultMode(mode) {
this._defaultMode = mode;
}
set defaultLabel(labelId) {
this._defaultLabel = +labelId;
}
}
class ShapeCreatorController {
constructor(drawerModel) {
this._model = drawerModel;
setupShortkeys.call(this);
function setupShortkeys() {
let shortkeys = window.cvat.config.shortkeys;
let switchDrawHandler = Logger.shortkeyLogDecorator(function() {
this.switchCreateMode(false, true);
}.bind(this));
Mousetrap.bind(shortkeys["switch_draw_mode"].value, switchDrawHandler.bind(this), 'keydown');
}
}
switchCreateMode(force, usingShortkey = false) {
this._model.switchCreateMode(force, usingShortkey);
}
setDefaultShapeType(type) {
this._model.defaultType = type;
}
setDefaultShapeMode(mode) {
this._model.defaultMode = mode;
}
setDefaultShapeLabel(labelId) {
this._model.defaultLabel = labelId;
}
finish(result) {
this._model.finish(result);
}
get currentShapes() {
return this._model.currentShapes;
}
}
class ShapeCreatorView {
constructor(drawerModel, drawerController) {
drawerModel.subscribe(this);
this._controller = drawerController;
this._createButton = $('#createShapeButton');
this._labelSelector = $('#shapeLabelSelector');
this._modeSelector = $('#shapeModeSelector');
this._typeSelector = $('#shapeTypeSelector');
this._polyShapeSizeInput = $('#polyShapeSize');
this._commonBordersCheckbox = $('#commonBordersCheckbox');
this._frameContent = SVG.adopt($('#frameContent')[0]);
this._frameText = SVG.adopt($('#frameText')[0]);
this._playerFrame = $('#playerFrame');
this._createButton.on('click', () => this._controller.switchCreateMode(false));
this._drawInstance = null;
this._aim = null;
this._aimCoord = {
x: 0,
y: 0
};
this._polyShapeSize = 0;
this._type = null;
this._mode = null;
this._cancel = false;
this._scale = 1;
this._borderSticker = null;
let shortkeys = window.cvat.config.shortkeys;
this._createButton.attr('title', `
${shortkeys['switch_draw_mode'].view_value} - ${shortkeys['switch_draw_mode'].description}`);
this._labelSelector.attr('title', `
${shortkeys['change_default_label'].view_value} - ${shortkeys['change_default_label'].description}`);
const labels = window.cvat.labelsInfo.labels();
const labelsKeys = Object.keys(labels);
for (let i = 0; i < labelsKeys.length; i += 1) {
this._labelSelector.append(
// eslint-disable-next-line
$(`<option value=${labelsKeys[i]}> ${labels[labelsKeys[i]].normalize()} </option>`)
);
}
this._labelSelector.val(labelsKeys[0]);
this._typeSelector.val('box');
this._typeSelector.on('change', (e) => {
// FIXME: In the future we have to make some generic solution
const mode = this._modeSelector.prop('value');
const type = $(e.target).prop('value');
if (type !== 'box' && type !== 'box_by_4_points'
&& !(type === 'points' && this._polyShapeSize === 1) && mode !== 'annotation') {
this._modeSelector.prop('value', 'annotation');
this._controller.setDefaultShapeMode('annotation');
showMessage('Only the annotation mode allowed for the shape');
}
this._controller.setDefaultShapeType(type);
}).trigger('change');
this._labelSelector.on('change', (e) => {
this._controller.setDefaultShapeLabel($(e.target).prop('value'));
}).trigger('change');
this._modeSelector.on('change', (e) => {
// FIXME: In the future we have to make some generic solution
const mode = $(e.target).prop('value');
const type = this._typeSelector.prop('value');
if (mode !== 'annotation' && !(type === 'points' && this._polyShapeSize === 1)
&& type !== 'box' && type !== 'box_by_4_points') {
this._typeSelector.prop('value', 'box');
this._controller.setDefaultShapeType('box');
showMessage('Only boxes and single point allowed in the interpolation mode');
}
this._controller.setDefaultShapeMode(mode);
}).trigger('change');
this._polyShapeSizeInput.on('change', (e) => {
e.stopPropagation();
let size = +e.target.value;
if (size < 0) size = 0;
if (size > 100) size = 0;
const mode = this._modeSelector.prop('value');
const type = this._typeSelector.prop('value');
if (mode === 'interpolation' && type === 'points' && size !== 1) {
showMessage('Only single point allowed in the interpolation mode');
size = 1;
}
e.target.value = size || '';
this._polyShapeSize = size;
}).trigger('change');
this._polyShapeSizeInput.on('keydown', function(e) {
e.stopPropagation();
});
this._playerFrame.on('mousemove.shapeCreatorAIM', (e) => {
if (!['polygon', 'polyline', 'points'].includes(this._type)) {
this._aimCoord = window.cvat.translate.point.clientToCanvas(this._frameContent.node, e.clientX, e.clientY);
if (this._aim) {
this._aim.x.attr({
y1: this._aimCoord.y,
y2: this._aimCoord.y,
});
this._aim.y.attr({
x1: this._aimCoord.x,
x2: this._aimCoord.x,
});
}
}
});
this._commonBordersCheckbox.on('change.shapeCreator', (e) => {
if (this._drawInstance) {
if (!e.target.checked) {
if (this._borderSticker) {
this._borderSticker.disable();
this._borderSticker = null;
}
} else {
this._borderSticker = new BorderSticker(this._drawInstance, this._frameContent,
this._controller.currentShapes, this._scale);
}
}
});
}
_createCuboidEvent() {
let sizeUI = null;
const backFaceOffset = 20;
this._drawInstance = this._frameContent.rect().draw({ snapToGrid: 0.1 }).addClass("shapeCreation").attr({
"stroke-width": STROKE_WIDTH / this._scale,
})
.on("drawstop", (e) => {
if (this._cancel) {
return;
}
if (sizeUI) {
sizeUI.rm();
sizeUI = null;
}
const rect = window.cvat.translate.box.canvasToActual(e.target.getBBox());
const p1 = { x: rect.x, y: rect.y + 1 };
const p2 = { x: rect.x, y: rect.y - 1 + rect.height };
const p3 = { x: rect.x + rect.width, y: rect.y };
const p4 = { x: rect.x + rect.width, y: rect.y + rect.height };
const p5 = { x: p3.x + backFaceOffset, y: p3.y - backFaceOffset + 1 };
const p6 = { x: p3.x + backFaceOffset, y: p4.y - backFaceOffset - 1 };
let points = [p1, p2, p3, p4, p5, p6];
if (!CuboidModel.isWithinFrame(points)) {
this._controller.switchCreateMode(true);
return;
}
const viewModel = new Cuboid2PointViewModel(points);
points = viewModel.getPoints();
points = PolyShapeModel.convertNumberArrayToString(points);
e.target.setAttribute("points",
window.cvat.translate.points.actualToCanvas(points));
this._controller.finish({ points }, this._type);
this._controller.switchCreateMode(true);
})
.on("drawupdate", (e) => {
sizeUI = drawBoxSize.call(sizeUI, this._frameContent,
this._frameText, e.target.getBBox());
})
.on("drawcancel", () => {
if (sizeUI) {
sizeUI.rm();
sizeUI = null;
}
});
}
_createPolyEvents() {
// If number of points for poly shape specified, use it.
// Dicrement number on draw new point events. Drawstart trigger when create first point
let lastPoint = {
x: null,
y: null,
};
let numberOfPoints = 0;
this._drawInstance.attr({
z_order: Number.MAX_SAFE_INTEGER,
});
if (this._polyShapeSize) {
let size = this._polyShapeSize;
const sizeDecrement = function sizeDecrement() {
size -= 1;
if (!size) {
numberOfPoints = this._polyShapeSize;
this._drawInstance.draw('done');
}
}.bind(this);
const sizeIncrement = function sizeIncrement() {
size += 1;
};
this._drawInstance.on('drawstart', sizeDecrement);
this._drawInstance.on('drawpoint', sizeDecrement);
this._drawInstance.on('undopoint', sizeIncrement);
}
// Otherwise draw will stop by Ctrl + N press
this._drawInstance.on('drawpoint', () => {
if (this._borderSticker) {
this._borderSticker.reset();
}
});
// Callbacks for point scale
this._drawInstance.on('drawstart', this._rescaleDrawPoints.bind(this));
this._drawInstance.on('drawpoint', this._rescaleDrawPoints.bind(this));
this._drawInstance.on('drawstart', (e) => {
lastPoint = {
x: e.detail.event.clientX,
y: e.detail.event.clientY,
};
numberOfPoints ++;
});
this._drawInstance.on('drawpoint', (e) => {
lastPoint = {
x: e.detail.event.clientX,
y: e.detail.event.clientY,
};
numberOfPoints += 1;
if (this._type === "cuboid" && numberOfPoints === 4) {
this._drawInstance.draw("done");
}
});
this._commonBordersCheckbox.css('display', '').trigger('change.shapeCreator');
this._commonBordersCheckbox.parent().css('display', '');
$('body').on('keydown.shapeCreator', (e) => {
if (e.ctrlKey && e.keyCode === 17) {
this._commonBordersCheckbox.prop('checked', !this._borderSticker);
this._commonBordersCheckbox.trigger('change.shapeCreator');
}
});
this._frameContent.on('mousedown.shapeCreator', (e) => {
if (e.which === 3) {
let lenBefore = this._drawInstance.array().value.length;
this._drawInstance.draw('undo');
if (this._borderSticker) {
this._borderSticker.reset();
}
let lenAfter = this._drawInstance.array().value.length;
if (lenBefore != lenAfter) {
numberOfPoints --;
}
}
});
this._frameContent.on('mousemove.shapeCreator', (e) => {
if (e.shiftKey && ['polygon', 'polyline'].includes(this._type)) {
if (lastPoint.x === null || lastPoint.y === null) {
this._drawInstance.draw('point', e);
}
else {
let delta = Math.sqrt(Math.pow(e.clientX - lastPoint.x, 2) + Math.pow(e.clientY - lastPoint.y, 2));
let deltaTreshold = 15;
if (delta > deltaTreshold) {
this._drawInstance.draw('point', e);
lastPoint = {
x: e.clientX,
y: e.clientY,
};
}
}
}
});
this._drawInstance.on('drawstop', () => {
this._frameContent.off('mousedown.shapeCreator');
this._frameContent.off('mousemove.shapeCreator');
this._commonBordersCheckbox.css('display', 'none');
this._commonBordersCheckbox.parent().css('display', 'none');
$('body').off('keydown.shapeCreator');
if (this._borderSticker) {
this._borderSticker.disable();
this._borderSticker = null;
}
});
// Also we need callback on drawdone event for get points
this._drawInstance.on("drawdone", (e) => {
let actualPoints = window.cvat.translate.points.canvasToActual(e.target.getAttribute("points"));
actualPoints = PolyShapeModel.convertStringToNumberArray(actualPoints);
// Min 2 points for polyline and 3 points for polygon
if (actualPoints.length) {
if (this._type === 'polyline' && actualPoints.length < 2) {
showMessage("Min 2 points must be for polyline drawing.");
}
else if (this._type === 'polygon' && actualPoints.length < 3) {
showMessage("Min 3 points must be for polygon drawing.");
} else if (this._type === "cuboid" && (actualPoints.length !== 4)) {
showMessage("Exactly 4 points must be used for cuboid drawing."
+ " Second point must be below the first point."
+ "(HINT) The first 3 points define the front face"
+ " and the last point should define the depth and orientation of the cuboid ");
} else if (this._type === "cuboid") {
let points = this._makeCuboid(actualPoints);
const viewModel = new Cuboid2PointViewModel(points);
if (!CuboidModel.isWithinFrame(points)) {
this._controller.switchCreateMode(true);
return;
}
points = viewModel.getPoints();
points = PolyShapeModel.convertNumberArrayToString(points);
e.target.setAttribute("points",
window.cvat.translate.points.actualToCanvas(points));
this._controller.finish({ points }, this._type);
this._controller.switchCreateMode(true);
} else {
const { frameWidth } = window.cvat.player.geometry;
const { frameHeight } = window.cvat.player.geometry;
for (const point of actualPoints) {
point.x = Math.clamp(point.x, 0, frameWidth);
point.y = Math.clamp(point.y, 0, frameHeight);
}
actualPoints = PolyShapeModel.convertNumberArrayToString(actualPoints);
// Update points in a view in order to get an updated box
e.target.setAttribute("points", window.cvat.translate.points.actualToCanvas(actualPoints));
const polybox = e.target.getBBox();
const w = polybox.width;
const h = polybox.height;
const area = w * h;
const type = this._type;
if (area >= AREA_TRESHOLD || type === "points" && numberOfPoints || type === "polyline" && (w >= AREA_TRESHOLD || h >= AREA_TRESHOLD)) {
this._controller.finish({ points: actualPoints }, type);
}
}
}
this._controller.switchCreateMode(true);
});
}
_sortClockwise(points){
points.sort((a, b) => a.y - b.y);
// Get center y
const cy = (points[0].y + points[points.length - 1].y) / 2;
// Sort from right to left
points.sort((a, b) => b.x - a.x);
// Get center x
const cx = (points[0].x + points[points.length - 1].x) / 2;
// Center point
var center = {
x : cx,
y : cy
};
// Starting angle used to reference other angles
var startAng;
points.forEach((point) => {
var ang = Math.atan2(point.y - center.y, point.x - center.x);
if (!startAng) {
startAng = ang;
} else {
if (ang < startAng) { // ensure that all points are clockwise of the start point
ang += Math.PI * 2;
}
}
point.angle = ang; // add the angle to the point
});
// first sort clockwise
points.sort((a, b) => a.angle - b.angle);
return points.reverse();
}
_makeCuboid(actualPoints){
let unsortedPlanePoints = actualPoints.slice(0,3);
function rotate( array , times ){
while( times-- ){
var temp = array.shift();
array.push( temp );
}
}
let plane1;
let plane2 = {p1:actualPoints[0], p2:actualPoints[0], p3:actualPoints[0], p4:actualPoints[0]};
// completing the plane
const vector = {
x: actualPoints[2].x - actualPoints[1].x,
y: actualPoints[2].y - actualPoints[1].y,
}
// sorting the first plane
unsortedPlanePoints.push({x:actualPoints[0].x + vector.x, y: actualPoints[0].y + vector.y});
let sortedPlanePoints = this._sortClockwise(unsortedPlanePoints);
let leftIndex = 0;
for(let i = 0; i<4; i++){
leftIndex = sortedPlanePoints[`${i}`].x < sortedPlanePoints[`${leftIndex}`].x ? i : leftIndex;
}
rotate(sortedPlanePoints,leftIndex);
plane1 = {
p1:sortedPlanePoints[0],
p2:sortedPlanePoints[1],
p3:sortedPlanePoints[2],
p4:sortedPlanePoints[3]
};
const vec = {
x: actualPoints[3].x - actualPoints[2].x,
y: actualPoints[3].y - actualPoints[2].y,
};
// determine the orientation
let angle = Math.atan2(vec.y,vec.x);
// making the other plane
plane2.p1 = {x:plane1.p1.x + vec.x, y:plane1.p1.y + vec.y};
plane2.p2 = {x:plane1.p2.x + vec.x, y:plane1.p2.y + vec.y};
plane2.p3 = {x:plane1.p3.x + vec.x, y:plane1.p3.y + vec.y};
plane2.p4 = {x:plane1.p4.x + vec.x, y:plane1.p4.y + vec.y};
let points ;
// right
if(Math.abs(angle) < Math.PI/2-0.1){
return this._setupCuboidPoints(actualPoints);
}
// left
else if(Math.abs(angle) > Math.PI/2+0.1){
return this._setupCuboidPoints(actualPoints);
}
// down
else if(angle>0){
points = [plane1.p1,plane2.p1,plane1.p2,plane2.p2,plane1.p3,plane2.p3];
points[0].y+=0.1;
points[4].y+=0.1;
return [plane1.p1,plane2.p1,plane1.p2,plane2.p2,plane1.p3,plane2.p3];
}
// up
else{
points = [plane2.p1,plane1.p1,plane2.p2,plane1.p2,plane2.p3,plane1.p3];
points[0].y+=0.1;
points[4].y+=0.1;
return points;
}
}
_setupCuboidPoints(actualPoints) {
let left,right,left2,right2;
let p1,p2,p3,p4,p5,p6;
const height = Math.abs(actualPoints[0].x - actualPoints[1].x)
< Math.abs(actualPoints[1].x - actualPoints[2].x)
? Math.abs(actualPoints[1].y - actualPoints[0].y)
: Math.abs(actualPoints[1].y - actualPoints[2].y);
// seperate into left and right point
// we pick the first and third point because we know assume they will be on
// opposite corners
if(actualPoints[0].x < actualPoints[2].x){
left = actualPoints[0];
right = actualPoints[2];
}else{
left = actualPoints[2];
right = actualPoints[0];
}
// get other 2 points using the given height
if(left.y < right.y){
left2 = { x: left.x, y: left.y + height };
right2 = { x: right.x, y: right.y - height };
}else{
left2 = { x: left.x, y: left.y - height };
right2 = { x: right.x, y: right.y + height };
}
// get the vector for the last point relative to the previous point
const vec = {
x: actualPoints[3].x - actualPoints[2].x,
y: actualPoints[3].y - actualPoints[2].y,
};
if(left.y < left2.y){
p1 = left;
p2 = left2;
}else{
p1 = left2;
p2 = left;
}
if(right.y < right2.y){
p3 = right;
p4 = right2;
}else{
p3 = right2;
p4 = right;
}
p5 = { x: p3.x + vec.x, y: p3.y + vec.y + 0.1 };
p6 = { x: p4.x + vec.x, y: p4.y + vec.y - 0.1 };
p1.y += 0.1;
return [p1, p2, p3, p4, p5, p6];
}
_create() {
let sizeUI = null;
switch(this._type) {
case 'box':
this._drawInstance = this._frameContent.rect().draw({ snapToGrid: 0.1 }).addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / this._scale,
z_order: Number.MAX_SAFE_INTEGER,
}).on('drawstop', function(e) {
if (this._cancel) return;
if (sizeUI) {
sizeUI.rm();
sizeUI = null;
}
const frameWidth = window.cvat.player.geometry.frameWidth;
const frameHeight = window.cvat.player.geometry.frameHeight;
const rect = window.cvat.translate.box.canvasToActual(e.target.getBBox());
const xtl = Math.clamp(rect.x, 0, frameWidth);
const ytl = Math.clamp(rect.y, 0, frameHeight);
const xbr = Math.clamp(rect.x + rect.width, 0, frameWidth);
const ybr = Math.clamp(rect.y + rect.height, 0, frameHeight);
if ((ybr - ytl) * (xbr - xtl) >= AREA_TRESHOLD) {
const box = {
xtl,
ytl,
xbr,
ybr,
};
if (this._mode === 'interpolation') {
box.outside = false;
}
this._controller.finish(box, this._type);
}
this._controller.switchCreateMode(true);
}.bind(this)).on('drawupdate', (e) => {
sizeUI = drawBoxSize.call(sizeUI, this._frameContent, this._frameText, e.target.getBBox());
}).on('drawcancel', () => {
if (sizeUI) {
sizeUI.rm();
sizeUI = null;
}
});
break;
case 'box_by_4_points':
let numberOfPoints = 0;
this._drawInstance = this._frameContent.polyline().draw({ snapToGrid: 0.1 })
.addClass('shapeCreation').attr({
'stroke-width': 0,
}).on('drawstart', () => {
// init numberOfPoints as one on drawstart
numberOfPoints = 1;
}).on('drawpoint', (e) => {
// increase numberOfPoints by one on drawpoint
numberOfPoints += 1;
// finish if numberOfPoints are exactly four
if (numberOfPoints === 4) {
let actualPoints = window.cvat.translate.points.canvasToActual(e.target.getAttribute('points'));
actualPoints = PolyShapeModel.convertStringToNumberArray(actualPoints);
const { frameWidth, frameHeight } = window.cvat.player.geometry;
// init bounding box
const box = {
'xtl': frameWidth,
'ytl': frameHeight,
'xbr': 0,
'ybr': 0
};
for (const point of actualPoints) {
// clamp point
point.x = Math.clamp(point.x, 0, frameWidth);
point.y = Math.clamp(point.y, 0, frameHeight);
// update bounding box
box.xtl = Math.min(point.x, box.xtl);
box.ytl = Math.min(point.y, box.ytl);
box.xbr = Math.max(point.x, box.xbr);
box.ybr = Math.max(point.y, box.ybr);
}
if ((box.ybr - box.ytl) * (box.xbr - box.xtl) >= AREA_TRESHOLD) {
if (this._mode === 'interpolation') {
box.outside = false;
}
// finish drawing
this._controller.finish(box, this._type);
}
this._controller.switchCreateMode(true);
}
}).on('undopoint', () => {
if (numberOfPoints > 0) {
numberOfPoints -= 1;
}
}).off('drawdone').on('drawdone', () => {
if (numberOfPoints !== 4) {
showMessage('Click exactly four extreme points for an object');
this._controller.switchCreateMode(true);
} else {
throw Error('numberOfPoints is exactly four, but box drawing did not finish.');
}
});
this._createPolyEvents();
break;
case 'points':
this._drawInstance = this._frameContent.polyline().draw({ snapToGrid: 0.1 })
.addClass('shapeCreation').attr({
'stroke-width': 0,
});
this._createPolyEvents();
break;
case 'polygon':
if (this._polyShapeSize && this._polyShapeSize < 3) {
if (!$('.drawAllert').length) {
showMessage("Min 3 points must be for polygon drawing.").addClass('drawAllert');
}
this._controller.switchCreateMode(true);
return;
}
this._drawInstance = this._frameContent.polygon().draw({ snapToGrid: 0.1 })
.addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / this._scale,
});
this._createPolyEvents();
break;
case 'polyline':
if (this._polyShapeSize && this._polyShapeSize < 2) {
if (!$('.drawAllert').length) {
showMessage("Min 2 points must be for polyline drawing.").addClass('drawAllert');
}
this._controller.switchCreateMode(true);
return;
}
this._drawInstance = this._frameContent.polyline().draw({ snapToGrid: 0.1 })
.addClass('shapeCreation').attr({
'stroke-width': STROKE_WIDTH / this._scale,
});
this._createPolyEvents();
break;
case "cuboid":
this._drawInstance = this._frameContent.polyline().draw({ snapToGrid: 0.1 }).addClass("shapeCreation").attr({
"stroke-width": STROKE_WIDTH / this._scale,
});
this._createPolyEvents();
break;
default:
throw Error(`Bad type found ${this._type}`);
}
}
_rescaleDrawPoints() {
const scale = this._scale;
$(".svg_draw_point").each(function () {
this.instance.radius(2.5 / scale).attr("stroke-width", 1 / scale);
});
}
_drawAim() {
if (!(this._aim)) {
this._aim = {
x: this._frameContent.line(0, this._aimCoord.y, this._frameContent.node.clientWidth, this._aimCoord.y)
.attr({
'stroke-width': STROKE_WIDTH / this._scale,
'stroke': 'red',
'z_order': Number.MAX_SAFE_INTEGER,
}).addClass('aim'),
y: this._frameContent.line(this._aimCoord.x, 0, this._aimCoord.x, this._frameContent.node.clientHeight)
.attr({
'stroke-width': STROKE_WIDTH / this._scale,
'stroke': 'red',
'z_order': Number.MAX_SAFE_INTEGER,
}).addClass('aim'),
};
}
}
_removeAim() {
if (this._aim) {
this._aim.x.remove();
this._aim.y.remove();
this._aim = null;
}
}
onShapeCreatorUpdate(model) {
if (model.createMode && !this._drawInstance) {
this._cancel = false;
this._type = model.defaultType;
this._mode = model.defaultMode;
if (!['polygon', 'polyline', 'points'].includes(this._type)) {
if (!model.usingShortkey) {
this._aimCoord = {
x: 0,
y: 0
};
}
this._drawAim();
}
this._createButton.text("Stop Creation");
document.oncontextmenu = () => false;
this._create();
}
else {
this._removeAim();
this._cancel = true;
this._createButton.text("Create Shape");
document.oncontextmenu = null;
if (this._drawInstance) {
// We save current result for poly shape if it's need
// drawInstance will be removed after save when drawdone handler calls switchCreateMode with force argument
if (model.saveCurrent && this._type != 'box') {
this._drawInstance.draw('done');
}
else {
this._drawInstance.draw('cancel');
this._drawInstance.remove();
this._drawInstance = null;
}
}
}
this._typeSelector.prop('disabled', model.createMode);
this._modeSelector.prop('disabled', model.createMode);
this._polyShapeSizeInput.prop('disabled', model.createMode);
}
onPlayerUpdate(player) {
if (!player.ready()) return;
if (this._scale != player.geometry.scale) {
this._scale = player.geometry.scale;
if (this._drawInstance) {
this._rescaleDrawPoints();
if (this._borderSticker) {
this._borderSticker.scale(this._scale);
}
if (this._aim) {
this._aim.x.attr('stroke-width', STROKE_WIDTH / this._scale);
this._aim.y.attr('stroke-width', STROKE_WIDTH / this._scale);
}
if (['box', 'polygon', 'polyline'].includes(this._type)) {
this._drawInstance.attr('stroke-width', STROKE_WIDTH / this._scale);
}
}
}
}
}

@ -1,246 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported FilterModel FilterController FilterView */
/* eslint no-unused-vars: ["error", { "caughtErrors": "none" }] */
/* global
defiant:false
*/
class FilterModel {
constructor(update) {
this._regex = /^[0-9]+|[-,?!()\s]+/g;
this._filter = '';
this._update = update;
this._labels = window.cvat.labelsInfo.labels();
this._attributes = window.cvat.labelsInfo.attributes();
}
_convertShape(shape) {
// We replace all special characters due to defiant.js can't work with them
function convertAttributes(attributes) {
const convertedAttributes = {};
for (const attrId in attributes) {
if (Object.prototype.hasOwnProperty.call(attributes, attrId)) {
const key = attributes[attrId].name
.toLowerCase().replace(this._regex, '_');
convertedAttributes[key] = String(attributes[attrId].value)
.toLowerCase();
}
}
return convertedAttributes;
}
const converted = {
id: shape.model.id,
serverid: shape.model.serverID,
label: shape.model.label,
type: shape.model.type.split('_')[1],
mode: shape.model.type.split('_')[0],
occluded: Boolean(shape.interpolation.position.occluded),
attr: convertAttributes.call(this, shape.interpolation.attributes),
lock: shape.model.lock,
};
if (shape.model.type.split('_')[1] === 'box') {
converted.width = shape.interpolation.position.xbr - shape.interpolation.position.xtl;
converted.height = shape.interpolation.position.ybr - shape.interpolation.position.ytl;
} else {
converted.width = shape.interpolation.position.width;
converted.height = shape.interpolation.position.height;
}
return converted;
}
_convertCollection(collection) {
const converted = {};
for (const labelId in this._labels) {
if (Object.prototype.hasOwnProperty.call(this._labels, labelId)) {
converted[this._labels[labelId].toLowerCase().replace(this._regex, '_')] = [];
}
}
for (const shape of collection) {
converted[this._labels[shape.model.label]
.toLowerCase().replace(this._regex, '_')]
.push(this._convertShape.call(this, shape));
}
return converted;
}
filter(interpolation) {
if (this._filter.length) {
// Get shape indexes
try {
const idxs = defiant.search(this._convertCollection(interpolation), `(${this._filter})/id`);
return interpolation.filter(x => idxs.indexOf(x.model.id) !== -1);
} catch (ignore) {
return [];
}
} else {
return interpolation;
}
}
updateFilter(value, silent) {
this._filter = value;
if (!silent) {
this._update();
}
}
get regex() {
return this._regex;
}
}
class FilterController {
constructor(filterModel) {
this._model = filterModel;
}
updateFilter(value, silent) {
if (!value.length) {
this._model.updateFilter('', silent);
return true;
}
try {
value = value.toLowerCase();
const labels = String.customSplit(value, '[|]').map(el => el.trim());
let result = '';
for (const label of labels) {
const labelName = label.match(/^[-,?!_0-9a-z()*\s"]+/)[0];
const labelFilters = label.substr(labelName.length).trim();
result += `${labelName.replace(this._model.regex, '_').replace(/"/g, '')}`;
const orExpressions = String.customSplit(labelFilters, 'or').map(el => el.trim());
const formattedOrExpressions = [];
for (const orExpression of orExpressions) {
const andExpressions = String.customSplit(orExpression, 'and').map(el => el.trim());
const formattedAndExpressions = [];
for (const andExpression of andExpressions) {
if (andExpression.includes('attr/')) {
const attrMatch = andExpression.match(/[\\[(]*attr\//);
const attrPrefix = attrMatch[0];
const attrExpression = andExpression.substr(attrMatch.index
+ attrPrefix.length);
const [attrName, attrValue] = String
.customSplit(attrExpression, '=|<=|>=|<|>|!=');
const condition = attrExpression
.slice(attrName.length, -attrValue.length).trim();
formattedAndExpressions
.push(`${attrPrefix}${attrName.trim().replace(this._model.regex, '_')
.replace(/"/g, '')}${condition}${attrValue.trim()}`);
} else {
formattedAndExpressions.push(andExpression);
}
}
if (formattedAndExpressions.length > 1) {
formattedOrExpressions.push(formattedAndExpressions.join(' and '));
} else {
formattedOrExpressions.push(formattedAndExpressions[0]);
}
}
if (formattedOrExpressions.length > 1) {
result += `${formattedOrExpressions.join(' or ')}`;
} else {
result += `${formattedOrExpressions[0]}`;
}
result += '|';
}
result = result.substr(0, result.length - 1);
result = result.split('|').map(x => `/d:data/${x}`).join('|');
document.evaluate(result, document, () => 'ns');
this._model.updateFilter(result, silent);
} catch (ignore) {
return false;
}
return true;
}
deactivate() {
this._model.active = false;
}
}
class FilterView {
constructor(filterController) {
this._controller = filterController;
this._filterString = $("#filterInputString");
this._resetFilterButton = $("#resetFilterButton");
this._filterString.on("keypress keydown keyup", (e) => e.stopPropagation());
this._filterSubmitList = $("#filterSubmitList");
let predefinedValues = null;
try {
predefinedValues = JSON.parse(localStorage.getItem("filterValues")) || [];
}
catch (ignore) {
predefinedValues = [];
}
let initSubmitList = () => {
this._filterSubmitList.empty();
for (let value of predefinedValues) {
value = value.replace(/'/g, '"');
this._filterSubmitList.append(`<option value='${value}'> ${value} </option>`);
}
}
initSubmitList();
this._filterString.on("change", (e) => {
let value = $.trim(e.target.value).replace(/'/g, '"');
if (this._controller.updateFilter(value, false)) {
this._filterString.css("color", "green");
if (!predefinedValues.includes(value)) {
predefinedValues = (predefinedValues.concat([value])).slice(-10);
localStorage.setItem("filterValues", JSON.stringify(predefinedValues));
initSubmitList();
}
}
else {
this._filterString.css("color", "red");
this._controller.updateFilter("", false);
}
});
let shortkeys = window.cvat.config.shortkeys;
this._filterString.attr("title", `
${shortkeys["prev_filter_frame"].view_value} - ${shortkeys["prev_filter_frame"].description}` + `\n` +
`${shortkeys["next_filter_frame"].view_value} - ${shortkeys["next_filter_frame"].description}`);
this._resetFilterButton.on("click", () => {
this._filterString.prop("value", "");
this._controller.updateFilter("", false);
});
let initialFilter = window.cvat.search.get("filter");
if (initialFilter) {
this._filterString.prop("value", initialFilter);
if (this._controller.updateFilter(initialFilter, true)) {
this._filterString.css("color", "green");
}
else {
this._filterString.prop("value", "");
this._filterString.css("color", "red");
}
}
}
}

@ -1,274 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported ShapeGrouperModel ShapeGrouperController ShapeGrouperView*/
/* global
Listener:false
Logger:false
Mousetrap:false
STROKE_WIDTH:false
*/
"use strict";
class ShapeGrouperModel extends Listener {
constructor(shapeCollection) {
super('onGrouperUpdate', () => this);
this._shapeCollection = shapeCollection;
this._active = false;
this._selectedObjects = [];
}
_unselectObjects() {
for (let obj of this._selectedObjects) {
obj.groupping = false;
}
this._selectedObjects = [];
}
apply() {
if (this._selectedObjects.length) {
this._shapeCollection.joinToGroup(this._selectedObjects);
}
}
reset() {
if (this._selectedObjects.length) {
this._shapeCollection.resetGroupFor(this._selectedObjects);
}
}
cancel() {
if (this._active) {
this._unselectObjects();
this._active = false;
if (window.cvat.mode === 'groupping') {
window.cvat.mode = null;
}
this.notify();
}
}
switch() {
if (this._active) {
this.apply();
this.cancel();
}
else if (window.cvat.mode === null) {
window.cvat.mode = 'groupping';
this._active = true;
this._shapeCollection.resetActive();
this.notify();
}
}
add(model) {
let idx = this._selectedObjects.indexOf(model);
if (idx === -1) {
this._selectedObjects.push(model);
model.groupping = true;
}
}
click() {
if (this._active) {
let active = this._shapeCollection.selectShape(this._shapeCollection.lastPosition, true);
if (active) {
let idx = this._selectedObjects.indexOf(active);
if (idx != -1) {
this._selectedObjects.splice(idx, 1);
active.groupping = false;
}
else {
this._selectedObjects.push(active);
active.groupping = true;
}
}
}
}
onCollectionUpdate() {
if (this._active) {
this._unselectObjects();
}
}
get active() {
return this._active;
}
}
class ShapeGrouperController {
constructor(grouperModel) {
this._model = grouperModel;
setupGrouperShortkeys.call(this);
function setupGrouperShortkeys() {
let shortkeys = window.cvat.config.shortkeys;
let switchGrouperHandler = Logger.shortkeyLogDecorator(function() {
this.switch();
}.bind(this));
let resetGroupHandler = Logger.shortkeyLogDecorator(function() {
if (this._model.active) {
this._model.reset();
this._model.cancel();
this._model.notify();
}
}.bind(this));
Mousetrap.bind(shortkeys["switch_group_mode"].value, switchGrouperHandler.bind(this), 'keydown');
Mousetrap.bind(shortkeys["reset_group"].value, resetGroupHandler.bind(this), 'keydown');
}
}
switch() {
this._model.switch();
}
add(model) {
this._model.add(model);
}
click() {
this._model.click();
}
}
class ShapeGrouperView {
constructor(grouperModel, grouperController) {
this._controller = grouperController;
this._frameContent = $('#frameContent');
this._groupShapesButton = $('#groupShapesButton');
this._rectSelector = null;
this._initPoint = null;
this._scale = 1;
this._groupShapesButton.on('click', () => {
this._controller.switch();
});
let shortkeys = window.cvat.config.shortkeys;
this._groupShapesButton.attr('title', `
${shortkeys['switch_group_mode'].view_value} - ${shortkeys['switch_group_mode'].description}` + `\n` +
`${shortkeys['reset_group'].view_value} - ${shortkeys['reset_group'].description}`);
grouperModel.subscribe(this);
}
_select() {
if (this._rectSelector) {
let rect1 = this._rectSelector[0].getBBox();
for (let shape of this._frameContent.find('.shape')) {
let rect2 = shape.getBBox();
if (rect1.x < rect2.x && rect1.y < rect2.y &&
rect1.x + rect1.width > rect2.x + rect2.width &&
rect1.y + rect1.height > rect2.y + rect2.height) {
this._controller.add(shape.cvatView.controller().model());
}
}
}
}
_reset() {
if (this._rectSelector) {
this._rectSelector.remove();
this._rectSelector = null;
}
if (this._initPoint) {
this._initPoint = null;
}
}
_enableEvents() {
this._frameContent.on('mousedown.grouper', (e) => {
this._initPoint = window.cvat.translate.point.clientToCanvas(this._frameContent[0], e.clientX, e.clientY);
});
this._frameContent.on('mousemove.grouper', (e) => {
let currentPoint = window.cvat.translate.point.clientToCanvas(this._frameContent[0], e.clientX, e.clientY);
if (this._initPoint) {
if (!this._rectSelector) {
this._rectSelector = $(document.createElementNS('http://www.w3.org/2000/svg', 'rect'));
this._rectSelector.appendTo(this._frameContent);
this._rectSelector.attr({
'stroke-width': (STROKE_WIDTH / 3) / this._scale,
'stroke': 'darkmagenta',
'fill': 'darkmagenta',
'fill-opacity': 0.5,
'stroke-dasharray': 5,
});
}
this._rectSelector.attr({
'x': Math.min(this._initPoint.x, currentPoint.x),
'y': Math.min(this._initPoint.y, currentPoint.y),
'width': Math.max(this._initPoint.x, currentPoint.x) - Math.min(this._initPoint.x, currentPoint.x),
'height': Math.max(this._initPoint.y, currentPoint.y) - Math.min(this._initPoint.y, currentPoint.y),
});
}
});
this._frameContent.on('mouseup.grouper', () => {
this._select();
this._reset();
});
this._frameContent.on('mouseleave.grouper', () => {
this._select();
this._reset();
});
this._frameContent.on('click.grouper', () => {
this._controller.click();
});
}
_disableEvents() {
this._frameContent.off('mousedown.grouper');
this._frameContent.off('mousemove.grouper');
this._frameContent.off('mouseup.grouper');
this._frameContent.off('mouseleave.grouper');
this._frameContent.off('click.grouper');
}
onGrouperUpdate(grouper) {
if (grouper.active) {
this._enableEvents();
this._groupShapesButton.text('Apply Group');
}
else {
this._reset();
this._disableEvents();
this._groupShapesButton.text('Group Shapes');
if (this._rectSelector) {
this._rectSelector.remove();
this._rectSelector = null;
}
}
}
onPlayerUpdate(player) {
if (this._scale != player.geometry.scale) {
this._scale = player.geometry.scale;
if (this._rectSelector) {
this._rectSelector.attr({
'stroke-width': STROKE_WIDTH / this._scale,
});
}
}
}
}

@ -1,285 +0,0 @@
/*
* Copyright (C) 2018-2020 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported ShapeMergerModel ShapeMergerController ShapeMergerView*/
/* global
Listener:false
Logger:false
Mousetrap:false
*/
"use strict";
class ShapeMergerModel extends Listener {
constructor(collectionModel) {
super('onShapeMergerUpdate', () => this);
this._collectionModel = collectionModel;
this._shapesForMerge = [];
this._mergeMode = false;
this._shapeType = null;
}
_pushForMerge(shape) {
if (!this._shapesForMerge.length) {
this._shapeType = shape.type.split('_')[1];
this._shapesForMerge.push(shape);
shape.merge = true;
}
else if (shape.type.split('_')[1] == this._shapeType) {
let idx = this._shapesForMerge.indexOf(shape);
if (idx != -1) {
this._shapesForMerge.splice(idx, 1);
shape.merge = false;
}
else {
this._shapesForMerge.push(shape);
shape.merge = true;
}
}
}
start() {
if (!window.cvat.mode) {
window.cvat.mode = 'merge';
this._mergeMode = true;
this._collectionModel.resetActive();
this.notify();
}
}
done() {
if (this._shapesForMerge.length > 1) {
let shapeDict = {};
for (let shape of this._shapesForMerge) {
let keyframes = shape.keyframes;
for (let keyframe of keyframes) {
keyframe = +keyframe;
let interpolation = shape.interpolate(keyframe);
if (keyframe in shapeDict && !interpolation.position.outside) {
shapeDict[keyframe] = {
shape: shape,
interpolation: interpolation
};
}
else if (!(keyframe in shapeDict)) {
shapeDict[keyframe] = {
shape: shape,
interpolation: interpolation
};
}
}
}
let sortedFrames = Object.keys(shapeDict).map(x => +x).sort((a,b) => a - b);
// remove all outside in begin of the track
while (shapeDict[sortedFrames[0]].interpolation.position.outside) {
delete shapeDict[sortedFrames[0]];
sortedFrames.splice(0, 1);
}
// if several shapes placed on single frame, do not merge
if (Object.keys(shapeDict).length <= 1) {
this.cancel();
return;
}
let label = shapeDict[sortedFrames[0]].shape.label;
let object = {
label_id: label,
group: 0,
frame: sortedFrames[0],
attributes: [],
shapes: [],
};
let lastMutableAttr = {};
let attributes = shapeDict[sortedFrames[0]].interpolation.attributes;
for (let attrId in attributes) {
if (!window.cvat.labelsInfo.attrInfo(attrId).mutable) {
object.attributes.push({
id: attrId,
value: attributes[attrId].value,
});
}
else {
lastMutableAttr[attrId] = null;
}
}
for (let frame of sortedFrames) {
// Not save continiously attributes. Only updates.
let shapeAttributes = [];
if (shapeDict[frame].shape.label === label) {
let attributes = shapeDict[frame].interpolation.attributes;
for (let attrId in attributes) {
if (window.cvat.labelsInfo.attrInfo(attrId).mutable) {
if (attributes[attrId].value != lastMutableAttr[attrId]) {
lastMutableAttr[attrId] = attributes[attrId].value;
shapeAttributes.push({
id: attrId,
value: attributes[attrId].value,
});
}
}
}
}
object.shapes.push(
Object.assign(shapeDict[frame].interpolation.position,
{
frame: frame,
attributes: shapeAttributes
}
)
);
// push an outsided box after each annotation box if next frame is empty
let nextFrame = frame + 1;
let stopFrame = window.cvat.player.frames.stop;
let type = shapeDict[frame].shape.type;
if (type.startsWith('annotation_') && !(nextFrame in shapeDict) && nextFrame <= stopFrame) {
let copy = Object.assign({}, object.shapes[object.shapes.length - 1]);
copy.outside = true;
copy.frame += 1;
copy.z_order = 0;
copy.attributes = [];
object.shapes.push(copy);
}
}
Logger.addEvent(Logger.EventType.mergeObjects, {
count: this._shapesForMerge.length,
});
this._collectionModel.add(object, `interpolation_${this._shapeType}`);
this._collectionModel.update();
let model = this._collectionModel.shapes.slice(-1)[0];
let shapes = this._shapesForMerge;
// Undo/redo code
window.cvat.addAction('Merge Objects', () => {
model.unsubscribe(this._collectionModel);
model.removed = true;
for (let shape of shapes) {
shape.removed = false;
shape.subscribe(this._collectionModel);
}
this._collectionModel.update();
}, () => {
for (let shape of shapes) {
shape.removed = true;
shape.unsubscribe(this._collectionModel);
}
model.subscribe(this._collectionModel);
model.removed = false;
}, window.cvat.player.frames.current);
// End of undo/redo code
this.cancel();
for (let shape of shapes) {
shape.removed = true;
shape.unsubscribe(this._collectionModel);
}
}
else {
this.cancel();
}
}
cancel() {
if (window.cvat.mode == 'merge') {
window.cvat.mode = null;
this._mergeMode = false;
for (let shape of this._shapesForMerge) {
shape.merge = false;
}
this._shapesForMerge = [];
this.notify();
}
}
click() {
if (this._mergeMode) {
const active = this._collectionModel.selectShape(
this._collectionModel.lastPosition,
true,
);
if (active) {
this._pushForMerge(active);
}
}
}
get mergeMode() {
return this._mergeMode;
}
}
class ShapeMergerController {
constructor(model) {
this._model = model;
setupMergeShortkeys.call(this);
function setupMergeShortkeys() {
let shortkeys = window.cvat.config.shortkeys;
let switchMergeHandler = Logger.shortkeyLogDecorator(function() {
this.switch();
}.bind(this));
Mousetrap.bind(shortkeys["switch_merge_mode"].value, switchMergeHandler.bind(this), 'keydown');
}
}
switch() {
if (this._model.mergeMode) {
this._model.done();
}
else {
this._model.start();
}
}
click() {
this._model.click();
}
}
class ShapeMergerView {
constructor(model, controller) {
this._controller = controller;
this._frameContent = $('#frameContent');
this._mergeButton = $('#mergeTracksButton');
this._mergeButton.on('click', () => controller.switch());
let shortkeys = window.cvat.config.shortkeys;
this._mergeButton.attr('title', `
${shortkeys['switch_merge_mode'].view_value} - ${shortkeys['switch_merge_mode'].description}`);
model.subscribe(this);
}
onShapeMergerUpdate(shapeMerger) {
if (shapeMerger.mergeMode) {
this._mergeButton.text('Apply Merge');
this._frameContent.on('click.merger', () => {
this._controller.click();
});
}
else {
this._frameContent.off('click.merger');
this._mergeButton.text('Merge Shapes');
}
}
}

@ -1,91 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported ShapeSplitter */
"use strict";
class ShapeSplitter {
_convertMutableAttributes(attributes) {
const result = [];
for (const attrId in attributes) {
if (Object.prototype.hasOwnProperty.call(attributes, attrId)) {
const attrInfo = window.cvat.labelsInfo.attrInfo(attrId);
if (attrInfo.mutable) {
result.push({
id: +attrId,
value: attributes[attrId].value,
});
}
}
}
return result;
}
split(track, frame) {
const keyFrames = track.keyframes.map(keyframe => +keyframe).sort((a, b) => a - b);
const exported = track.export();
if (frame > +keyFrames[0]) {
const curInterpolation = track.interpolate(frame);
const prevInterpolation = track.interpolate(frame - 1);
const curAttributes = this._convertMutableAttributes(curInterpolation.attributes);
const prevAttrributes = this._convertMutableAttributes(prevInterpolation.attributes);
const curPositionList = [];
const prevPositionList = [];
for (const shape of exported.shapes) {
if (shape.frame < frame - 1) {
prevPositionList.push(shape);
} else if (shape.frame > frame) {
curPositionList.push(shape);
}
}
if (track.type.split('_')[1] === 'box') {
const prevPos = prevInterpolation.position;
prevPositionList.push(Object.assign({}, {
frame: frame - 1,
attributes: prevAttrributes,
type: 'box',
}, prevPos));
const curPos = curInterpolation.position;
prevPositionList.push(Object.assign({}, {
frame,
attributes: curAttributes,
type: 'box',
}, curPos, { outside: true }));
curPositionList.push(Object.assign({}, {
frame,
attributes: curAttributes,
type: 'box',
}, curPos));
} else {
const curPos = curInterpolation.position;
curPositionList.push(Object.assign({
frame,
attributes: curAttributes,
type: track.type.split('_')[1],
}, curPos));
}
// don't clone id of splitted object
delete exported.id;
// don't clone group of splitted object
delete exported.group;
const prevExported = JSON.parse(JSON.stringify(exported));
const curExported = JSON.parse(JSON.stringify(exported));
prevExported.shapes = prevPositionList;
curExported.shapes = curPositionList;
curExported.frame = frame;
return [prevExported, curExported];
}
return [exported];
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,368 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported Config */
class Config {
constructor() {
this._username = '_default_';
this._shortkeys = {
switch_lock_property: {
value: 'l',
view_value: 'L',
description: 'switch lock property for active shape',
},
switch_all_lock_property: {
value: 't l',
view_value: 'T + L',
description: 'switch lock property for all shapes on current frame',
},
switch_occluded_property: {
value: 'q,/'.split(','),
view_value: 'Q or Num Division',
description: 'switch occluded property for active shape',
},
switch_draw_mode: {
value: 'n',
view_value: 'N',
description: 'start draw / stop draw',
},
switch_merge_mode: {
value: 'm',
view_value: 'M',
description: 'start merge / apply changes',
},
switch_group_mode: {
value: 'g',
view_value: 'G',
description: 'start group / apply changes',
},
reset_group: {
value: 'shift+g',
view_value: 'Shift + G',
description: 'reset group for selected shapes',
},
change_shape_label: {
value: 'ctrl+1,ctrl+2,ctrl+3,ctrl+4,ctrl+5,ctrl+6,ctrl+7,ctrl+8,ctrl+9'.split(','),
view_value: 'Ctrl + (1,2,3,4,5,6,7,8,9)',
description: 'change shape label for existing object',
},
change_default_label: {
value: 'shift+1,shift+2,shift+3,shift+4,shift+5,shift+6,shift+7,shift+8,shift+9'.split(','),
view_value: 'Shift + (1,2,3,4,5,6,7,8,9)',
description: 'change label default label',
},
change_shape_color: {
value: 'enter',
view_value: 'Enter',
description: 'change color for highlighted shape',
},
change_player_brightness: {
value: 'shift+b,alt+b'.split(','),
view_value: 'Shift+B / Alt+B',
description: 'increase/decrease brightness of an image',
},
change_player_contrast: {
value: 'shift+c,alt+c'.split(','),
view_value: 'Shift+C / Alt+C',
description: 'increase/decrease contrast of an image',
},
change_player_saturation: {
value: 'shift+s,alt+s'.split(','),
view_value: 'Shift+S / Alt+S',
description: 'increase/decrease saturation of an image',
},
switch_hide_mode: {
value: 'h',
view_value: 'H',
description: 'switch hide mode for active shape',
},
switch_active_keyframe: {
value: 'k',
view_value: 'K',
description: 'switch keyframe property for active shape',
},
switch_active_outside: {
value: 'o',
view_value: 'O',
description: 'switch outside property for active shape',
},
switch_all_hide_mode: {
value: 't h',
view_value: 'T + H',
description: 'switch hide mode for all shapes',
},
delete_shape: {
value: 'del,shift+del'.split(','),
view_value: 'Del, Shift + Del',
description: 'delete active shape (use shift for force deleting)',
},
focus_to_frame: {
value: '`,~'.split(','),
view_value: '~ / `',
description: 'focus to "go to frame" element',
},
next_frame: {
value: 'f',
view_value: 'F',
description: 'move to next player frame',
},
prev_frame: {
value: 'd',
view_value: 'D',
description: 'move to previous player frame',
},
forward_frame: {
value: 'v',
view_value: 'V',
description: 'move forward several frames',
},
backward_frame: {
value: 'c',
view_value: 'C',
description: 'move backward several frames',
},
next_key_frame: {
value: 'r',
view_value: 'R',
description: 'move to next key frame of highlighted track',
},
prev_key_frame: {
value: 'e',
view_value: 'E',
description: 'move to previous key frame of highlighted track',
},
prev_filter_frame: {
value: 'left',
view_value: 'Left Arrow',
description: 'move to prev frame which satisfies the filter',
},
next_filter_frame: {
value: 'right',
view_value: 'Right Arrow',
description: 'move to next frame which satisfies the filter',
},
play_pause: {
value: 'space',
view_value: 'Space',
description: 'switch play / pause of player',
},
open_help: {
value: 'f1',
view_value: 'F1',
description: 'open help window',
},
open_settings: {
value: 'f2',
view_value: 'F2',
description: 'open settings window ',
},
save_work: {
value: 'ctrl+s',
view_value: 'Ctrl + S',
description: 'save work on the server',
},
copy_shape: {
value: 'ctrl+c',
view_value: 'Ctrl + C',
description: 'copy active shape to buffer',
},
propagate_shape: {
value: 'ctrl+b',
view_value: 'Ctrl + B',
description: 'propagate active shape',
},
switch_paste: {
value: 'ctrl+v',
view_value: 'Ctrl + V',
description: 'switch paste mode',
},
switch_aam_mode: {
value: 'shift+enter',
view_value: 'Shift + Enter',
description: 'switch attribute annotation mode',
},
aam_next_attribute: {
value: 'down',
view_value: 'Down Arrow',
description: 'move to next attribute in attribute annotation mode',
},
aam_prev_attribute: {
value: 'up',
view_value: 'Up Arrow',
description: 'move to previous attribute in attribute annotation mode',
},
aam_next_shape: {
value: 'tab',
view_value: 'Tab',
description: 'move to next shape in attribute annotation mode',
},
aam_prev_shape: {
value: 'shift+tab',
view_value: 'Shift + Tab',
description: 'move to previous shape in attribute annotation mode',
},
select_i_attribute: {
value: '1,2,3,4,5,6,7,8,9,0'.split(','),
view_value: '1,2,3,4,5,6,7,8,9,0',
description: 'setup corresponding attribute value in attribute annotation mode',
},
change_grid_opacity: {
value: ['alt+g+=', 'alt+g+-'],
view_value: 'Alt + G + "+", Alt + G + "-"',
description: 'increase/decrease grid opacity',
},
change_grid_color: {
value: 'alt+g+enter',
view_value: 'Alt + G + Enter',
description: 'change grid color',
},
undo: {
value: 'ctrl+z',
view_value: 'Ctrl + Z',
description: 'undo',
},
redo: {
value: ['ctrl+shift+z', 'ctrl+y'],
view_value: 'Ctrl + Shift + Z / Ctrl + Y',
description: 'redo',
},
cancel_mode: {
value: 'esc',
view_value: 'Esc',
description: 'cancel active mode',
},
clockwise_rotation: {
value: 'ctrl+r',
view_value: 'Ctrl + R',
description: 'clockwise image rotation',
},
counter_clockwise_rotation: {
value: 'ctrl+shift+r',
view_value: 'Ctrl + Shift + R',
description: 'counter clockwise image rotation',
},
next_shape_type: {
value: ['alt+.'],
view_value: 'Alt + >',
description: 'switch next default shape type',
},
prev_shape_type: {
value: ['alt+,'],
view_value: 'Alt + <',
description: 'switch previous default shape type',
},
};
if (window.cvat && window.cvat.job && window.cvat.job.z_order) {
this._shortkeys.inc_z = {
value: '+,='.split(','),
view_value: '+',
description: 'increase z order for active shape',
};
this._shortkeys.dec_z = {
value: '-,_'.split(','),
view_value: '-',
description: 'decrease z order for active shape',
};
}
this._settings = {
player_step: {
value: '10',
description: 'step size for player when move on several frames forward/backward',
},
player_speed: {
value: '25 FPS',
description: 'playback speed of the player',
},
reset_zoom: {
value: 'false',
description: 'reset frame zoom when move between the frames',
},
enable_auto_save: {
value: 'false',
description: 'enable auto save ability',
},
auto_save_interval: {
value: '15',
description: 'auto save interval (min)',
},
};
this._defaultShortkeys = JSON.parse(JSON.stringify(this._shortkeys));
this._defaultSettings = JSON.parse(JSON.stringify(this._settings));
}
reset() {
this._shortkeys = JSON.parse(JSON.stringify(this._defaultShortkeys));
this._settings = JSON.parse(JSON.stringify(this._defaultSettings));
}
get shortkeys() {
return JSON.parse(JSON.stringify(this._shortkeys));
}
get settings() {
return JSON.parse(JSON.stringify(this._settings));
}
}

@ -1,56 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported convertPlainArrayToActual, convertToArray, intersection */
/* global
PolylineModel:false
*/
// Takes a 2d array of canvas points and transforms it to an array of point objects
function convertPlainArrayToActual(arr) {
let actual = [{ x: arr[0], y: arr[1] }];
actual = PolylineModel.convertNumberArrayToString(actual);
actual = window.cvat.translate.points.canvasToActual(actual);
actual = PolylineModel.convertStringToNumberArray(actual);
return actual;
}
// converts an array of point objects to a 2D array
function convertToArray(points) {
const arr = [];
points.forEach((point) => {
arr.push([point.x, point.y]);
});
return arr;
}
function line(p1, p2) {
const a = p1[1] - p2[1];
const b = p2[0] - p1[0];
const c = b * p1[1] + a * p1[0];
return [a, b, c];
}
function intersection(p1, p2, p3, p4) {
const L1 = line(p1, p2);
const L2 = line(p3, p4);
const D = L1[0] * L2[1] - L1[1] * L2[0];
const Dx = L1[2] * L2[1] - L1[1] * L2[2];
const Dy = L1[0] * L2[2] - L1[2] * L2[0];
let x = null;
let y = null;
if (D !== 0) {
x = Dx / D;
y = Dy / D;
return [x, y];
}
return null;
}

@ -1,599 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* ----------------------- Classes ----------------------- */
.playerButton {
fill: #09c;
stroke-width: 2px;
stroke: black;
margin-top: 1%;
margin-right: 15px;
float:left;
width: 40px;
height: 40px;
}
.playerButton:hover {
fill: rgba(0,0,0,0);
}
.playerButton:active {
fill: #017196;
}
.disabledPlayerButton {
fill:grey !important;
stroke: grey !important;
}
.graphicButton {
opacity: 0.6;
width: 20px;
height: 20px;
border: none;
outline: none;
margin-right: 10px;
}
.graphicButton:hover {
opacity: 1;
}
.graphicButton:active {
opacity: 0.2;
}
.lockButton {
background: url("icons/unlock.png") no-repeat;
background-size: 20px;
}
.locked {
background: url("icons/lock.png") no-repeat;
background-size: 20px;
}
.occludedButton {
background: url("icons/non-occluded.png") no-repeat;
background-size: 20px;
}
.occluded {
background: url("icons/occluded.png") no-repeat;
background-size: 20px;
}
.copyButton {
background: url("icons/copy.png") no-repeat;
background-size: 20px;
}
.propagateButton {
background: url("icons/propagate.png") no-repeat;
background-size: 20px;
}
.hiddenButton {
background: url("icons/non-hidden.png") no-repeat;
background-size: 20px;
}
.hiddenText {
background: url("icons/hidden-text.png") no-repeat;
background-size: 20px;
}
.hiddenShape {
background: url("icons/hidden-shape.png") no-repeat;
background-size: 20px;
}
.outsideButton {
background: url("icons/non-outside.png") no-repeat;
background-size: 20px;
}
.outside {
background: url("icons/outside.png") no-repeat;
background-size: 20px;
}
.keyFrameButton {
background: url("icons/non-keyframe.png") no-repeat;
background-size: 20px;
}
.keyFrame {
background: url("icons/keyframe.png") no-repeat;
background-size: 20px;
}
.prevKeyFrameButton {
background: url("icons/prevKeyFrame.png") no-repeat;
background-size: 20px;
}
.initKeyFrameButton {
background: url("icons/initKeyFrame.png") no-repeat;
background-size: 20px;
}
.nextKeyFrameButton {
background: url("icons/nextKeyFrame.png") no-repeat;
background-size: 20px;
}
.uiElement, .labelContentElement {
width: 93%;
border-radius: 2px;
margin: 1% auto;
padding: 1%;
text-align: left;
position: relative;
border: 3px solid rgba(0,0,0,0);
opacity: 0.65;
}
.labelContentElement:active {
opacity: 0.6;
}
.highlightedUI {
border: 3px solid black;
opacity: 1;
}
.uiAttr {
margin-left: 5%;
margin-bottom: 2px;
}
.selectAttr {
width: 60%;
}
.numberAttr {
width: 20%;
}
.textAttr {
width: 60%;
}
.shape {
position: relative;
shape-rendering: geometricprecision;
color-rendering: optimizeQuality;
}
.selectedShape {
cursor: move;
}
.polyline {
fill: none !important;
}
.points {
stroke: none !important;
fill: none !important;
}
.shapeSelect {
fill-opacity: 0;
stroke-opacity: 1;
visibility: hidden;
}
.shapeCreation {
stroke: #fff;
fill-opacity: 0;
}
.svg_select_points {
fill-opacity: 1;
cursor: pointer;
}
.svg_select_points_lt{
cursor: nw-resize;
}
.svg_select_points_rt{
cursor: ne-resize;
}
.svg_select_points_rb{
cursor: se-resize;
}
.svg_select_points_lb{
cursor: sw-resize;
}
.svg_select_points_t{
cursor: n-resize;
}
.svg_select_points_r{
cursor: e-resize;
}
.svg_select_points_b{
cursor: s-resize;
}
.svg_select_points_l{
cursor: w-resize;
}
.svg_select_points_ns{
cursor: ns-resize;
}
.svg_select_points_ew{
cursor: ew-resize;
}
.shape-creator-border-point {
opacity: 0.55;
}
.shape-creator-border-point:hover {
opacity: 1;
fill: red;
}
.shape-creator-border-point:active {
opacity: 0.55;
fill: red;
}
.shape-creator-border-point-direction {
fill: blueviolet;
}
.shapeText {
font-size: 0.12em;
fill: white;
stroke:black;
stroke-width: 0.05;
cursor: default;
}
.highlightedShape {
fill-opacity: 0.2;
stroke-opacity: 1;
}
.occludedShape {
stroke-dasharray: 5;
}
.mergeShape {
fill: blue;
fill-opacity: 0.5;
}
.mergePoints {
fill: blue;
fill-opacity: 0.5;
}
.mergeLine {
stroke: blue;
stroke-opacity: 1;
}
.groupShape {
fill: darkmagenta;
fill-opacity: 0.5;
}
.groupPoints {
fill: darkmagenta;
fill-opacity: 0.5;
}
.groupLine {
stroke: darkmagenta;
stroke-opacity: 1;
}
.lockedShape {
mix-blend-mode: screen;
}
.lockedText {
fill-opacity: 0.5;
}
.menuButton {
width: 90%;
height: 40px;
margin-top: 2px;
}
.settingsBox {
width: 15px;
height: 15px;
margin-left: 0px;
}
.custom-menu {
display: none;
z-index: 1000;
position: absolute;
overflow: hidden;
border: 1px solid #CCC;
white-space: nowrap;
font-family: sans-serif;
background: #FFF;
color: #333;
border-radius: 5px;
padding: 0;
}
.buttonBlockOfLabelUI {
margin: 5px;
padding: 5px;
border: 1px solid black;
border-radius: 2px;
}
/* Each of the items in the list */
.custom-menu li {
padding: 8px 12px;
cursor: pointer;
list-style-type: none;
transition: all .3s ease;
user-select: none;
}
.custom-menu li:hover {
background-color: #DEF;
}
.customizedTab {
border-radius: 5px 5px 0px 0px;
width: 100%;
width: 15%;
float: left;
margin: 0px 10px;
}
/* ----------------------- IDs ----------------------- */
#helpWindowContent, #settingsWindowContent, #colorUIWindowContent {
width: 700px;
height: 450px;
}
#annotationMenu
{
background-color: #B0C4DE;
padding: 8px 0;
height: auto;
max-width: 1000px;
z-index: 100;
float: left;
position: absolute;
border-radius: 5px;
border: 1px solid black;
padding: 5px;
}
#taskAnnotationCenterPanel {
width: 83%;
height: 100%;
float: left;
}
#taskAnnotationRightPanel {
height: 100%;
width: 15%;
float: left;
margin: 0px 10px;
}
#uiContent, #trackManagement, #aamMenu, #labelsContent {
border-bottom: 1px solid black;
border-right: 1px solid black;
border-left: 1px solid black;
border-radius: 5px;
box-shadow: 0 0 5px rgba(0,0,0,0.5);
background-color: #B0C4DE;
text-align: center;
}
#shapeLabelSelector, #shapeModeSelector, #shapeTypeSelector, polyShapeSizeWrapper {
width: 80%;
margin-left: auto;
margin-top: 2%;
}
#trackManagement, #aamMenu {
padding: 1em 0;
height: auto;
width: 100%;
margin: 10px 0px;
}
#aamMenu {
text-align: center;
}
#aamHelpContainer {
text-align: left;
margin-left: 5px;
overflow-y: auto;
}
#uiContent, #labelsContent {
height: 75.4%;
width: 100%;
border-radius: 0px 0px 5px 5px;
overflow-y: auto;
}
#player {
margin: auto;
width: 100%;
height: 100%;
}
#playerFrame {
width: 100%;
height: 80%;
border: 1px black solid;
border-radius: 5px;
background-color: #B0C4DE;
overflow: hidden;
position: relative;
}
#rotationWrapper {
width: 100%;
height: 100%;
transform-origin: center center;
}
#frameContent {
position: absolute;
z-index: 2;
outline: 10px solid black;
transform-origin: top left;
}
#frameText {
position: absolute;
z-index: 3;
transform-origin: center center;
pointer-events: none;
}
#frameGrid {
position: absolute;
z-index: 2;
transform-origin: top left;
pointer-events: none;
}
#frameBackground {
position: absolute;
z-index: -1;
background-repeat: no-repeat;
transform-origin: top left;
}
#canvasBackground {
position: absolute;
z-index: 0;
background-repeat: no-repeat;
transform-origin: top left;
}
#frameLoadingAnimation {
fill-opacity: 0;
stroke: #09c;
stroke-width: 3px;
stroke-dasharray: 50;
animation: dash 1s linear infinite;
}
#frameLoadingAnim {
z-index: 2;
position: absolute;
}
#annotationStatisticTable {
margin: 0px auto;
border: 1px solid dimgrey;
border-radius: 6px;
width: 100%;
text-align: center;
border-collapse: separate;
border-spacing: 3px;
}
#annotationStatisticTable th, #annotationStatisticTable td { padding: 3px; }
#annotationSettingsTable th, #annotationSettingsTable td {
padding: 20px;
}
#playerPanel {
width: 100%;
height: 5%;
}
#playerSettings {
width: 50%;
height: 100%;
}
#playerStep {
width: 2em;
}
#filterInputString {
margin-right: 10px;
width: 300px;
}
#playerProgress {
-webkit-appearance: none;
margin-top: 1.1%;
background-color: rgba(0,0,0,0);
}
#playerProgress::-webkit-slider-runnable-track {
height: 70%;
cursor: pointer;
background: #09c;
border-radius: 3px;
border: 1px solid #010101;
}
#playerProgress:focus {
outline-color: transparent;
outline-style: none;
}
#playerProgress::-webkit-slider-thumb {
border: 1px solid #000000;
height: 36px;
width: 16px;
border-radius: 3px;
background: #ffffff;
cursor: pointer;
-webkit-appearance: none;
margin-top: -10px;
}
#playerProgress::-moz-range-track {
height: 90%;
cursor: pointer;
background: #09c;
border-radius: 3px;
border: 1px solid #010101;
}
#playerProgress::-moz-range-thumb {
border: 1px solid #000000;
height: 36px;
width: 16px;
border-radius: 3px;
background: #ffffff;
cursor: pointer;
}
#playerProgress::-moz-focus-outer { border: 0; }
@keyframes dash {
0% {stroke-dashoffset: 1; stroke: #09c;}
50% {stroke-dashoffset: 100; stroke: #f44;}
100% {stroke-dashoffset: 300; stroke: #09c;}
}

@ -1,473 +0,0 @@
<!--
Copyright (C) 2018-2020 Intel Corporation
SPDX-License-Identifier: MIT
-->
{% extends 'engine/base.html' %}
{% load static %}
{% block head_css %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'engine/stylesheet.css' %}">
{% for css_file in css_3rdparty %}
<link rel="stylesheet" type="text/css" src="{% static css_file %}">
{% endfor %}
{% endblock %}
{% block head_js_3rdparty %}
{{ block.super }}
<script type="text/javascript" src="{% static 'engine/js/3rdparty/svg.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/svg.draw.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/svg.resize.min.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/svg.draggable.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/svg.select.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/defiant.min.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/jquery-3.3.1.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/js.cookie.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/jquery.fullscreen.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/cvat-core.min.js' %}"></script>
{% for js_file in js_3rdparty %}
<script type="text/javascript" src="{% static js_file %}"></script>
{% endfor %}
{% endblock %}
{% block head_js_cvat %}
{{ block.super }}
<script type="text/javascript" src="{% static 'engine/js/utils.js' %}"></script>
<script type="text/javascript">
window.UI_URL = "{{ ui_url }}";
</script>
<script type="text/javascript" src="{% static 'engine/js/logger.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/server.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/listener.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/history.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/borderSticker.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/coordinateTranslator.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/labelsInfo.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/annotationParser.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/attributeAnnotationMode.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/shapeFilter.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/shapeSplitter.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/polyshapeEditor.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/bootstrap.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/shapes.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/cuboidShape.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/shapeCollection.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/player.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/shapeMerger.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/shapeCreator.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/shapeBuffer.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/shapeGrouper.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/annotationSaver.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/annotationUI.js' %}"></script>
{% endblock %}
{% block content %}
<div id="taskAnnotationCenterPanel">
<div id="player">
<div id="playerFrame">
<div id="rotationWrapper">
<svg id="frameLoadingAnim" style="width: 100%; height: 100%;" class="hidden">
<circle r="30" cx="50%" cy="50%" id="frameLoadingAnimation"/>
</svg>
<svg id="frameContent"> </svg>
<svg id="frameText"> </svg>
<svg id="frameBackground"> </svg>
<canvas id="canvasBackground"> </canvas>
<svg id="frameGrid" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="playerGridPattern" width="100" height="100" patternUnits="userSpaceOnUse">
<!-- Max size value for grid is 1000. Path size should be >= such value in order to it displayed correct -->
<path id="playerGridPath" d="M 1000 0 L 0 0 0 1000" fill="none" stroke="white" opacity="0" stroke-width="2"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#playerGridPattern)" />
</svg>
<ul id="shapeContextMenu" class='custom-menu' oncontextmenu="return false;">
<li action="object_url"> Copy Object URL </li>
<li action="change_color"> Change Color </li>
<li action="remove_shape"> Remove Shape </li>
<li action="switch_occluded"> Switch Occluded </li>
<li action="switch_lock"> Switch Lock </li>
<li class="interpolationItem" action="split_track"> Split </li>
<li class="polygonItem" action="drag_polygon"> Enable Dragging </li>
<li class="cuboidItem" action="reset_perspective"> Reset Perspective </li>
<li class="cuboidItem" action="switch_orientation"> Switch Perspective Orientation </li>
</ul>
<ul id="playerContextMenu" class='custom-menu' oncontextmenu="return false;">
<li action="job_url"> Copy Job URL </li>
<li action="frame_url"> Copy Frame URL </li>
</ul>
<ul id="pointContextMenu" class='custom-menu' oncontextmenu="return false;">
<li action="remove_point"> Remove </li>
</ul>
</div>
</div>
<div id="playerPanel">
<svg id="firstButton" class="playerButton">
<polygon points="100,0 100,80 75,60 75,80 50,60, 50,80 0,40 50,0 50,20 75,0 75,20" transform="scale(0.4)"/>
</svg>
<svg id="multiplePrevButton" class="playerButton">
<polygon points="100,0 100,80 75,60 75,80 25,40 75,0 75,20" transform="scale(0.4)"/>
</svg>
<svg id="prevButton" class="playerButton">
<polygon points="90,20 90,60 50,60 50,80 10,40 50,0 50,20" transform="scale(0.4)"/>
</svg>
<svg id="playButton" class="playerButton">
<polygon points="35,0 35,80 85,40" transform="scale(0.4)"/>
</svg>
<svg id="pauseButton" class="playerButton hidden">
<rect x="25" y="0" width="20" height="80" transform="scale(0.4)" />
<rect x="65" y="0" width="20" height="80" transform="scale(0.4)" />
</svg>
<svg id="nextButton" class="playerButton">
<polygon points="10,20 10,60 50,60 50,80 90,40 50,0 50,20" transform="scale(0.4)"/>
</svg>
<svg id="multipleNextButton" class="playerButton">
<polygon points="1,1 1,80 25,60 25,80 75,40 25,0 25,20" transform="scale(0.4)"/>
</svg>
<svg id="lastButton" class="playerButton">
<polygon points="1,1 1,80 25,60 25,80 50,60 50,80 100,40 50,0 50,20 25,0 25,20" transform="scale(0.4)"/>
</svg>
<input type = "range" id = "playerProgress"/>
</div> <!-- END of PLAYER PANEL -->
<div style="margin-top: 20px">
<button id="menuButton" class="regular h2"> Open Menu </button>
<label class="regular h2" style="margin-left: 50px"> Filter: </label>
<datalist id="filterSubmitList" style="display: none;"> </datalist>
<input type="text" list="filterSubmitList" id="filterInputString" class="regular h2" placeholder='car[attr/model=/"mazda"'/>
<button id="resetFilterButton" class="regular h2"> Reset </button>
<button class="regular h2" id="undoButton" disabled> &#x27F2; </button>
<select size="2" class="regular" style="overflow: hidden; width: 15%; top: 0.5em; position: relative;" disabled>
<option id="lastUndoText" title="Undo Action" selected> None </option>
<option id="lastRedoText" title="Redo Action"> None </option>
</select>
<button class="regular h2" id="redoButton" disabled> &#10227; </button>
<label class="regular h2" style="margin-left: 15px;"> Propagation </label>
<input type ="number" id="propagateFramesInput" style="width: 3em" min="1" max="10000" value="50" class="regular h2"/>
<label class="regular h2" style="margin-left: 15px;"> Rotation </label>
<button class="regular h2" id="clockwiseRotation" title="Clockwise rotation"> &#10227; </button>
<button class="regular h2" id="counterClockwiseRotation" title="Counter clockwise rotation"> &#10226; </button>
<div style="float: right;">
<label class="regular h2"> Frame </label>
<input class="regular h2" style="width: 3.5em;" type="number" id="frameNumber">
</div>
<hr>
<table id="annotationSettingsTable" class="regular">
<tr>
<td style="width: auto;">
<div style="float: left;"> <label class="semiBold"> Fill Opacity: </label> </div>
<div style="float: left; margin-left: 1em;"> <input type="range" min="-1" max="1" step="0.2" value="0" id="fillOpacityRange"/> </div>
</td>
<td style="width: auto;">
<div style="float: left;"> <label class="semiBold"> Selected Fill Opacity: </label> </div>
<div style="float: left; margin-left: 1em;"> <input type="range" min="0" max="1" value="0.2" step="0.2" id="selectedFillOpacityRange"/> </div>
</td>
<td style="width: auto;">
<div style="float: left;"> <label class="semiBold"> Black Stroke: </label> </div>
<div style="float: left; margin-left: 1em;"> <input type="checkbox" id="blackStrokeCheckbox" class="settingsBox"/> </div>
</td>
<td style="width: auto;">
<div style="float: left;"> <label class="semiBold"> Color by: </label> </div>
<div style="float: left; margin-left: 10px;">
<label style="margin-right: 10px;"> Instance </label>
<input type="radio" name="colorByRadio" id="colorByInstanceRadio" checked class="settingsBox"/>
</div>
<div style="float: left; margin-left: 10px;">
<label style="margin-right: 10px;"> Group </label>
<input type="radio" name="colorByRadio" id="colorByGroupRadio" class="settingsBox"/>
</div>
<div style="float: left; margin-left: 10px;">
<label style="margin-right: 10px;"> Label </label>
<input type="radio" name="colorByRadio" id="colorByLabelRadio" class="settingsBox"/>
</div>
<div style="float: left; margin-left: 50px;" title="Press Ctrl to switch">
<label style="display: none;">
Common borders
<input type="checkbox" id="commonBordersCheckbox" class="settingsBox" style="margin-left: 10px; display: none;"/>
</label>
</div>
</td>
</tr>
</table>
</div>
</div> <!-- END of PLAYER -->
<div id="helpWindow" class="modal hidden">
<div id="helpWindowContent" class="modal-content">
<div style="width: 100%; height: 90%; overflow-y: auto;">
<div class="selectable">
<label class="h1 semiBold"> Shortkeys: </label> <br>
<table class="regular" id="shortkeyHelpTable"> </table>
<label class="h1 semiBold"> <br> Hints: <br> </label>
<label class="regular"> - Hold MOUSEWHEEL in order to move frame (during drawing for example). </label> <br>
<label class="regular"> - Hold CTRL key when track highlighted and fix it. </label> <br>
<label class="regular"> - Hold CTRL key when paste shape from buffer for multiple pasting. </label> <br>
<label class="h1 semiBold"> <br> Filter Help: <br> </label>
<label class="regular">
Filter Format: label[property operator "value"] (USE LOWER CASE ONLY) <br>
Label is a type of bounding box (car, person, etc). Use "*" for any label. <br>
Property is a limited set of values: id, type, lock, occluded, attr. <br>
Operator is "=", "!=" for any properties and ">", "<", ">=", "<=" for numeric properties. <br>
For complex conditions please use 'or', 'and' (for properties) "|" (for labels) operators. <br> <br>
Examples: <br>
*[mode="annotation"] - only annotation objects <br>
*[type="polygon"] - only polygon objects <br>
car[occluded="true"] - only occluded cars <br>
*[lock!="true"] - only unlocked tracks <br>
person[attr/age>="25" and attr/age<="35"] - persons with age (number) between [25,40] years <br>
car[attr/parked="true"] - only parked cars <br>
person[attr/race="asian"] | car[attr/model="bmw"] - asians and BMW cars <br>
face[attr/glass="sunglasses" or attr/glass="no"] - faces with sunglasses or without glass <br>
*[attr/*="__undefined__"] - any tracks with any unlabeled attributes <br>
*[width<300 or height<300] - shapes with height or width less than 300px <br>
person[width>300 and height<200] - person shapes with width > 300px and height < 200px <br>
</label>
</div>
</div>
<center> <button id="closeHelpButton" class="regular h1" style="margin-top: 15px;"> Close </button> </center>
</div>
</div>
<div id="settingsWindow" class="modal hidden">
<div id="settingsWindowContent" class="modal-content">
<div id="playerSettings" style="width: 48%; height: 90%; float: left;">
<center> <label class="semiBold h1"> Player Settings </label> </center>
<table style="border-collapse: separate; border-spacing: 10px; overflow-y: auto;" class="regular">
<tr>
<td> <label> Player Step </label> </td>
<td> <input type="number" min="1" max="100" value="10" id="playerStep" class="regular h2"/> </td>
</tr>
<tr>
<td> <label> Player Speed </label> </td>
<td>
<select id="speedSelect" class="regular h3">
<option value="1"> 1 FPS </option>
<option value="2"> 5 FPS </option>
<option value="3"> 12 FPS </option>
<option value="4" selected> 25 FPS </option>
<option value="5"> 50 FPS </option>
<option value="6"> 100 FPS </option>
</select>
</td>
</tr>
<tr>
<td> <label> Reset Zoom </label> </td>
<td> <input type="checkbox" id="resetZoomBox" class="settingsBox"/> </td>
</tr>
<tr>
<td> <label> Grid Size </label> </td>
<td> <input type="number" min="5" max="1000" value="100" id="playerGridSizeInput" class="regular h2"/> </td>
</tr>
<tr>
<td> <label> Grid Opacity </label> </td>
<td> <input type="range" min="0" max="5" value="0" id="playerGridOpacityInput" class="regular h2"/> </td>
</tr>
<tr>
<td> <label> Grid Stroke </label> </td>
<td>
<select id="playerGridStrokeInput" class="regular h2">
<option value="black"> Black </option>
<option value="red"> Red </option>
<option value="green"> Green </option>
<option value="blue"> Blue </option>
<option value="white" selected> White </option>
</select>
</td>
</tr>
<tr>
<td> <label for="playerBrightnessRange"> Brightness: </label> </td>
<td> <input type="range" min="50" max="200" value="100" id="playerBrightnessRange"> </td>
</tr>
<tr>
<td> <label for="playerContrastRange"> Contrast: </label> </td>
<td> <input type="range" min="50" max="200" value="100" id="playerContrastRange"> </td>
</tr>
<tr>
<td> <label for="playerSaturationRange"> Saturation: </label> </td>
<td> <input type="range" min="0" max="300" value="100" id="playerSaturationRange"> </td>
</tr>
<tr>
<td colspan="2">
<button id="resetPlayerFilterButton" class="regular h1"> Reset Color Settings </button>
</td>
</tr>
</table>
</div>
<div id="otherSettigns" style="width: 48%; height: 90%; float: left;">
<center> <label class="semiBold h1"> Other Settings </label> </center>
<table style="border-collapse: separate; border-spacing: 10px; overflow-y: auto;" class="regular">
<tr>
<td> <label> Show All Interpolation Tracks </label> </td>
<td> <input type = "checkbox" id="showAllInterBox" class="settingsBox"/> </td>
</tr>
<tr >
<td> <label> AAM Zoom Margin </label> </td>
<td> <input type="range" min="0" max="1000" value="100" id="aamZoomMargin"/> </td>
</tr>
<tr>
<td> <label> Enable Auto Saving </label> </td>
<td> <input type = "checkbox" id="autoSaveBox" class="settingsBox"/> </td>
</tr>
<tr>
<td> <label> Auto Saving Interval (Min) </label> </td>
<td> <input type = "number" id="autoSaveTime" style="width: 3em" min="5" max="60" value="15" class="regular h2"/> </td>
</tr>
<tr>
<td> <label> Rotate All Images </label> </td>
<td> <input type = "checkbox" id="rotateAllImages" class="settingsBox"/> </td>
</tr>
<tr>
<td> <label> Cuboid Projection Lines: </label> </td>
<td><input type="checkbox" id="projectionLineEnable" class="settingsBox"/></td>
</tr>
</table>
</div>
<center>
<button id="closeSettignsButton" class="regular h1" style="margin-top: 15px;"> Close </button>
</center>
</div>
</div>
<div id="annotationMenu" class="hidden regular">
<center style="float:left; width: 28%; height: 100%;" id="engineMenuButtons">
<select id="downloadAnnotationButton" class="menuButton semiBold h2" style="text-align-last: center;">
<option selected disabled> Dump Annotation </option>
</select>
<select id="uploadAnnotationButton" class="menuButton semiBold h2" style="text-align-last: center;">
<option selected disabled> Upload Annotation </option>
</select>
<button id="openTaskButton" class="menuButton semiBold h2"> Open Task </button>
<button id="removeAnnotationButton" class="menuButton semiBold h2"> Remove Annotation </button>
<button id="settingsButton" class="menuButton semiBold h2"> Settings </button>
<button id="fullScreenButton" class="menuButton semiBold h2"> Fullscreen Player </button>
<button id="switchAAMButton" class="menuButton semiBold h2"> Switch AAM </button>
<button id="helpButton" class="menuButton semiBold h2"> Help </button>
<button id="saveButton" class="menuButton semiBold h2"> Save Work </button>
<input type="file" id="annotationFileSelector" style="display: none"/>
</center>
<div style="float:left; width: 70%; height: 100%; text-align: left;" class="selectable">
<center>
<label id="statTaskName" class="semiBold h2"> </label> <br>
<center>
<select id="statTaskStatus" class="regular h2" style="outline: none; border-radius: 10px; background:#B0C4DE; border: 1px solid black;">
{% for status in status_list %}
<option value="{{status}}"> {{status}} </option>
{% endfor %}
</select>
</center>
</center>
<center>
<table style="width: 100%">
<tr>
<td style="width: 30%;">
<label class="regular h2"> Frames: </label>
<label id="statFrames" class="regular h2"> </label>
</td>
<td style="width: 30%;">
<label class="regular h2"> Overlap: </label>
<label id="statOverlap" class="regular h2"> </label>
</td>
<td style="width: 30%;">
<label class="regular h2"> Z-Order: </label>
<label id="statZOrder" class="regular h2"> </label>
</td>
</tr>
</table>
</center>
<center> <label class="semiBold h2"> Segment Statistic </label> </center>
<div style="text-align: center; max-height: 250px; overflow: auto; display: block;">
<table id="annotationStatisticTable">
<tr class="semiBold">
<td> Label </td>
<td colspan="2"> Boxes </td>
<td colspan="2"> Polygons </td>
<td colspan="2"> Polylines </td>
<td colspan="2"> Points </td>
<td colspan="2"> Cuboids </td>
<td> Manually </td>
<td> Interpolated </td>
<td> Total </td>
</tr>
<tr>
<td> </td> <!-- Empty -->
<td> S </td>
<td> T </td>
<td> S </td>
<td> T </td>
<td> S </td>
<td> T </td>
<td> S </td>
<td> T </td>
<td> S </td>
<td> T </td>
<td> </td> <!-- Empty -->
<td> </td> <!-- Empty -->
</tr>
</table>
</div>
</div>
</div>
</div>
<div class="tab customizedTab">
<button class="h2 regular activeTabButton" id="sidePanelObjectsButton" style="width: 50%">Objects</button>
<button class="h2 regular" id="sidePanelLabelsButton" style="width: 50%">Labels</button>
</div>
<div id="taskAnnotationRightPanel">
<div id="uiContent"> </div>
<div id="labelsContent" class="hidden"> </div>
<div id="trackManagement">
<button id="createShapeButton" class="regular h2" style="width: 80%;"> Create Shape </button>
<button id="mergeTracksButton" class="regular h2" style="width: 80%; margin-top: 5px;"> Merge Shapes </button>
<button id="groupShapesButton" class="regular h2" style="width: 80%; margin-top: 5px;"> Group Shapes </button>
<select id="shapeLabelSelector" class="regular h2"> </select>
<select id="shapeModeSelector" class="regular h2">
<option value="annotation" class="regular"> Annotation </option>
<option value="interpolation" class="regular"> Interpolation </option>
</select>
<select id="shapeTypeSelector" class="regular h2">
<option value="box" class="regular" selected> Box </option>
<option value="box_by_4_points" class="regular"> Box by 4 points </option>
<option value="polygon" class="regular"> Polygon </option>
<option value="polyline" class="regular"> Polyline </option>
<option value="points" class="regular"> Points </option>
<option value="cuboid" class="regular"> Cuboid </option>
</select>
<div id="polyShapeSizeWrapper">
<label for="polyShapeSize" class="regular h2"> Poly Shape Size: </label>
<input id="polyShapeSize" type="text" value="" class="regular h2" style="width: 30%; margin-top: 1%;" placeholder="0-100"/>
</div>
</div>
<div id="aamMenu" class="hidden">
<label class="regular h1"> AAM Mode </label>
<label id="aamCounter" class="regular h1"> </label> <br>
<label id="aamTitle" class="regular h2"> </label>
<div id="aamHelpContainer" class="regular">
</div>
</div>
</div>
{% endblock %}

@ -1,90 +0,0 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
<!DOCTYPE html>
{% load static compress %}
<html>
<head>
<title>
{% block head_title %}
Computer Vision Annotation Tool (CVAT)
{% endblock %}
</title>
{% compress css %}
{% block head_css %}
<link rel="stylesheet" type="text/css" href="{% static 'engine/base.css' %}">
{% endblock %}
{% endcompress %}
{% compress js file platformchecker %}
<script type="text/javascript" src="{% static 'engine/js/3rdparty/platform.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/checkPlatform.js' %}"></script>
{% endcompress %}
{% compress js file thirdparty %}
{% block head_js_3rdparty %}
<script type="text/javascript" src="{% static 'engine/js/3rdparty/jquery-3.3.1.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/js.cookie.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/3rdparty/mousetrap.js' %}"></script>
{% endblock %}
{% endcompress %}
{% compress js file cvat %}
{% block head_js_cvat %}
<script type="text/javascript" src="{% static 'engine/js/base.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/userConfig.js' %}"></script>
{% endblock %}
{% endcompress %}
</head>
<body>
{% block header %}
{% endblock %}
{% block content %}
{% endblock %}
{% block footer %}
{% endblock %}
<svg class="overlay" id="loadingOverlay" style="display: block;">
<text class="semiBold" font-size="50" fill="#09c" x="44%" y="46%"> LOADING </text>
</svg>
</body>
<template id="confirmTemplate">
<div class="modal">
<div class="modal-content" style="width: 400px; height: auto; max-height: 600px; overflow: auto;">
<label class="regular templateMessage selectable"> </label>
<center>
<button class="regular h2 templateDisagreeButton" style="margin-top: 20px"> Cancel </button>
<button class="regular h2 templateAgreeButton" style="margin-top: 20px"> Ok </button>
</center>
</div>
</div>
</template>
<template id="messageTemplate">
<div class="modal">
<div class="modal-content" style="width: 400px; height: auto; max-height: 600px; overflow: auto;">
<label class="regular templateMessage selectable" style="word-break: break-word;"> </label>
<center>
<button class="regular h2 templateOKButton" style="margin-top: 20px"> Ok </button>
</center>
</div>
</div>
</template>
<template id="overlayTemplate">
<div class="modal" style="display: block !important;">
<div class="modal-content" style="width: 400px; height: auto; max-height: 600px; overflow: auto;">
<label class="regular templateMessage selectable"> </label>
</div>
</div>
</template>
</html>

@ -9,7 +9,10 @@ from rest_framework import routers
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from django.views.generic import RedirectView
from django.conf import settings
from cvat.apps.restrictions.views import RestrictionsViewSet
from cvat.apps.authentication.decorators import login_required
schema_view = get_schema_view(
openapi.Info(
@ -24,6 +27,22 @@ schema_view = get_schema_view(
permission_classes=(permissions.IsAuthenticated,),
)
# drf-yasg component doesn't handle correctly URL_FORMAT_OVERRIDE and
# send requests with ?format=openapi suffix instead of ?scheme=openapi.
# We map the required paramater explicitly and add it into query arguments
# on the server side.
def wrap_swagger(view):
@login_required
def _map_format_to_schema(request, scheme=None):
if 'format' in request.GET:
request.GET = request.GET.copy()
format_alias = settings.REST_FRAMEWORK['URL_FORMAT_OVERRIDE']
request.GET[format_alias] = request.GET['format']
return view(request, format=scheme)
return _map_format_to_schema
router = routers.DefaultRouter(trailing_slash=False)
router.register('projects', views.ProjectViewSet)
router.register('tasks', views.TaskViewSet)
@ -34,18 +53,18 @@ router.register('restrictions', RestrictionsViewSet, basename='restrictions')
urlpatterns = [
# Entry point for a client
path('', views.dispatch_request),
path('dashboard/', views.dispatch_request),
path('', RedirectView.as_view(url=settings.UI_URL, permanent=True,
query_string=True)),
# documentation for API
path('api/swagger<str:scheme>', views.wrap_swagger(
path('api/swagger<str:scheme>', wrap_swagger(
schema_view.without_ui(cache_timeout=0)), name='schema-json'),
path('api/swagger/', views.wrap_swagger(
path('api/swagger/', wrap_swagger(
schema_view.with_ui('swagger', cache_timeout=0)), name='schema-swagger-ui'),
path('api/docs/', views.wrap_swagger(
path('api/docs/', wrap_swagger(
schema_view.with_ui('redoc', cache_timeout=0)), name='schema-redoc'),
# entry point for API
path('api/v1/auth/', include('cvat.apps.authentication.api_urls')),
path('api/v1/auth/', include('cvat.apps.authentication.urls')),
path('api/v1/', include((router.urls, 'cvat'), namespace='v1'))
]

@ -13,11 +13,9 @@ import django_rq
from django.conf import settings
from django.contrib.auth.models import User
from django.db import IntegrityError
from django.http import HttpResponse, HttpResponseNotFound
from django.shortcuts import render
from django.http import HttpResponse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.generic import RedirectView
from django_filters import rest_framework as filters
from django_filters.rest_framework import DjangoFilterBackend
from drf_yasg import openapi
@ -34,7 +32,6 @@ from sendfile import sendfile
import cvat.apps.dataset_manager as dm
import cvat.apps.dataset_manager.views # pylint: disable=unused-import
from cvat.apps.authentication import auth
from cvat.apps.authentication.decorators import login_required
from cvat.apps.dataset_manager.serializers import DatasetFormatsSerializer
from cvat.apps.engine.frame_provider import FrameProvider
from cvat.apps.engine.models import Job, StatusChoice, Task
@ -44,50 +41,12 @@ from cvat.apps.engine.serializers import (
FileInfoSerializer, JobSerializer, LabeledDataSerializer,
LogEventSerializer, ProjectSerializer, RqStatusSerializer,
TaskSerializer, UserSerializer)
from cvat.settings.base import CSS_3RDPARTY, JS_3RDPARTY
from cvat.apps.engine.utils import av_scan_paths
from . import models, task
from .log import clogger, slogger
# drf-yasg component doesn't handle correctly URL_FORMAT_OVERRIDE and
# send requests with ?format=openapi suffix instead of ?scheme=openapi.
# We map the required paramater explicitly and add it into query arguments
# on the server side.
def wrap_swagger(view):
@login_required
def _map_format_to_schema(request, scheme=None):
if 'format' in request.GET:
request.GET = request.GET.copy()
format_alias = settings.REST_FRAMEWORK['URL_FORMAT_OVERRIDE']
request.GET[format_alias] = request.GET['format']
return view(request, format=scheme)
return _map_format_to_schema
# Server REST API
@login_required
def dispatch_request(request):
"""An entry point to dispatch legacy requests"""
if 'dashboard' in request.path or (request.path == '/' and 'id' not in request.GET):
return RedirectView.as_view(
url=settings.UI_URL,
permanent=True,
query_string=True
)(request)
elif request.method == 'GET' and 'id' in request.GET and request.path == '/':
return render(request, 'engine/annotation.html', {
'css_3rdparty': CSS_3RDPARTY.get('engine', []),
'js_3rdparty': JS_3RDPARTY.get('engine', []),
'status_list': [str(i) for i in StatusChoice],
'ui_url': settings.UI_URL
})
else:
return HttpResponseNotFound()
class ServerViewSet(viewsets.ViewSet):
serializer_class = None

@ -1,3 +0,0 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT

@ -1,3 +0,0 @@
# Copyright (C) 2020 Intel Corporation
#
# SPDX-License-Identifier: MIT

@ -82,10 +82,6 @@ try:
except Exception:
pass
# Application definition
JS_3RDPARTY = {}
CSS_3RDPARTY = {}
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
@ -209,7 +205,7 @@ WSGI_APPLICATION = 'cvat.wsgi.application'
# Django Auth
DJANGO_AUTH_TYPE = 'BASIC'
DJANGO_AUTH_DEFAULT_GROUPS = []
LOGIN_URL = 'login'
LOGIN_URL = 'rest_login'
LOGIN_REDIRECT_URL = '/'
AUTH_LOGIN_NOTE = '<p>Have not registered yet? <a href="/auth/register">Register here</a>.</p>'

@ -1,131 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
module.exports = {
'env': {
'browser': true,
'es6': true,
'jquery': true,
'qunit': true,
},
"extends": "eslint:recommended",
'rules': {
'indent': [
'warn',
4
],
'linebreak-style': [
'error',
'unix'
],
'semi': [
'warn'
],
'no-extra-semi': [
'warn'
],
'no-console': [
'warn'
],
},
'globals': {
// from attributeAnnotationMode.js
'AAMController': true,
'AAMModel': true,
'AAMView': true,
'AAMUndefinedKeyword': true,
// from annotationParser.js
'AnnotationParser': true,
// from annotationUI.js
'callAnnotationUI': true,
'blurAllElements': true,
'drawBoxSize': true,
'copyToClipboard': true,
// from base.js
'showMessage': true,
'showOverlay': true,
'confirm': true,
'dumpAnnotationRequest': true,
'createExportContainer': true,
'ExportType': true,
'getExportTargetContainer': true,
// from idGenerator.js
'IncrementIdGenerator': true,
'ConstIdGenerator': true,
// from shapeCollection.js
'ShapeCollectionModel': true,
'ShapeCollectionController': true,
'ShapeCollectionView': true,
// from shapeCreator.js
'ShapeCreatorModel': true,
'ShapeCreatorController': true,
'ShapeCreatorView': true,
// from shapeGrouper.js
'ShapeGrouperModel': true,
'ShapeGrouperController': true,
'ShapeGrouperView': true,
// from labelsInfo.js
'LabelsInfo': true,
// from listener.js
'Listener': true,
// from logger.js
'Logger': true,
// from shapeMerger.js
'ShapeMergerModel': true,
'ShapeMergerController': true,
'ShapeMergerView': true,
// from shapes.js
'PolyShapeModel': true,
'PolyShapeView': true,
'buildShapeModel': true,
'buildShapeController': true,
'buildShapeView': true,
'STROKE_WIDTH': true,
'POINT_RADIUS': true,
'AREA_TRESHOLD': true,
'SELECT_POINT_STROKE_WIDTH': true,
// from mousetrap.js
'Mousetrap': true,
// from platform.js
'platform': true,
// from player.js
'PlayerController': true,
'PlayerModel': true,
'PlayerView': true,
// from server.js
'serverRequest': true,
'saveJobRequest': true,
// from shapeBuffer.js
'ShapeBufferController': true,
'ShapeBufferModel': true,
'ShapeBufferView': true,
// from trackFilter.js
'FilterModel': true,
'FilterController': true,
'FilterView': true,
// from shapeSplitter.js
'ShapeSplitter': true,
// from userConfig.js
'Config': true,
// from cookies.js
'Cookies': true,
// from dashboard django template
'maxUploadCount': true,
'maxUploadSize': true,
// from SVG.js
'SVG': true,
// from history.js
'HistoryModel': true,
'HistoryController': true,
'HistoryView': true,
// from polyshapeEditor.js
'PolyshapeEditorModel': true,
'PolyshapeEditorController': true,
'PolyshapeEditorView': true,
// from coordinateTranslator
'CoordinateTranslator': true,
},
};

@ -1,68 +0,0 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
const path = require('path');
module.exports = function(config) {
config.set({
basePath: path.join(process.env.HOME, 'cvat/apps/'),
frameworks: ['qunit'],
files: [
'engine/static/engine/js/labelsInfo.js',
'engine/static/engine/js/annotationParser.js',
'engine/static/engine/js/listener.js',
'engine/static/engine/js/player.js',
'engine/static/engine/js/shapes.js',
'engine/static/engine/js/qunitTests.js',
],
port: 9876,
colors: true,
autoWatch: false,
browsers: ['ChromeNoSandbox'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
// Concurrency level
// how many browser should be started simultanous
concurrency: Infinity,
preprocessors: {
'**/!(qunitTests).js': ['coverage']
},
reporters: ['progress', 'junit', 'coverage', 'coveralls'],
coverageReporter: {
dir: path.join(process.env.HOME, 'media/coverage'),
reporters: [
{ type: 'html', subdir: '.' }, { type: 'lcov', subdir: '.' }
],
instrumenterOptions: {
istanbul: { noCompact: true }
}
},
junitReporter: {
outputDir: path.join(process.env.HOME, 'media/junit'),
outputFile: undefined,
useBrowserName: true,
nameFormatter: undefined,
classNameFormatter: undefined,
properties: {},
xmlVersion: null
},
customLaunchers: {
ChromeNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox']
}
},
logLevel: config.LOG_DEBUG
});
}

@ -1,15 +0,0 @@
#!/bin/bash
#
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
#
eslint -c ~/tests/eslintrc.conf.js -f ~/tests/node_modules/eslint-detailed-reporter/lib/detailed-multi.js \
~/cvat/apps/**/static/**/js/*.js -o ./report.html
ret_code=$?
mkdir -p ~/media/eslint
mv -t ~/media/eslint main.js styles.css report.html
exit $ret_code
Loading…
Cancel
Save