Moved development on public github.

- changed Content-type for save_job request to application/json, object…
- Adopted public PR - PASCAL VOC converter
- Added convert_to_mask.py script
- Fixed player continue plaing at the end of video, lock for editable object broke the client
- Same colors for shapes and menus, ability to change color for label or group
- Undo redo
- Added license header for all files
- Added .gitattributes file (critical for bash scripts)
- Fixed "Don't delete tasks if a user is deleted"
- More strict check for 'checkbox' and 'number' values
- Added convert_to_coco.py script
- Job statistic were extended. Blowradius was removed
- More strict labels verification
- Drag polygons by requirement, norm stroke opacity, easy box dragging
- Drawing with mouse outside the image area
- Fixed 7z support
- Boundig box size in drawer, switcheable grid
- Split tracks feature
- Fix flip parsing
- Update color set & color by label feature
- Add point for polygons feature
- Added context menu
- Polyshapes
main
Nikita Manovich 8 years ago
parent e7fba70c8e
commit a50b4cc144

29
.gitattributes vendored

@ -0,0 +1,29 @@
* text=auto whitespace=trailing-space,space-before-tab,-indent-with-non-tab,tab-in-indent,tabwidth=4
.git* text export-ignore
*.txt text
*.htm text
*.html text
*.js text
*.py text
*.css text
*.md text
*.yml text
Dockerfile text
LICENSE text
*.conf text
*.mimetypes text
*.sh text eol=lf
*.avi binary
*.bmp binary
*.exr binary
*.ico binary
*.jpeg binary
*.jpg binary
*.png binary
*.gif binary
*.ttf binary
*.pdf binary

@ -2,52 +2,3 @@
When contributing to this repository, please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change.
## Development environment
Next steps should work on clear Ubuntu 18.04.
- Install necessary dependencies:
```sh
$ sudo apt-get install -y curl redis-server python3-dev python3-pip python3-venv libldap2-dev libsasl2-dev
```
- Install [Visual Studio Code](https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions) for development
- Install CVAT on your local host:
```sh
$ git clone https://github.com/opencv/cvat
$ cd cvat && mkdir logs keys
$ python3 -m venv .env
$ . .env/bin/activate
$ pip install -U pip wheel
$ pip install -r cvat/requirements/development.txt
$ python manage.py migrate
$ python manage.py collectstatic
```
- Create a super user for CVAT:
```sh
$ python manage.py createsuperuser
Username (leave blank to use 'django'): ***
Email address: ***
Password: ***
Password (again): ***
```
- Run Visual Studio Code from the virtual environment
```
$ code .
```
- Inside Visual Studio Code install [Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) and [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) extensions
- Reload Visual Studio Code
- Select `CVAT Debugging` configuration and start debugging (F5)
You have done! Now it is possible to insert breakpoints and debug server and client of the tool.

@ -10,7 +10,7 @@
* Primary developer
* Author and maintainer
- **[Andrey Zhavoronkov]()**
- **[Andrey Zhavoronkov](https://github.com/azhavoro)**
* Developer
* Author and maintainer
@ -18,8 +18,13 @@
# Contributors
- **[Victor Salimonov](https://github.com/VikTorSalimonov)**
* Documentation, screencasts
- **[Dmitry Sidnev](https://github.com/DmitriySidnev)**
* [convert_to_coco.py](utils/coco) - an utility for converting annotation from CVAT to COCO data annotation format
- **[Sebastián Yonekura](https://github.com/syonekura)**
* [convert_to_voc.py](cvat/utils/convert_to_voc.py) - an utility for
converting CVAT XML to PASCAL VOC data annotation format.
* [convert_to_voc.py](utils/voc) - an utility for converting CVAT XML to PASCAL VOC data annotation format.

@ -87,6 +87,7 @@ COPY supervisord.conf mod_wsgi.conf wait-for-it.sh manage.py ${HOME}/
RUN pip3 install --no-cache-dir -r /tmp/requirements/${DJANGO_CONFIGURATION}.txt
COPY cvat/ ${HOME}/cvat
COPY tests ${HOME}/tests
RUN patch -p1 < ${HOME}/cvat/apps/engine/static/engine/js/3rdparty.patch
RUN chown -R ${USER}:${USER} .
# RUN all commands below as 'django' user

@ -1,21 +1,21 @@
MIT License
Copyright (c) 2018 annotation
Copyright (C) 2018 Intel Corporation
 
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.
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.
 
SPDX-License-Identifier: MIT

@ -82,7 +82,8 @@ Type your login/password for the superuser [on the login page](http://localhost:
### Stop all containers
The command below will stop and remove containers and networks created by `up`. See documentation for [docker-compose down](https://docs.docker.com/compose/reference/down/) for more details.
The command below will stop and remove containers, networks, volumes, and images
created by `up`.
```bash
docker-compose down
@ -116,3 +117,26 @@ cvat:
environment:
DJANGO_LOG_SERVER_URL: https://annotation.example.com:5000
```
### Share path
You can use a share storage for data uploading during you are creating a task. To do that you can mount it to CVAT docker container. Example of docker-compose.override.yml for this purpose:
```yml
version: "2.3"
services:
cvat:
environment:
CVAT_SHARE_URL: "Mounted from /mnt/share host directory"
volumes:
cvat_share:/home/django/share:ro
volumes:
cvat_share:
driver_opts:
type: none
device: /mnt/share
o: bind
```
You can change the share device path to your actual share. For user convenience we have defined the enviroment variable $CVAT_SHARE_URL. This variable contains a text (url for example) which will be being shown in the client-share browser.

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

@ -1 +1,7 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
default_app_config = 'cvat.apps.authentication.apps.AuthenticationConfig'

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

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.apps import AppConfig
from django.db.models.signals import post_migrate, post_save
from .settings.authentication import DJANGO_AUTH_TYPE

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.shortcuts import resolve_url, reverse
from django.http import JsonResponse

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.contrib.auth.forms import (
UsernameField,
AuthenticationForm,

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

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

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

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.conf import settings
import ldap
from django_auth_ldap.config import LDAPSearch, NestedActiveDirectoryGroupType

@ -1,2 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
# Specify groups that new users will have
AUTH_SIMPLE_DEFAULT_GROUPS = []

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.conf import settings
import os

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.db import models
from django.conf import settings

@ -1,3 +1,8 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
{% extends "auth_base.html" %}
{% block title %}Forbidden{% endblock %}

@ -1,3 +1,8 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
{% extends "auth_base.html" %}
{% block title %}Login{% endblock %}

@ -1,3 +1,8 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
{% extends "auth_base.html" %}
{% block title %}Login{% endblock %}

@ -1,2 +1,7 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
<p>
</p>

@ -1,3 +1,8 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
{% extends "auth_base.html" %}
{% block title %}Create user{% endblock %}

@ -1,3 +1,8 @@
<!--
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 %}

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

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.urls import path
import os

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.shortcuts import render
from django.contrib.auth.views import LoginView
from django.http import HttpResponseRedirect

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

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

@ -1,5 +1,11 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.apps import AppConfig
class DashboardConfig(AppConfig):
name = 'dashboard'

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

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

@ -1,9 +1,16 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
"use strict";
/* Dashboard entrypoint */
window.cvat = window.cvat || {};
window.cvat.dashboard = window.cvat.dashboard || {};
window.cvat.dashboard.uiCallbacks = window.cvat.dashboard.uiCallbacks || [];
window.cvat.config = new Config();
window.cvat.dashboard.uiCallbacks.push(function(elements) {
elements.each(function(idx) {
@ -93,6 +100,7 @@ function setupTaskCreator() {
let cancelBrowseServer = $('#dashboardCancelBrowseServer');
let submitBrowseServer = $('#dashboardSubmitBrowseServer');
let flipImagesBox = $('#dashboardFlipImages');
let zOrderBox = $('#dashboardZOrder');
let segmentSizeInput = $('#dashboardSegmentSize');
let customSegmentSize = $('#dashboardCustomSegment');
let overlapSizeInput = $('#dashboardOverlap');
@ -109,6 +117,7 @@ function setupTaskCreator() {
let bugTrackerLink = bugTrackerInput.prop('value');
let source = 'local';
let flipImages = false;
let zOrder = false;
let segmentSize = 5000;
let overlapSize = 0;
let compressQuality = 50;
@ -168,7 +177,13 @@ function setupTaskCreator() {
updateSelectedFiles();
});
flipImagesBox.on('click', (e) => {flipImages = e.target.checked;});
flipImagesBox.on('click', (e) => {
flipImages = e.target.checked;
});
zOrderBox.on('click', (e) => {
zOrder = e.target.checked;
});
customSegmentSize.on('change', (e) => segmentSizeInput.prop('disabled', !e.target.checked));
customOverlapSize.on('change', (e) => overlapSizeInput.prop('disabled', !e.target.checked));
customCompressQuality.on('change', (e) => imageQualityInput.prop('disabled', !e.target.checked));
@ -258,6 +273,7 @@ function setupTaskCreator() {
taskData.append('bug_tracker_link', bugTrackerLink);
taskData.append('labels', labels);
taskData.append('flip_flag', flipImages);
taskData.append('z_order', zOrder);
taskData.append('storage', source);
if (customSegmentSize.prop('checked')) {
@ -285,7 +301,11 @@ function setupTaskCreator() {
taskMessage.css('color', 'red');
taskMessage.text(response);
},
() => submitCreate.prop('disabled', false));
() => submitCreate.prop('disabled', false),
(status) => {
taskMessage.css('color', 'blue');
taskMessage.text(status);
});
});
function updateSelectedFiles() {
@ -385,7 +405,7 @@ function setupSearch() {
/* Server requests */
function createTaskRequest(oData, onSuccessRequest, onSuccessCreate, onError, onComplete) {
function createTaskRequest(oData, onSuccessRequest, onSuccessCreate, onError, onComplete, onUpdateStatus) {
$.ajax({
url: '/create/task',
type: 'POST',
@ -433,6 +453,9 @@ function createTaskRequest(oData, onSuccessRequest, onSuccessCreate, onError, on
onComplete();
onError(data.stderr);
}
else if (data['state'] == 'started' && 'status' in data) {
onUpdateStatus(data['status']);
}
}
}
}
@ -488,7 +511,7 @@ function uploadAnnotationRequest() {
function loadXML(e) {
input.remove();
let overlay = showOverlay("File uploading..");
let overlay = showOverlay("File is being uploaded..");
let file = e.target.files[0];
let fileReader = new FileReader();
fileReader.onload = (e) => parseFile(e, overlay);
@ -501,39 +524,48 @@ function uploadAnnotationRequest() {
$.ajax({
url: '/get/task/' + window.cvat.dashboard.taskID,
success: function(data) {
let labels = new LabelsInfo(data.spec);
let fakeJob = {
let annotationParser = new AnnotationParser({
start: 0,
stop: data.size
stop: data.size,
image_meta_data: data.image_meta_data,
flipped: data.flipped
}, new LabelsInfo(data.spec));
let asyncParse = function() {
let parsed = null;
try {
parsed = annotationParser.parse(xmlText);
}
catch(error) {
overlay.remove();
showMessage("Parsing errors was occured. " + error);
return;
}
let asyncSave = function() {
$.ajax({
url: '/save/annotation/task/' + window.cvat.dashboard.taskID,
type: 'POST',
data: JSON.stringify(parsed),
contentType: 'application/json',
success: function() {
let message = 'Annotation successfully uploaded';
showMessage(message);
},
error: function(response) {
let message = 'Annotation uploading errors was occured. ' + response.responseText;
showMessage(message);
},
complete: () => overlay.remove()
});
};
overlay.setMessage('Annotation is being saved..');
setTimeout(asyncSave);
};
let annotationParser = new AnnotationParser(labels, fakeJob);
let parsed = null;
try {
parsed = annotationParser.parse(xmlText);
}
catch(error) {
let message = "Parsing errors was occured. " + error;
showMessage(message);
overlay.remove();
return;
}
overlay.setMessage('Annotation saving..');
$.ajax({
url: '/save/annotation/task/' + window.cvat.dashboard.taskID,
type: 'POST',
data: JSON.stringify(parsed),
contentType: 'application/json',
success: function() {
let message = 'Annotation successfully uploaded';
showMessage(message);
},
error: function(response) {
let message = 'Annotation uploading errors was occured. ' + response.responseText;
showMessage(message);
},
complete: () => overlay.remove()
});
overlay.setMessage('File is being parsed..');
setTimeout(asyncParse);
},
error: function(response) {
overlay.remove();

@ -1,3 +1,9 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
.dashboardTaskUI {
margin: 5px auto;
width: 1200px;

@ -1,3 +1,8 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
{% extends 'engine/base.html' %}
{% load static %}
{% load pagination_tags %}
@ -25,9 +30,8 @@
<script type="text/javascript" src="{% static 'dashboard/js/dashboard.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/listener.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/labelsInfo.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/shapes.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/annotationParser.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/trackModel.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/collectionModel.js' %}"></script>
<script type="text/javascript" src="{% static 'engine/js/server.js' %}"></script>
<script>
window.maxUploadSize = {{ max_upload_size }};
@ -102,8 +106,8 @@ Example: @select=race:__undefined__,skip,asian,black,caucasian,other'/>
<tr>
<td> <label class="regular h2"> Source: </label> </td>
<td>
<input id="dashboardLocalSource" type="radio" name="sourceType" value="local" checked=true/> <label class="regular h2" for="localSource"> Local </label>
<br> <input id="dashboardShareSource" type="radio" name="sourceType" value="share"/> <label class="regular h2" for="shareSource"> Share </label>
<input id="dashboardLocalSource" type="radio" name="sourceType" value="local" checked=true/> <label for="dashboardLocalSource" class="regular h2" for="localSource"> Local </label>
<br> <input id="dashboardShareSource" type="radio" name="sourceType" value="share"/> <label for="dashboardShareSource" class="regular h2" for="shareSource"> Share </label>
</td>
</tr>
<tr>
@ -114,6 +118,14 @@ Example: @select=race:__undefined__,skip,asian,black,caucasian,other'/>
<input type="checkbox" id="dashboardFlipImages"/>
</td>
</tr>
<tr>
<td>
<label class="regular h2"> Z-Order </label>
</td>
<td>
<input type="checkbox" id="dashboardZOrder"/>
</td>
</tr>
<tr>
<td>
<label class="regular h2"> Overlap Size </label>
@ -165,7 +177,7 @@ Example: @select=race:__undefined__,skip,asian,black,caucasian,other'/>
<div id="dashboardShareBrowseModal" class="modal hidden">
<div style="width: 600px; height: 400px;" class="modal-content noSelect">
<center> <label class="regular h1"> //icv-cifs/icv_projects/cvat/data </label> </center>
<center> <label class="regular h1"> {{ share_path }} </label> </center>
<div id="dashboardShareBrowser"> </div>
<center>
<button id="dashboardCancelBrowseServer" class="regular h2" style="margin: 0px 10px"> Cancel </button>

@ -1,3 +1,8 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
<div class="dashboardTaskUI" id="dashboardTask_{{item.task_id}}">
<center class="dashboardTitleWrapper">
<label class="semiBold h1 dashboardTaskNameLabel selectable"> {{ item.name }} </label>

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

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.urls import path
from . import views
@ -5,3 +10,4 @@ urlpatterns = [
path('get_share_nodes', views.JsTreeView),
path('', views.DashboardView),
]

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
from django.shortcuts import redirect
from django.shortcuts import render
@ -108,5 +113,6 @@ def DashboardView(request):
'data': data,
'max_upload_size': settings.LOCAL_LOAD_MAX_FILES_SIZE,
'max_upload_count': settings.LOCAL_LOAD_MAX_FILES_COUNT,
'share_path': os.getenv('CVAT_SHARE_URL', default=r'${cvat_root}/share'),
'js_3rdparty': JS_3RDPARTY.get('dashboard', [])
})

@ -1,3 +1,9 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from cvat.settings.base import JS_3RDPARTY
JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['documentation/js/shortcuts.js']

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

@ -1,5 +1,11 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.apps import AppConfig
class DocumentationConfig(AppConfig):
name = 'cvat.apps.documentation'

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

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

File diff suppressed because one or more lines are too long

@ -1,4 +1,10 @@
Mousetrap.bind(userConfig.shortkeys["open_help"].value, function() {
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
Mousetrap.bind(window.cvat.config.shortkeys["open_help"].value, function() {
window.location.href = "/documentation/user_guide.html";
return false;

@ -1,3 +1,8 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
<!DOCTYPE html>
{% load static compress %}

@ -1,3 +1,8 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
{% extends 'documentation/base_page.html' %}
{% block title %}

@ -1,3 +1,8 @@
<!--
Copyright (C) 2018 Intel Corporation
SPDX-License-Identifier: MIT
-->
{% extends 'documentation/base_page.html' %}
{% block title %}

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

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.urls import path
from . import views
@ -5,3 +10,4 @@ urlpatterns = [
path('user_guide.html', views.UserGuideView),
path('xml_format.html', views.XmlFormatView),
]

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.shortcuts import render
import os

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

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

File diff suppressed because it is too large Load Diff

@ -1,5 +1,11 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.apps import AppConfig
class EngineConfig(AppConfig):
name = 'engine'

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
import os
import inspect
import logging

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
# Generated by Django 2.0.3 on 2018-05-23 11:51
from django.conf import settings

@ -0,0 +1,175 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
# Generated by Django 2.0.3 on 2018-05-30 09:53
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('engine', '0001_release_v0_1_0'),
]
operations = [
migrations.CreateModel(
name='LabeledPoints',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('frame', models.PositiveIntegerField()),
('occluded', models.BooleanField(default=False)),
('points', models.TextField()),
('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.Job')),
('label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.Label')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='LabeledPointsAttributeVal',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.CharField(max_length=64)),
('points', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.LabeledPoints')),
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.AttributeSpec')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='LabeledPolygon',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('frame', models.PositiveIntegerField()),
('occluded', models.BooleanField(default=False)),
('points', models.TextField()),
('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.Job')),
('label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.Label')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='LabeledPolygonAttributeVal',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.CharField(max_length=64)),
('polygon', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.LabeledPolygon')),
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.AttributeSpec')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='LabeledPolyline',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('frame', models.PositiveIntegerField()),
('occluded', models.BooleanField(default=False)),
('points', models.TextField()),
('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.Job')),
('label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.Label')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='LabeledPolylineAttributeVal',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.CharField(max_length=64)),
('polyline', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.LabeledPolyline')),
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.AttributeSpec')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='TrackedPoints',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('occluded', models.BooleanField(default=False)),
('points', models.TextField()),
('frame', models.PositiveIntegerField()),
('outside', models.BooleanField(default=False)),
('track', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.ObjectPath')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='TrackedPointsAttributeVal',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.CharField(max_length=64)),
('points', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.TrackedPoints')),
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.AttributeSpec')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='TrackedPolygon',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('occluded', models.BooleanField(default=False)),
('points', models.TextField()),
('frame', models.PositiveIntegerField()),
('outside', models.BooleanField(default=False)),
('track', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.ObjectPath')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='TrackedPolygonAttributeVal',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.CharField(max_length=64)),
('polygon', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.TrackedPolygon')),
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.AttributeSpec')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='TrackedPolyline',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('occluded', models.BooleanField(default=False)),
('points', models.TextField()),
('frame', models.PositiveIntegerField()),
('outside', models.BooleanField(default=False)),
('track', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.ObjectPath')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='TrackedPolylineAttributeVal',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.CharField(max_length=64)),
('polyline', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.TrackedPolyline')),
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.AttributeSpec')),
],
options={
'abstract': False,
},
),
]

@ -0,0 +1,23 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
# Generated by Django 2.0.3 on 2018-06-04 11:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('engine', '0002_labeledpoints_labeledpointsattributeval_labeledpolygon_labeledpolygonattributeval_labeledpolyline_la'),
]
operations = [
migrations.AddField(
model_name='objectpath',
name='shapes',
field=models.CharField(default='boxes', max_length=10),
),
]

@ -0,0 +1,23 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
# Generated by Django 2.0.3 on 2018-06-09 10:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('engine', '0003_objectpath_shapes'),
]
operations = [
migrations.AddField(
model_name='task',
name='z_order',
field=models.BooleanField(default=False),
),
]

@ -0,0 +1,58 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
# Generated by Django 2.0.3 on 2018-06-09 12:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('engine', '0004_task_z_order'),
]
operations = [
migrations.AddField(
model_name='labeledbox',
name='z_order',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='labeledpoints',
name='z_order',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='labeledpolygon',
name='z_order',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='labeledpolyline',
name='z_order',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='trackedbox',
name='z_order',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='trackedpoints',
name='z_order',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='trackedpolygon',
name='z_order',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='trackedpolyline',
name='z_order',
field=models.IntegerField(default=0),
),
]

@ -0,0 +1,43 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
# Generated by Django 2.0.3 on 2018-06-29 12:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('engine', '0005_auto_20180609_1512'),
]
operations = [
migrations.AddField(
model_name='labeledbox',
name='group_id',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='labeledpoints',
name='group_id',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='labeledpolygon',
name='group_id',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='labeledpolyline',
name='group_id',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='objectpath',
name='group_id',
field=models.PositiveIntegerField(default=0),
),
]

@ -0,0 +1,23 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
# Generated by Django 2.0.3 on 2018-07-16 08:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('engine', '0006_auto_20180629_1501'),
]
operations = [
migrations.AddField(
model_name='task',
name='flipped',
field=models.BooleanField(default=False),
),
]

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

@ -1,3 +1,8 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from django.db import models
from django.conf import settings
@ -15,12 +20,14 @@ class Task(models.Model):
size = models.PositiveIntegerField()
path = models.CharField(max_length=256)
mode = models.CharField(max_length=32)
owner = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
bug_tracker = models.CharField(max_length=2000, default="")
created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=32, default="annotate")
overlap = models.PositiveIntegerField(default=0)
z_order = models.BooleanField(default=False)
flipped = models.BooleanField(default=False)
# Extend default permission model
class Meta:
@ -37,7 +44,8 @@ class Task(models.Model):
return os.path.join(self.path, "data")
def get_dump_path(self):
return os.path.join(self.path, "{}.dump".format(self.name))
name = re.sub(r'[\\/*?:"<>|]', '_', self.name)
return os.path.join(self.path, "{}.dump".format(name))
def get_log_path(self):
return os.path.join(self.path, "task.log")
@ -45,6 +53,9 @@ class Task(models.Model):
def get_client_log_path(self):
return os.path.join(self.path, "client.log")
def get_image_meta_cache_path(self):
return os.path.join(self.path, "image_meta.cache")
def set_task_dirname(self, path):
self.path = path
self.save(update_fields=['path'])
@ -72,18 +83,24 @@ class Label(models.Model):
def __str__(self):
return self.name
def parse_attribute(text):
match = re.match(r'^([~@])(\w+)=(\w+):(.+)?$', text)
prefix = match.group(1)
type = match.group(2)
name = match.group(3)
if match.group(4):
values = list(csv.reader(StringIO(match.group(4)), quotechar="'"))[0]
else:
values = []
return {'prefix':prefix, 'type':type, 'name':name, 'values':values}
class AttributeSpec(models.Model):
label = models.ForeignKey(Label, on_delete=models.CASCADE)
text = models.CharField(max_length=1024)
def get_attribute(self):
match = re.match(r'^([~@])(\w+)=(\w+):(.+)$', self.text)
prefix = match.group(1)
type = match.group(2)
name = match.group(3)
values = list(csv.reader(StringIO(match.group(4)), quotechar="'"))[0]
return {'prefix':prefix, 'type':type, 'name':name, 'values':values}
return parse_attribute(self.text)
def is_mutable(self):
attr = self.get_attribute()
@ -120,16 +137,26 @@ class Annotation(models.Model):
job = models.ForeignKey(Job, on_delete=models.CASCADE)
label = models.ForeignKey(Label, on_delete=models.CASCADE)
frame = models.PositiveIntegerField()
group_id = models.PositiveIntegerField(default=0)
class Meta:
abstract = True
class Shape(models.Model):
occluded = models.BooleanField(default=False)
z_order = models.IntegerField(default=0)
class Meta:
abstract = True
class BoundingBox(models.Model):
class BoundingBox(Shape):
xtl = models.FloatField()
ytl = models.FloatField()
xbr = models.FloatField()
ybr = models.FloatField()
# TODO: need to think where to define below properties
occluded = models.BooleanField(default=False)
class Meta:
abstract = True
class PolyShape(Shape):
points = models.TextField()
class Meta:
abstract = True
@ -139,9 +166,27 @@ class LabeledBox(Annotation, BoundingBox):
class LabeledBoxAttributeVal(AttributeVal):
box = models.ForeignKey(LabeledBox, on_delete=models.CASCADE)
class ObjectPath(Annotation):
class LabeledPolygon(Annotation, PolyShape):
pass
class LabeledPolygonAttributeVal(AttributeVal):
polygon = models.ForeignKey(LabeledPolygon, on_delete=models.CASCADE)
class LabeledPolyline(Annotation, PolyShape):
pass
class LabeledPolylineAttributeVal(AttributeVal):
polyline = models.ForeignKey(LabeledPolyline, on_delete=models.CASCADE)
class LabeledPoints(Annotation, PolyShape):
pass
class LabeledPointsAttributeVal(AttributeVal):
points = models.ForeignKey(LabeledPoints, on_delete=models.CASCADE)
class ObjectPath(Annotation):
shapes = models.CharField(max_length=10, default='boxes')
class ObjectPathAttributeVal(AttributeVal):
track = models.ForeignKey(ObjectPath, on_delete=models.CASCADE)
@ -157,3 +202,21 @@ class TrackedBox(TrackedObject, BoundingBox):
class TrackedBoxAttributeVal(AttributeVal):
box = models.ForeignKey(TrackedBox, on_delete=models.CASCADE)
class TrackedPolygon(TrackedObject, PolyShape):
pass
class TrackedPolygonAttributeVal(AttributeVal):
polygon = models.ForeignKey(TrackedPolygon, on_delete=models.CASCADE)
class TrackedPolyline(TrackedObject, PolyShape):
pass
class TrackedPolylineAttributeVal(AttributeVal):
polyline = models.ForeignKey(TrackedPolyline, on_delete=models.CASCADE)
class TrackedPoints(TrackedObject, PolyShape):
pass
class TrackedPointsAttributeVal(AttributeVal):
points = models.ForeignKey(TrackedPoints, on_delete=models.CASCADE)

@ -1,3 +1,9 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
html {
background-color: #FFFFFF;
margin: 0px auto;
@ -61,7 +67,7 @@ html {
.modal {
position: fixed;
z-index: 1;
z-index: 10;
left: 0;
top: 0;
width: 100%;
@ -74,10 +80,38 @@ html {
.modal-content {
background-color: #FFFFFF;
margin: 15% auto; /* 15% from the top and centered */
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%; /* Could be more or less, depending on screen size */
width: 80%;
}
.tab {
overflow: hidden;
border: 1px solid black;
background-color: #B0C4DE;
}
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
}
.tab button:hover {
background-color: #81aee8;
}
.tab button.active {
background-color: #ccc;
}
.activeTabButton {
background-color: #81aee8 !important;
}
.hidden {

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

@ -0,0 +1,117 @@
From b89380c65ea8bc9231cc98a6ae0e812227c85b3d Mon Sep 17 00:00:00 2001
From: Boris Sekachev <boris.sekachev@intel.com>
Date: Tue, 10 Jul 2018 14:31:13 +0300
Subject: [PATCH] tmp
---
.../engine/static/engine/js/3rdparty/svg.draggable.js | 1 +
cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js | 17 +++++++++++++++--
.../apps/engine/static/engine/js/3rdparty/svg.resize.js | 5 +++--
.../apps/engine/static/engine/js/3rdparty/svg.select.js | 1 +
4 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js
index d88abf5..06158f1 100644
--- a/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js
+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js
@@ -109,6 +109,7 @@
// while dragging
DragHandler.prototype.drag = function(e){
+ this.m = this.el.node.getScreenCTM().inverse();
var box = this.getBBox()
, p = this.transformPoint(e)
diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js
index 68dbf2a..9884b75 100644
--- a/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js
+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js
@@ -18,6 +18,7 @@
this.startPoint = null;
this.lastUpdateCall = null;
this.options = {};
+ this.set = new SVG.Set();
// Merge options and defaults
for (var i in this.el.draw.defaults) {
@@ -139,6 +140,8 @@
// Call the calc-function which calculates the new position and size
this.calc(event);
+ this.m = this.el.node.getScreenCTM().inverse();
+ this.offset = { x: window.pageXOffset, y: window.pageYOffset };
// Fire the `drawupdate`-event
this.el.fire('drawupdate', {event:event, p:this.p, m:this.m});
};
@@ -160,6 +163,16 @@
this.el.fire('drawcancel');
};
+ // Undo last drawed point
+ PaintHandler.prototype.undo = function () {
+ if (this.set.length()) {
+ this.set.members.splice(-1, 1)[0].remove();
+ this.el.array().value.splice(-2, 1);
+ this.el.plot(this.el.array());
+ this.el.fire('undopoint');
+ }
+ };
+
// Calculate the corrected position when using `snapToGrid`
PaintHandler.prototype.snapToGrid = function (draw) {
@@ -371,14 +384,14 @@
this.set.clear();
- for (var i = 0; i < array.length; ++i) {
+ for (var i = 0; i < array.length - 1; ++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));
+ this.set.add(this.parent.circle(5).stroke({width: 1}).fill('#ccc').center(p.x, p.y)).addClass("svg_draw_point");
}
}
diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.js
index 0c3b63d..fb5dc26 100644
--- a/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.js
+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.js
@@ -34,8 +34,8 @@
// Extract a position from a mouse/touch event.
// Returns { x: .., y: .. }
return {
- x: event.clientX || event.touches[0].pageX,
- y: event.clientY || event.touches[0].pageY
+ x: event.clientX != null ? event.clientX : event.touches[0].clientX,
+ y: event.clientY != null ? event.clientY : event.touches[0].clientY
};
};
@@ -343,6 +343,7 @@
}
return;
}
+ this.m = this.el.node.getScreenCTM().inverse();
// Calculate the difference between the mouseposition at start and now
var txPt = this._extractPosition(event);
diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js
index 47e07bd..f1d0c02 100644
--- a/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js
+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js
@@ -160,6 +160,7 @@ SelectHandler.prototype.drawPoints = function () {
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
ev.stopPropagation();
+ if (ev.which != 1) return false;
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});
--
2.7.4

File diff suppressed because it is too large Load Diff

@ -0,0 +1,184 @@
/**
* @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);

@ -0,0 +1,280 @@
/*
* JavaScript MD5
* https://github.com/blueimp/JavaScript-MD5
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* https://opensource.org/licenses/MIT
*
* Based on
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/* global define */
;(function ($) {
'use strict'
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safeAdd (x, y) {
var lsw = (x & 0xffff) + (y & 0xffff)
var msw = (x >> 16) + (y >> 16) + (lsw >> 16)
return (msw << 16) | (lsw & 0xffff)
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bitRotateLeft (num, cnt) {
return (num << cnt) | (num >>> (32 - cnt))
}
/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5cmn (q, a, b, x, s, t) {
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b)
}
function md5ff (a, b, c, d, x, s, t) {
return md5cmn((b & c) | (~b & d), a, b, x, s, t)
}
function md5gg (a, b, c, d, x, s, t) {
return md5cmn((b & d) | (c & ~d), a, b, x, s, t)
}
function md5hh (a, b, c, d, x, s, t) {
return md5cmn(b ^ c ^ d, a, b, x, s, t)
}
function md5ii (a, b, c, d, x, s, t) {
return md5cmn(c ^ (b | ~d), a, b, x, s, t)
}
/*
* Calculate the MD5 of an array of little-endian words, and a bit length.
*/
function binlMD5 (x, len) {
/* append padding */
x[len >> 5] |= 0x80 << (len % 32)
x[((len + 64) >>> 9 << 4) + 14] = len
var i
var olda
var oldb
var oldc
var oldd
var a = 1732584193
var b = -271733879
var c = -1732584194
var d = 271733878
for (i = 0; i < x.length; i += 16) {
olda = a
oldb = b
oldc = c
oldd = d
a = md5ff(a, b, c, d, x[i], 7, -680876936)
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586)
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819)
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330)
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897)
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426)
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341)
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983)
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416)
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417)
c = md5ff(c, d, a, b, x[i + 10], 17, -42063)
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162)
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682)
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101)
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290)
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329)
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510)
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632)
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713)
b = md5gg(b, c, d, a, x[i], 20, -373897302)
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691)
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083)
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335)
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848)
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438)
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690)
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961)
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501)
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467)
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784)
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473)
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734)
a = md5hh(a, b, c, d, x[i + 5], 4, -378558)
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463)
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562)
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556)
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060)
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353)
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632)
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640)
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174)
d = md5hh(d, a, b, c, x[i], 11, -358537222)
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979)
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189)
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487)
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835)
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520)
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651)
a = md5ii(a, b, c, d, x[i], 6, -198630844)
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415)
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905)
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055)
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571)
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606)
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523)
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799)
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359)
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744)
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380)
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649)
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070)
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379)
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259)
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551)
a = safeAdd(a, olda)
b = safeAdd(b, oldb)
c = safeAdd(c, oldc)
d = safeAdd(d, oldd)
}
return [a, b, c, d]
}
/*
* Convert an array of little-endian words to a string
*/
function binl2rstr (input) {
var i
var output = ''
var length32 = input.length * 32
for (i = 0; i < length32; i += 8) {
output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xff)
}
return output
}
/*
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*/
function rstr2binl (input) {
var i
var output = []
output[(input.length >> 2) - 1] = undefined
for (i = 0; i < output.length; i += 1) {
output[i] = 0
}
var length8 = input.length * 8
for (i = 0; i < length8; i += 8) {
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << (i % 32)
}
return output
}
/*
* Calculate the MD5 of a raw string
*/
function rstrMD5 (s) {
return binl2rstr(binlMD5(rstr2binl(s), s.length * 8))
}
/*
* Calculate the HMAC-MD5, of a key and some data (raw strings)
*/
function rstrHMACMD5 (key, data) {
var i
var bkey = rstr2binl(key)
var ipad = []
var opad = []
var hash
ipad[15] = opad[15] = undefined
if (bkey.length > 16) {
bkey = binlMD5(bkey, key.length * 8)
}
for (i = 0; i < 16; i += 1) {
ipad[i] = bkey[i] ^ 0x36363636
opad[i] = bkey[i] ^ 0x5c5c5c5c
}
hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8)
return binl2rstr(binlMD5(opad.concat(hash), 512 + 128))
}
/*
* Convert a raw string to a hex string
*/
function rstr2hex (input) {
var hexTab = '0123456789abcdef'
var output = ''
var x
var i
for (i = 0; i < input.length; i += 1) {
x = input.charCodeAt(i)
output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f)
}
return output
}
/*
* Encode a string as utf-8
*/
function str2rstrUTF8 (input) {
return unescape(encodeURIComponent(input))
}
/*
* Take string arguments and return either raw or hex encoded strings
*/
function rawMD5 (s) {
return rstrMD5(str2rstrUTF8(s))
}
function hexMD5 (s) {
return rstr2hex(rawMD5(s))
}
function rawHMACMD5 (k, d) {
return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d))
}
function hexHMACMD5 (k, d) {
return rstr2hex(rawHMACMD5(k, d))
}
function md5 (string, key, raw) {
if (!key) {
if (!raw) {
return hexMD5(string)
}
return rawMD5(string)
}
if (!raw) {
return hexHMACMD5(key, string)
}
return rawHMACMD5(key, string)
}
if (typeof define === 'function' && define.amd) {
define(function () {
return md5
})
} else if (typeof module === 'object' && module.exports) {
module.exports = md5
} else {
$.md5 = md5
}
})(this)

File diff suppressed because one or more lines are too long

@ -0,0 +1,227 @@
/*! 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);

@ -0,0 +1,442 @@
/*! 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

@ -0,0 +1,454 @@
/*!
* svg.resize.js - An extension for svg.js which allows to resize elements which are selected
* @version 1.4.1
* https://github.com/svgdotjs/svg.resize.js
*
* @copyright [object Object]
* @license MIT
*/;
;(function() {
"use strict";
;(function () {
function ResizeHandler(el) {
el.remember('_resizeHandler', this);
this.el = el;
this.parameters = {};
this.lastUpdateCall = null;
this.p = el.doc().node.createSVGPoint();
}
ResizeHandler.prototype.transformPoint = function(x, y, m){
this.p.x = x - (this.offset.x - window.pageXOffset);
this.p.y = y - (this.offset.y - window.pageYOffset);
return this.p.matrixTransform(m || this.m);
};
ResizeHandler.prototype._extractPosition = function(event) {
// Extract a position from a mouse/touch event.
// Returns { x: .., y: .. }
return {
x: event.clientX || event.touches[0].pageX,
y: event.clientY || event.touches[0].pageY
};
};
ResizeHandler.prototype.init = function (options) {
var _this = this;
this.stop();
if (options === 'stop') {
return;
}
this.options = {};
// Merge options and defaults
for (var i in this.el.resize.defaults) {
this.options[i] = this.el.resize.defaults[i];
if (typeof options[i] !== 'undefined') {
this.options[i] = options[i];
}
}
// We listen to all these events which are specifying different edges
this.el.on('lt.resize', function(e){ _this.resize(e || window.event); }); // Left-Top
this.el.on('rt.resize', function(e){ _this.resize(e || window.event); }); // Right-Top
this.el.on('rb.resize', function(e){ _this.resize(e || window.event); }); // Right-Bottom
this.el.on('lb.resize', function(e){ _this.resize(e || window.event); }); // Left-Bottom
this.el.on('t.resize', function(e){ _this.resize(e || window.event); }); // Top
this.el.on('r.resize', function(e){ _this.resize(e || window.event); }); // Right
this.el.on('b.resize', function(e){ _this.resize(e || window.event); }); // Bottom
this.el.on('l.resize', function(e){ _this.resize(e || window.event); }); // Left
this.el.on('rot.resize', function(e){ _this.resize(e || window.event); }); // Rotation
this.el.on('point.resize', function(e){ _this.resize(e || window.event); }); // Point-Moving
// This call ensures, that the plugin reacts to a change of snapToGrid immediately
this.update();
};
ResizeHandler.prototype.stop = function(){
this.el.off('lt.resize');
this.el.off('rt.resize');
this.el.off('rb.resize');
this.el.off('lb.resize');
this.el.off('t.resize');
this.el.off('r.resize');
this.el.off('b.resize');
this.el.off('l.resize');
this.el.off('rot.resize');
this.el.off('point.resize');
return this;
};
ResizeHandler.prototype.resize = function (event) {
var _this = this;
this.m = this.el.node.getScreenCTM().inverse();
this.offset = { x: window.pageXOffset, y: window.pageYOffset };
var txPt = this._extractPosition(event.detail.event);
this.parameters = {
type: this.el.type, // the type of element
p: this.transformPoint(txPt.x, txPt.y),
x: event.detail.x, // x-position of the mouse when resizing started
y: event.detail.y, // y-position of the mouse when resizing started
box: this.el.bbox(), // The bounding-box of the element
rotation: this.el.transform().rotation // The current rotation of the element
};
// Add font-size parameter if the element type is text
if (this.el.type === "text") {
this.parameters.fontSize = this.el.attr()["font-size"];
}
// the i-param in the event holds the index of the point which is moved, when using `deepSelect`
if (event.detail.i !== undefined) {
// get the point array
var array = this.el.array().valueOf();
// Save the index and the point which is moved
this.parameters.i = event.detail.i;
this.parameters.pointCoords = [array[event.detail.i][0], array[event.detail.i][1]];
}
// Lets check which edge of the bounding-box was clicked and resize the this.el according to this
switch (event.type) {
// Left-Top-Edge
case 'lt':
// We build a calculating function for every case which gives us the new position of the this.el
this.calc = function (diffX, diffY) {
// The procedure is always the same
// First we snap the edge to the given grid (snapping to 1px grid is normal resizing)
var snap = this.snapToGrid(diffX, diffY);
// Now we check if the new height and width still valid (> 0)
if (this.parameters.box.width - snap[0] > 0 && this.parameters.box.height - snap[1] > 0) {
// ...if valid, we resize the this.el (which can include moving because the coord-system starts at the left-top and this edge is moving sometimes when resized)
/*
* but first check if the element is text box, so we can change the font size instead of
* the width and height
*/
if (this.parameters.type === "text") {
this.el.move(this.parameters.box.x + snap[0], this.parameters.box.y);
this.el.attr("font-size", this.parameters.fontSize - snap[0]);
return;
}
this.el.move(this.parameters.box.x + snap[0], this.parameters.box.y + snap[1]).size(this.parameters.box.width - snap[0], this.parameters.box.height - snap[1]);
}
};
break;
// Right-Top
case 'rt':
// s.a.
this.calc = function (diffX, diffY) {
var snap = this.snapToGrid(diffX, diffY, 1 << 1);
if (this.parameters.box.width + snap[0] > 0 && this.parameters.box.height - snap[1] > 0) {
if (this.parameters.type === "text") {
this.el.move(this.parameters.box.x - snap[0], this.parameters.box.y);
this.el.attr("font-size", this.parameters.fontSize + snap[0]);
return;
}
this.el.move(this.parameters.box.x, this.parameters.box.y + snap[1]).size(this.parameters.box.width + snap[0], this.parameters.box.height - snap[1]);
}
};
break;
// Right-Bottom
case 'rb':
// s.a.
this.calc = function (diffX, diffY) {
var snap = this.snapToGrid(diffX, diffY, 0);
if (this.parameters.box.width + snap[0] > 0 && this.parameters.box.height + snap[1] > 0) {
if (this.parameters.type === "text") {
this.el.move(this.parameters.box.x - snap[0], this.parameters.box.y);
this.el.attr("font-size", this.parameters.fontSize + snap[0]);
return;
}
this.el.move(this.parameters.box.x, this.parameters.box.y).size(this.parameters.box.width + snap[0], this.parameters.box.height + snap[1]);
}
};
break;
// Left-Bottom
case 'lb':
// s.a.
this.calc = function (diffX, diffY) {
var snap = this.snapToGrid(diffX, diffY, 1);
if (this.parameters.box.width - snap[0] > 0 && this.parameters.box.height + snap[1] > 0) {
if (this.parameters.type === "text") {
this.el.move(this.parameters.box.x + snap[0], this.parameters.box.y);
this.el.attr("font-size", this.parameters.fontSize - snap[0]);
return;
}
this.el.move(this.parameters.box.x + snap[0], this.parameters.box.y).size(this.parameters.box.width - snap[0], this.parameters.box.height + snap[1]);
}
};
break;
// Top
case 't':
// s.a.
this.calc = function (diffX, diffY) {
var snap = this.snapToGrid(diffX, diffY, 1 << 1);
if (this.parameters.box.height - snap[1] > 0) {
// Disable the font-resizing if it is not from the corner of bounding-box
if (this.parameters.type === "text") {
return;
}
this.el.move(this.parameters.box.x, this.parameters.box.y + snap[1]).height(this.parameters.box.height - snap[1]);
}
};
break;
// Right
case 'r':
// s.a.
this.calc = function (diffX, diffY) {
var snap = this.snapToGrid(diffX, diffY, 0);
if (this.parameters.box.width + snap[0] > 0) {
if (this.parameters.type === "text") {
return;
}
this.el.move(this.parameters.box.x, this.parameters.box.y).width(this.parameters.box.width + snap[0]);
}
};
break;
// Bottom
case 'b':
// s.a.
this.calc = function (diffX, diffY) {
var snap = this.snapToGrid(diffX, diffY, 0);
if (this.parameters.box.height + snap[1] > 0) {
if (this.parameters.type === "text") {
return;
}
this.el.move(this.parameters.box.x, this.parameters.box.y).height(this.parameters.box.height + snap[1]);
}
};
break;
// Left
case 'l':
// s.a.
this.calc = function (diffX, diffY) {
var snap = this.snapToGrid(diffX, diffY, 1);
if (this.parameters.box.width - snap[0] > 0) {
if (this.parameters.type === "text") {
return;
}
this.el.move(this.parameters.box.x + snap[0], this.parameters.box.y).width(this.parameters.box.width - snap[0]);
}
};
break;
// Rotation
case 'rot':
// s.a.
this.calc = function (diffX, diffY) {
// yes this is kinda stupid but we need the mouse coords back...
var current = {x: diffX + this.parameters.p.x, y: diffY + this.parameters.p.y};
// start minus middle
var sAngle = Math.atan2((this.parameters.p.y - this.parameters.box.y - this.parameters.box.height / 2), (this.parameters.p.x - this.parameters.box.x - this.parameters.box.width / 2));
// end minus middle
var pAngle = Math.atan2((current.y - this.parameters.box.y - this.parameters.box.height / 2), (current.x - this.parameters.box.x - this.parameters.box.width / 2));
var angle = (pAngle - sAngle) * 180 / Math.PI;
// We have to move the element to the center of the box first and change the rotation afterwards
// because rotation always works around a rotation-center, which is changed when moving the element
// We also set the new rotation center to the center of the box.
this.el.center(this.parameters.box.cx, this.parameters.box.cy).rotate(this.parameters.rotation + angle - angle % this.options.snapToAngle, this.parameters.box.cx, this.parameters.box.cy);
};
break;
// Moving one single Point (needed when an element is deepSelected which means you can move every single point of the object)
case 'point':
this.calc = function (diffX, diffY) {
// Snapping the point to the grid
var snap = this.snapToGrid(diffX, diffY, this.parameters.pointCoords[0], this.parameters.pointCoords[1]);
// Get the point array
var array = this.el.array().valueOf();
// Changing the moved point in the array
array[this.parameters.i][0] = this.parameters.pointCoords[0] + snap[0];
array[this.parameters.i][1] = this.parameters.pointCoords[1] + snap[1];
// And plot the new this.el
this.el.plot(array);
};
}
this.el.fire('resizestart', {dx: this.parameters.x, dy: this.parameters.y, event: event});
// When resizing started, we have to register events for...
// Touches.
SVG.on(window, 'touchmove.resize', function(e) {
_this.update(e || window.event);
});
SVG.on(window, 'touchend.resize', function() {
_this.done();
});
// Mouse.
SVG.on(window, 'mousemove.resize', function (e) {
_this.update(e || window.event);
});
SVG.on(window, 'mouseup.resize', function () {
_this.done();
});
};
// The update-function redraws the element every time the mouse is moving
ResizeHandler.prototype.update = function (event) {
if (!event) {
if (this.lastUpdateCall) {
this.calc(this.lastUpdateCall[0], this.lastUpdateCall[1]);
}
return;
}
// Calculate the difference between the mouseposition at start and now
var txPt = this._extractPosition(event);
var p = this.transformPoint(txPt.x, txPt.y);
var diffX = p.x - this.parameters.p.x,
diffY = p.y - this.parameters.p.y;
this.lastUpdateCall = [diffX, diffY];
// Calculate the new position and height / width of the element
this.calc(diffX, diffY);
// Emit an event to say we have changed.
this.el.fire('resizing', {dx: diffX, dy: diffY, event: event});
};
// Is called on mouseup.
// Removes the update-function from the mousemove event
ResizeHandler.prototype.done = function () {
this.lastUpdateCall = null;
SVG.off(window, 'mousemove.resize');
SVG.off(window, 'mouseup.resize');
SVG.off(window, 'touchmove.resize');
SVG.off(window, 'touchend.resize');
this.el.fire('resizedone');
};
// The flag is used to determine whether the resizing is used with a left-Point (first bit) and top-point (second bit)
// In this cases the temp-values are calculated differently
ResizeHandler.prototype.snapToGrid = function (diffX, diffY, flag, pointCoordsY) {
var temp;
// If `pointCoordsY` is given, a single Point has to be snapped (deepSelect). That's why we need a different temp-value
if (typeof pointCoordsY !== 'undefined') {
// Note that flag = pointCoordsX in this case
temp = [(flag + diffX) % this.options.snapToGrid, (pointCoordsY + diffY) % this.options.snapToGrid];
} else {
// We check if the flag is set and if not we set a default-value (both bits set - which means upper-left-edge)
flag = flag == null ? 1 | 1 << 1 : flag;
temp = [(this.parameters.box.x + diffX + (flag & 1 ? 0 : this.parameters.box.width)) % this.options.snapToGrid, (this.parameters.box.y + diffY + (flag & (1 << 1) ? 0 : this.parameters.box.height)) % this.options.snapToGrid];
}
diffX -= (Math.abs(temp[0]) < this.options.snapToGrid / 2 ?
temp[0] :
temp[0] - (diffX < 0 ? -this.options.snapToGrid : this.options.snapToGrid));
diffY -= (Math.abs(temp[1]) < this.options.snapToGrid / 2 ?
temp[1] :
temp[1] - (diffY < 0 ? -this.options.snapToGrid : this.options.snapToGrid));
return this.constraintToBox(diffX, diffY, flag, pointCoordsY);
};
// keep element within constrained box
ResizeHandler.prototype.constraintToBox = function (diffX, diffY, flag, pointCoordsY) {
//return [diffX, diffY]
var c = this.options.constraint || {};
var orgX, orgY;
if (typeof pointCoordsY !== 'undefined') {
orgX = flag;
orgY = pointCoordsY;
} else {
orgX = this.parameters.box.x + (flag & 1 ? 0 : this.parameters.box.width);
orgY = this.parameters.box.y + (flag & (1<<1) ? 0 : this.parameters.box.height);
}
if (typeof c.minX !== 'undefined' && orgX + diffX < c.minX) {
diffX = c.minX - orgX;
}
if (typeof c.maxX !== 'undefined' && orgX + diffX > c.maxX) {
diffX = c.maxX - orgX;
}
if (typeof c.minY !== 'undefined' && orgY + diffY < c.minY) {
diffY = c.minY - orgY;
}
if (typeof c.maxY !== 'undefined' && orgY + diffY > c.maxY) {
diffY = c.maxY - orgY;
}
return [diffX, diffY];
};
SVG.extend(SVG.Element, {
// Resize element with mouse
resize: function (options) {
(this.remember('_resizeHandler') || new ResizeHandler(this)).init(options || {});
return this;
}
});
SVG.Element.prototype.resize.defaults = {
snapToAngle: 0.1, // Specifies the speed the rotation is happening when moving the mouse
snapToGrid: 1, // Snaps to a grid of `snapToGrid` Pixels
constraint: {} // keep element within constrained box
};
}).call(this);
}());

@ -0,0 +1,417 @@
/*!
* 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,162 +1,85 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported AnnotationParser */
"use strict";
class AnnotationParser {
constructor(labelsInfo, job) {
constructor(job, labelsInfo) {
this._parser = new DOMParser();
this._labelsInfo = labelsInfo;
this._startFrame = job.start;
this._stopFrame = job.stop;
this._flipped = job.flipped;
this._im_meta = job.image_meta_data;
this._labelsInfo = labelsInfo;
}
_parseInterpolationData(xml) {
let data = [];
let trackTags = xml.getElementsByTagName('track');
for (let trackTag of trackTags) {
let labelName = trackTag.getAttribute('label');
let labelId = this._labelsInfo.labelIdOf(labelName);
if (labelId === null) {
throw Error('An unknown label found in the annotation file: ' + name);
}
let immutableAttributes = {};
let boxTags = Array.from(trackTag.getElementsByTagName('box'));
// Old annotation support
if (boxTags.length == 3 && boxTags[0].getAttribute('outside')) {
boxTags.shift();
}
let track = {
label_id: labelId,
frame: +boxTags[0].getAttribute('frame'),
boxes: [],
attributes: []
};
for (let boxTag of boxTags) {
let keyFrame = +boxTag.getAttribute('keyframe');
let frame = +boxTag.getAttribute('frame');
let [xtl, ytl, xbr, ybr, occluded, outside] = this._getPosition(boxTag);
if (!keyFrame && frame != this._startFrame || frame < this._startFrame ||
(frame > this._stopFrame && !(frame == this._stopFrame + 1 && outside))) { // for annotation boxes on last frame (outside for it behind the last frame)
continue;
}
let box = {
frame: frame,
xtl: xtl,
ytl: ytl,
xbr: xbr,
ybr: ybr,
occluded: occluded,
outside: outside,
attributes: []
};
let mutableAttributes = {};
let attrTags = boxTag.getElementsByTagName('attribute');
for (let attrTag of attrTags) {
let [id, value] = this._getAttribute(labelId, attrTag);
if (this._labelsInfo.attrInfo(id).mutable) {
mutableAttributes[id] = value;
}
else {
immutableAttributes[id] = value;
}
}
_xmlParseError(parsedXML) {
return parsedXML.getElementsByTagName("parsererror");
}
for (let key in mutableAttributes) {
box.attributes.push({
id: key,
value: mutableAttributes[key]
});
}
_getBoxPosition(box, frame) {
frame = Math.min(frame - this._startFrame, this._im_meta['original_size'].length - 1);
let im_w = this._im_meta['original_size'][frame].width;
let im_h = this._im_meta['original_size'][frame].height;
track.boxes.push(box);
}
let xtl = +box.getAttribute('xtl');
let ytl = +box.getAttribute('ytl');
let xbr = +box.getAttribute('xbr');
let ybr = +box.getAttribute('ybr');
for (let key in immutableAttributes) {
track.attributes.push({
id: key,
value: immutableAttributes[key]
});
}
if (xtl < 0 || ytl < 0 || xbr < 0 || ybr < 0 ||
xtl > im_w || ytl > im_h || xbr > im_w || ybr > im_h) {
let message = `Incorrect bb found in annotation file: xtl=${xtl} ytl=${ytl} xbr=${xbr} ybr=${ybr}. `;
message += `Box out of range: ${im_w}x${im_h}`;
throw Error(message);
}
data.push(track);
if (this._flipped) {
let _xtl = im_w - xbr;
let _xbr = im_w - xtl;
let _ytl = im_h - ybr;
let _ybr = im_h - ytl;
xtl = _xtl;
ytl = _ytl;
xbr = _xbr;
ybr = _ybr;
}
return data;
let occluded = +box.getAttribute('occluded');
let z_order = box.getAttribute('z_order') || '0';
return [xtl, ytl, xbr, ybr, occluded, +z_order];
}
_parseAnnotationData(xml) {
let data = [];
let images = xml.getElementsByTagName('image');
for (let image of images) {
let frame = +image.getAttribute('id');
if (frame < this._startFrame || frame > this._stopFrame) continue;
let boxTags = image.getElementsByTagName('box');
for (let boxTag of boxTags) {
let labelName = boxTag.getAttribute('label');
let labelId = this._labelsInfo.labelIdOf(labelName);
if (labelId === null) {
throw Error('An unknown label found in the annotation file: ' + name);
}
let [xtl, ytl, xbr, ybr, occluded] = this._getPosition(boxTag);
let box = {
label_id: labelId,
xtl: xtl,
ytl: ytl,
xbr: xbr,
ybr: ybr,
occluded: occluded,
frame: frame,
attributes: []
};
let attributes = {};
let attrTags = boxTag.getElementsByTagName('attribute');
for (let attrTag of attrTags ) {
let [id, value] = this._getAttribute(labelId, attrTag);
attributes[id] = value;
}
for (let key in attributes) {
box.attributes.push({
id: key,
value: attributes[key]
});
}
data.push(box);
_getPolyPosition(shape, frame) {
frame = Math.min(frame - this._startFrame, this._im_meta['original_size'].length - 1);
let im_w = this._im_meta['original_size'][frame].width;
let im_h = this._im_meta['original_size'][frame].height;
let points = shape.getAttribute('points').split(';').join(' ');
points = PolyShapeModel.convertStringToNumberArray(points);
for (let point of points) {
if (point.x < 0 || point.y < 0 || point.x > im_w || point.y > im_h) {
let message = `Incorrect point found in annotation file x=${point.x} y=${point.y}. `;
message += `Point out of range ${im_w}x${im_h}`;
throw Error(message);
}
}
return data;
}
_getPosition(boxTag) {
let xtl = +boxTag.getAttribute('xtl');
let ytl = +boxTag.getAttribute('ytl');
let xbr = +boxTag.getAttribute('xbr');
let ybr = +boxTag.getAttribute('ybr');
if (xtl >= xbr || ytl >= ybr || xtl < 0 || ytl < 0) {
let message = `Incorrect box found in annotation file.
Position: x: ${xtl}, y: ${ytl}, width: ${xbr - xtl}, height: ${ybr - ytl}`;
throw Error(message);
if (this._flipped) {
point.x = im_w - point.x;
point.y = im_h - point.y;
}
}
points = PolyShapeModel.convertNumberArrayToString(points);
let outside = +boxTag.getAttribute('outside');
let occluded = +boxTag.getAttribute('occluded');
return [xtl, ytl, xbr, ybr, occluded, outside];
let occluded = +shape.getAttribute('occluded');
let z_order = shape.getAttribute('z_order') || '0';
return [points, occluded, +z_order];
}
_getAttribute(labelId, attrTag) {
let name = attrTag.getAttribute('name');
let attrId = this._labelsInfo.attrIdOf(labelId, name);
@ -185,11 +108,280 @@ class AnnotationParser {
return [attrId, value];
}
_getAttributeList(shape, labelId) {
let attributeDict = {};
let attributes = shape.getElementsByTagName('attribute');
for (let attribute of attributes ) {
let [id, value] = this._getAttribute(labelId, attribute);
attributeDict[id] = value;
}
_xmlParseError(parsedXML) {
return parsedXML.getElementsByTagName("parsererror");
let attributeList = [];
for (let attrId in attributeDict) {
attributeList.push({
id: attrId,
value: attributeDict[attrId],
});
}
return attributeList;
}
_getShapeFromPath(shape_type, tracks) {
let result = [];
for (let track of tracks) {
let label = track.getAttribute('label');
let group_id = track.getAttribute('group_id') || "0";
let labelId = this._labelsInfo.labelIdOf(label);
if (labelId === null) {
throw Error(`An unknown label found in the annotation file: ${label}`);
}
let shapes = Array.from(track.getElementsByTagName(shape_type));
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_id);
result.push(shapes[0]);
}
}
}
return result;
}
_parseAnnotationData(xml) {
let data = {
boxes: [],
polygons: [],
polylines: [],
points: []
};
let tracks = xml.getElementsByTagName('track');
let parsed = {
boxes: this._getShapeFromPath('box', tracks),
polygons: this._getShapeFromPath('polygon', tracks),
polylines: this._getShapeFromPath('polyline', tracks),
points: this._getShapeFromPath('points', tracks),
};
let images = xml.getElementsByTagName('image');
for (let image of images) {
let frame = image.getAttribute('id');
for (let box of image.getElementsByTagName('box')) {
box.setAttribute('frame', frame);
parsed.boxes.push(box);
}
for (let polygon of image.getElementsByTagName('polygon')) {
polygon.setAttribute('frame', frame);
parsed.polygons.push(polygon);
}
for (let polyline of image.getElementsByTagName('polyline')) {
polyline.setAttribute('frame', frame);
parsed.polylines.push(polyline);
}
for (let points of image.getElementsByTagName('points')) {
points.setAttribute('frame', frame);
parsed.points.push(points);
}
}
for (let shape_type in parsed) {
for (let shape of parsed[shape_type]) {
let frame = +shape.getAttribute('frame');
if (frame < this._startFrame || frame > this._stopFrame) continue;
let labelId = this._labelsInfo.labelIdOf(shape.getAttribute('label'));
let groupId = shape.getAttribute('group_id') || "0";
if (labelId === null) {
throw Error('An unknown label found in the annotation file: ' + shape.getAttribute('label'));
}
let attributeList = this._getAttributeList(shape, labelId);
if (shape_type === 'boxes') {
let [xtl, ytl, xbr, ybr, occluded, z_order] = this._getBoxPosition(shape, frame);
data.boxes.push({
label_id: labelId,
group_id: +groupId,
frame: frame,
occluded: occluded,
xtl: xtl,
ytl: ytl,
xbr: xbr,
ybr: ybr,
z_order: z_order,
attributes: attributeList,
});
}
else {
let [points, occluded, z_order] = this._getPolyPosition(shape, frame);
data[shape_type].push({
label_id: labelId,
group_id: +groupId,
frame: frame,
points: points,
occluded: occluded,
z_order: z_order,
attributes: attributeList,
});
}
}
}
return data;
}
_parseInterpolationData(xml) {
let data = {
box_paths: [],
polygon_paths: [],
polyline_paths: [],
points_paths: []
};
let tracks = xml.getElementsByTagName('track');
for (let track of tracks) {
let labelId = this._labelsInfo.labelIdOf(track.getAttribute('label'));
let groupId = track.getAttribute('group_id') || "0";
if (labelId === null) {
throw Error('An unknown label found in the annotation file: ' + name);
}
let parsed = {
boxes: Array.from(track.getElementsByTagName('box')),
polygons: Array.from(track.getElementsByTagName('polygon')),
polylines: Array.from(track.getElementsByTagName('polyline')),
points: Array.from(track.getElementsByTagName('points')),
};
for (let shape_type in parsed) {
let shapes = parsed[shape_type];
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')) {
parsed[shape_type] = []; // pseudo interpolation track (actually is annotation)
}
}
}
let type = null, target = null;
if (parsed.boxes.length) {
type = 'boxes';
target = 'box_paths';
}
else if (parsed.polygons.length) {
type = 'polygons';
target = 'polygon_paths';
}
else if (parsed.polylines.length) {
type = 'polylines';
target = 'polyline_paths';
}
else if (parsed.points.length) {
type = 'points';
target = 'points_paths';
}
else continue;
let path = {
label_id: labelId,
group_id: +groupId,
frame: +parsed[type][0].getAttribute('frame'),
attributes: [],
shapes: []
};
for (let shape of parsed[type]) {
let keyFrame = +shape.getAttribute('keyframe');
let outside = +shape.getAttribute('outside');
let 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.
*/
let significant = keyFrame || frame === this._startFrame;
significant = significant && frame >= this._startFrame;
significant = significant && frame <= this._stopFrame;
if (significant) {
let attributeList = this._getAttributeList(shape, labelId);
let shapeAttributes = [];
let pathAttributes = [];
for (let attr of attributeList) {
let attrInfo = this._labelsInfo.attrInfo(attr.id);
if (attrInfo.mutable) {
shapeAttributes.push({
id: attr.id,
value: attr.value,
});
}
else {
pathAttributes.push({
id: attr.id,
value: attr.value,
});
}
}
path.attributes = pathAttributes;
if (type === 'boxes') {
let [xtl, ytl, xbr, ybr, occluded, z_order] = this._getBoxPosition(shape, frame);
path.shapes.push({
frame: frame,
occluded: occluded,
outside: outside,
xtl: xtl,
ytl: ytl,
xbr: xbr,
ybr: ybr,
z_order: z_order,
attributes: shapeAttributes,
});
}
else {
let [points, occluded, z_order] = this._getPolyPosition(shape, frame);
path.shapes.push({
frame: frame,
occluded: occluded,
outside: outside,
points: points,
z_order: z_order,
attributes: shapeAttributes,
});
}
}
}
if (path.shapes.length) {
data[target].push(path);
}
}
return data;
}
parse(text) {
let xml = this._parser.parseFromString(text, 'text/xml');
@ -200,9 +392,6 @@ class AnnotationParser {
let interpolationData = this._parseInterpolationData(xml);
let annotationData = this._parseAnnotationData(xml);
return {
"boxes": annotationData,
"tracks": interpolationData
};
return Object.assign({}, annotationData, interpolationData);
}
}

File diff suppressed because it is too large Load Diff

@ -1,22 +0,0 @@
/* exported setupAPI */
function setupAPI(collectionModel, job) {
window.cvat = {};
let space = window.cvat;
space.tracks = {
get: collectionModel.exportTracks.bind(collectionModel),
set: (data) => {
collectionModel.importTracks(data);
collectionModel.update();
},
clear: collectionModel.removeTracks.bind(collectionModel),
};
space.job = {
id: job.jobid,
mode: job.mode,
start: job.start,
stop: job.stop
};
}

@ -1,507 +1,393 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported AAMModel AAMController AAMView */
"use strict";
const AAMUndefinedKeyword = '__undefined__';
/* AAM is Attribute Annotation Mode with using keyboard only */
class AAMModel extends Listener {
constructor(labelsInfo, setActiveTrack, resetActiveTrack, focus) {
super('onAAMUpdate', getState);
this._activeAAM = false;
this._labelsInfo = labelsInfo;
this._currentTracks = [];
this._activeTrack = null;
this._curFrame = null;
this._setActiveTrackCallback = setActiveTrack;
this._resetActiveTrackCallback = resetActiveTrack;
class AAMModel extends Listener {
constructor(shapeCollection, focus) {
super('onAAMUpdate', () => this);
this._shapeCollection = shapeCollection;
this._focus = focus;
this._zoomBoxes = true;
this._zoomMargin = 100;
this._hideNonActive = true;
this._attributeByLabel = new Object();
this._titles = new Object();
this._helps = new Object();
let labels = labelsInfo.labels();
for (let labelId in labels) {
let attributes = labelsInfo.labelAttributes(labelId);
this._attributeByLabel[labelId] = Object.keys(attributes).length ? 0 : null;
for (let attrId in attributes) {
this._titles[attrId] = labels[labelId].normalize() + ' : ' + attributes[attrId].normalize();
this._helps[attrId] = [];
let attrInfo = labelsInfo.attrInfo(attrId);
switch (attrInfo.type) {
case 'text':
case 'number':
continue;
case 'checkbox':
this._helps[attrId].push('0 - ' + attrInfo.values[0]);
this._helps[attrId].push('1 - ' + !attrInfo.values[0]);
break;
default:
for (let idx = 0; idx < attrInfo.values.length; idx ++) {
if (idx > 9) break;
if (attrInfo.values[0] === AAMUndefinedKeyword) {
if (!idx) continue;
this._helps[attrId].push(idx - 1 + ' - ' + attrInfo.values[idx]);
}
else {
this._helps[attrId].push(idx + ' - ' + attrInfo.values[idx]);
}
}
} // switch
} // attribute for
} // label for
let self = this;
function getState() {
return self;
this._activeAAM = false;
this._activeIdx = null;
this._active = null;
this._margin = 100;
this._currentShapes = [];
this._attrNumberByLabel = {};
this._helps = {};
for (let labelId in window.cvat.labelsInfo.labels()) {
let labelAttributes = window.cvat.labelsInfo.labelAttributes(labelId);
if (Object.keys(labelAttributes).length) {
this._attrNumberByLabel[labelId] = {
current: 0,
end: Object.keys(labelAttributes).length
};
for (let attrId in labelAttributes) {
this._helps[attrId] = {
title: `${window.cvat.labelsInfo.labels()[labelId]}, ${window.cvat.labelsInfo.attributes()[attrId]}`,
help: getHelp(attrId),
};
}
}
}
} // constructor
function getHelp(attrId) {
let attrInfo = window.cvat.labelsInfo.attrInfo(attrId);
let 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 ++) {
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]);
}
}
}
_labelId() {
if (this._activeTrack != null) {
return this._currentTracks[this._activeTrack].label;
return help;
}
return null;
}
_attributeId() {
let labelId = this._labelId();
let attrKeys = Object.keys(this._labelsInfo.labelAttributes(labelId));
let attrOrderIdx = this._attributeByLabel[labelId];
if (attrOrderIdx != null) {
return +attrKeys[attrOrderIdx];
}
shapeCollection.subscribe(this);
}
_updateActiveTrack(active) {
if (this._activeTrack === null) return;
let track = this._currentTracks[this._activeTrack];
track.activeAAMTrack = active;
track.hidden = !active && this._hideNonActive;
if (active) {
if (this._curFrame != null && this._zoomBoxes) {
let pos = track.interpolate(this._curFrame).position;
this._focus(pos.xtl - this._zoomMargin, pos.xbr + this._zoomMargin, pos.ytl - this._zoomMargin, pos.ybr + this._zoomMargin);
_bbRect(pos) {
if ('points' in pos) {
let 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 (let 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);
}
this._setActiveTrackCallback(track.id); // via track collection for occluded property
return [xtl, ytl, xbr, ybr];
}
else {
this._resetActiveTrackCallback();
return [pos.xtl, pos.ytl, pos.xbr, pos.ybr];
}
}
_updateCollection() {
this._currentShapes = [];
_updateActiveAttribute(active) {
if (this._activeTrack === null) return;
if (active) {
let attrId = this._attributeId();
if (Number.isInteger(+attrId)) {
this._currentTracks[this._activeTrack].activeAttribute = attrId;
for (let shape of this._shapeCollection.currentShapes) {
let labelAttributes = window.cvat.labelsInfo.labelAttributes(shape.model.label);
if (Object.keys(labelAttributes).length && !shape.model.removed && !shape.interpolation.position.outside) {
this._currentShapes.push(shape);
}
}
if (this._currentShapes.length) {
this._activeIdx = 0;
this._active = this._currentShapes[0].model;
}
else {
this._currentTracks[this._activeTrack].activeAttribute = null;
this._activeIdx = null;
this._active = null;
}
}
_switchHidden(value) {
for (let idx = 0; idx < this._currentTracks.length; idx++) {
this._currentTracks[idx].hidden = this._hideNonActive && value && (idx != this._activeTrack);
}
_attrIdByIdx(labelId, attrIdx) {
return Object.keys(window.cvat.labelsInfo.labelAttributes(labelId))[attrIdx];
}
_activate() {
if (this._activeAAM && this._active) {
let label = this._active.label;
let attrId = +this._attrIdByIdx(label, this._attrNumberByLabel[label].current);
let attrInfo = window.cvat.labelsInfo.attrInfo(attrId);
switchAAM() {
if (this._activeAAM) {
this.disableAAM();
let [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._active.activeAAM = {
shape: true,
attribute: attrId,
};
this.notify();
if (attrInfo.type === 'text' || attrInfo.type === 'number') {
this._active.aamAttributeFocus();
}
}
else {
this.enableAAM();
this.notify();
}
}
_deactivate() {
if (this._activeAAM && this._active) {
this._active.activeAAM = {
shape: false,
attribute: null
};
}
}
enableAAM() {
this._activeAAM = true;
this.notify();
this._switchHidden(true);
this._updateActiveAttribute(true);
this._updateActiveTrack(true);
_enable() {
if (window.cvat.mode === null) {
window.cvat.mode = 'aam';
this._shapeCollection.resetActive();
this._activeAAM = true;
this._updateCollection();
this.notify();
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;
disableAAM() {
this._activeAAM = false;
this._updateActiveAttribute(false);
this._updateActiveTrack(false);
this._switchHidden(false);
this.notify();
// Notify for remove aam UI
this.notify();
}
}
nextTrack(direction) {
if (!this._activeAAM || !this._currentTracks.length) return;
this._updateActiveAttribute(false);
let newActiveTrack = this._activeTrack + 1 * direction;
if (newActiveTrack < 0) {
newActiveTrack = this._currentTracks.length - 1;
switchAAMMode() {
if (this._activeAAM) {
this._disable();
}
else if (newActiveTrack >= this._currentTracks.length) {
newActiveTrack = 0;
else {
this._enable();
}
this._updateActiveTrack(false);
this._activeTrack = newActiveTrack;
this._updateActiveTrack(true);
this.nextAttribute(0);
}
moveShape(direction) {
if (!this._activeAAM || this._currentShapes.length < 2) {
return;
}
nextAttribute(direction) {
if (!this._activeAAM || !this._currentTracks.length) return;
let labelId = this._labelId();
let attributes = this._labelsInfo.labelAttributes(labelId);
let numOfAttributes = Object.keys(attributes).length;
let currentAttr = this._attributeByLabel[labelId];
if (numOfAttributes >= 2) {
currentAttr += 1 * direction;
if (currentAttr < 0) {
this._attributeByLabel[labelId] = numOfAttributes - 1;
}
else if (currentAttr >= numOfAttributes) {
this._attributeByLabel[labelId] = 0;
this._deactivate();
if (Math.sign(direction) > 0) {
// next
this._activeIdx ++;
if (this._activeIdx >= this._currentShapes.length) {
this._activeIdx = 0;
}
else {
this._attributeByLabel[labelId] = currentAttr;
}
else {
// prev
this._activeIdx --;
if (this._activeIdx < 0) {
this._activeIdx = this._currentShapes.length - 1;
}
}
this._updateActiveAttribute(true);
this.notify(); // notify for help update in view
}
this._active = this._currentShapes[this._activeIdx].model;
this._activate();
}
setupAttributeValue(attrValueIndex) {
if (!this._activeAAM || this._activeTrack === null) return;
let labelId = this._labelId();
let attrOrderIdx = this._attributeByLabel[labelId];
if (attrOrderIdx === null) return;
let attrId = this._attributeId();
let attrInfo = this._labelsInfo.attrInfo(attrId);
let values = attrInfo.values;
if (attrInfo.type === 'text' || attrInfo.type === 'number') return;
if (attrValueIndex >= values.length) {
if (attrInfo.type === 'checkbox' && attrValueIndex < 2) {
values.push(!values[0]);
}
else return;
}
if (values[0] === AAMUndefinedKeyword) {
if (attrValueIndex >= values.length - 1) return;
attrValueIndex ++;
moveAttr(direction) {
if (!this._activeAAM || !this._active) {
return;
}
this._currentTracks[this._activeTrack].recordAttribute(attrId, values[attrValueIndex]);
}
let curAttr = this._attrNumberByLabel[this._active.label];
onCollectionUpdate(collection) {
if (this._activeAAM) {
this._updateActiveTrack(false);
this._updateActiveAttribute(false);
this._switchHidden(false);
if (curAttr.end < 2) {
return;
}
this._currentTracks = [];
this._curFrame = collection._curFrame;
for (let track of collection.currentTracks) {
if (!track.trackModel.removed) {
this._currentTracks.push(track.trackModel);
if (Math.sign(direction) > 0) {
// next
curAttr.current ++;
if (curAttr.current >= curAttr.end) {
curAttr.current = 0;
}
}
for (let track of this._currentTracks) {
track.subscribe(this);
else {
// prev
curAttr.current --;
if (curAttr.current < 0) {
curAttr.current = curAttr.end - 1;
}
}
this._activate();
}
if (this._currentTracks.length) this._activeTrack = 0;
else this._activeTrack = null;
if (this._activeAAM) {
this._switchHidden(true);
this._updateActiveTrack(true);
this._updateActiveAttribute(true);
setupAttributeValue(key) {
if (!this._activeAAM || !this._active) {
return;
}
this.notify();
}
let label = this._active.label;
let frame = window.cvat.player.frames.current;
let attrId = this._attrIdByIdx(label, this._attrNumberByLabel[label].current);
let attrInfo = window.cvat.labelsInfo.attrInfo(attrId);
onTrackUpdate(track) {
if (track.model.removed) {
let idx = this._currentTracks.indexOf(track.model);
if (idx != -1) {
this._currentTracks.splice(idx, 1);
if (this._currentTracks.length) {
if (this._activeTrack != null) {
if (idx <= this._activeTrack && this._activeTrack > 0) this._activeTrack --;
}
}
else {
this._activeTrack = null;
}
if (key >= attrInfo.values.length) {
if (attrInfo.type === 'checkbox' && key < 2) {
this._active.updateAttribute(frame, attrId, !attrInfo.values[0]);
}
return;
}
}
set zoomBoxes(value) {
if (value != true && value != false) {
throw new Error(`Input value must be boolean, but ${value} found.`);
if (attrInfo.values[0] === AAMUndefinedKeyword) {
if (key >= attrInfo.values.length - 1) {
return;
}
key ++;
}
this._zoomBoxes = value;
}
this._active.updateAttribute(frame, attrId, attrInfo.values[key]);
}
set hideNonActive(value) {
if (value != true && value != false) {
throw new Error(`Input value must be boolean, but ${value} found.`);
}
this._hideNonActive = value;
onCollectionUpdate() {
if (this._activeAAM) {
this._switchHidden(value);
// No need deactivate active view because all listeners already unsubscribed
this._updateCollection();
this._activate();
}
}
set zoomMargin(value) {
value = Math.clamp(value, 0, 500);
this._zoomMargin = value;
}
get zoomMargin() {
return this._zoomMargin;
generateHelps() {
if (this._active) {
let label = this._active.label;
let attrId = +this._attrIdByIdx(label, this._attrNumberByLabel[label].current);
return [this._helps[attrId].title, this._helps[attrId].help, `${this._activeIdx + 1}/${this._currentShapes.length}`];
}
else {
return ['No Shapes Found', '', '0/0'];
}
}
get activeAAM() {
return this._activeAAM;
}
get helps() {
let title = '';
let helps = [];
let counter = '0/0';
let label = this._labelId();
if (label != null) {
counter = (this._activeTrack + 1) + '/' + this._currentTracks.length;
let attrOrderIdx = this._attributeByLabel[label];
if (attrOrderIdx != null) {
let attrId = this._attributeId();
title = this._titles[attrId];
helps = this._helps[attrId].slice();
}
}
return [title, helps, counter];
}
get activeAttribute() {
let labelId = this._labelId();
if (labelId === null) return [null];
let activeTrack = this._currentTracks[this._activeTrack];
let activeTrackId = activeTrack.id;
let attrOrderIdx = this._attributeByLabel[labelId];
if (attrOrderIdx === null) return [null];
let attrId = this._attributeId();
let attrInfo = this._labelsInfo.attrInfo(attrId);
return [activeTrackId, attrInfo.type, attrInfo.name];
}
get zoomBoxes() {
return this._zoomBoxes;
}
get hideNonActive() {
return this._hideNonActive;
set margin(value) {
this._margin = value;
}
}
class AAMController {
constructor(model) {
this._model = model;
constructor(aamModel) {
this._model = aamModel;
setupAAMShortkeys.call(this);
function setupAAMShortkeys() {
let switchAAMHandler = Logger.shortkeyLogDecorator(function() {
this._model.switchAAM();
this._model.switchAAMMode();
}.bind(this));
let nextAttributeHandler = Logger.shortkeyLogDecorator(function(e) {
this._model.nextAttribute(1);
this._model.moveAttr(1);
e.preventDefault();
}.bind(this));
let prevAttributeHandler = Logger.shortkeyLogDecorator(function(e) {
this._model.nextAttribute(-1);
this._model.moveAttr(-1);
e.preventDefault();
}.bind(this));
let nextTrackHandler = Logger.shortkeyLogDecorator(function(e) {
this._model.nextTrack(1);
let nextShapeHandler = Logger.shortkeyLogDecorator(function(e) {
this._model.moveShape(1);
e.preventDefault();
}.bind(this));
let prevTrackHandler = Logger.shortkeyLogDecorator(function(e) {
this._model.nextTrack(-1);
let prevShapeHandler = Logger.shortkeyLogDecorator(function(e) {
this._model.moveShape(-1);
e.preventDefault();
}.bind(this));
let selectAttributeHandler = Logger.shortkeyLogDecorator(function(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;
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);
}.bind(this));
let shortkeys = userConfig.shortkeys;
let shortkeys = window.cvat.config.shortkeys;
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_track"].value, nextTrackHandler, 'keydown');
Mousetrap.bind(shortkeys["aam_prev_track"].value, prevTrackHandler, 'keydown');
Mousetrap.bind(shortkeys["select_i_attribute"].value.split(','), selectAttributeHandler, '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');
}
}
zoomMargin(value) {
this._model.zoomMargin = value;
}
zoomBoxes(value) {
this._model.zoomBoxes = value;
}
hideNonActive(value) {
this._model.hideNonActive = value;
setMargin(value) {
this._model.margin = value;
}
}
class AAMView {
constructor(model, controller) {
this._controller = controller;
this._trackManagementMenu = $('#trackManagement');
constructor(aamModel, aamController) {
this._trackManagement = $('#trackManagement');
this._aamMenu = $('#aamMenu');
this._aamTitle = $('#aamTitle');
this._aamCounter = $('#aamCounter');
this._aamHelpContainer = $('#aamHelpContainer');
this._removeAnnotationButton = $('#removeAnnotationButton');
this._zoomBoxesBox = $('#zoomAAMBoxesBox');
this._hideNonActiveBox = $('#hideNonActiveBox');
this._zoomMargin = $('#aamZoomMargin');
this._controller = aamController;
this._zoomBoxesBox.prop('checked', model.zoomBoxes);
this._zoomBoxesBox.on('change', function(e) {
let value = e.target.checked;
this._controller.zoomBoxes(value);
}.bind(this));
this._zoomMargin.prop('checked', model.zoomMargin);
this._zoomMargin.on('change', function(e) {
this._zoomMargin.on('change', (e) => {
let value = +e.target.value;
this._controller.zoomMargin(value);
}.bind(this));
this._hideNonActiveBox.prop('checked', model.hideNonActive);
this._hideNonActiveBox.on('change', function(e) {
let value = e.target.checked;
this._controller.hideNonActive(value);
}.bind(this));
model.subscribe(this);
}
_blurAll(){
var tmp = document.createElement("input");
document.body.appendChild(tmp);
tmp.focus();
document.body.removeChild(tmp);
this._controller.setMargin(value);
}).trigger('change');
aamModel.subscribe(this);
}
onAAMUpdate(aam) {
if (aam.activeAAM && this._aamMenu.hasClass('hidden')) {
this._trackManagementMenu.addClass('hidden');
this._aamMenu.removeClass('hidden');
this._removeAnnotationButton.prop('disabled', true);
}
else if (!aam.activeAAM) {
this._blurAll();
window.getSelection().removeAllRanges();
this._aamMenu.addClass('hidden');
this._trackManagementMenu.removeClass('hidden');
this._removeAnnotationButton.prop('disabled', false);
return;
}
let [title, helps, counter] = aam.helps;
this._aamTitle.text(title);
this._aamCounter.text(counter);
this._aamHelpContainer.empty();
let table = $('<table></table>').css({
'width': '100%',
'text-align': 'left'
}).appendTo(this._aamHelpContainer);
while (helps.length) {
let row = $('<tr></tr>').appendTo(table);
let first = helps.shift();
let second = helps.shift();
row.append(`<td>${first}</td>`);
if (second) {
row.append(`<td>${second}</td>`);
if (aam.activeAAM) {
if (this._aamMenu.hasClass('hidden')) {
this._trackManagement.addClass('hidden');
this._aamMenu.removeClass('hidden');
}
}
for (let help of helps) {
let helpView = $(`<label> ${help} </label>`).addClass('regular');
helpView.appendTo(this._aamHelpContainer);
$('<br>').appendTo(this._aamHelpContainer);
}
let [activeTrackId, type, name] = aam.activeAttribute;
if (activeTrackId === null) return;
let [title, help, counter] = aam.generateHelps();
this._aamHelpContainer.empty();
this._aamCounter.text(counter);
this._aamTitle.text(title);
switch (type) {
case 'text': {
let text = $(`#${activeTrackId}_${name}_text`)[0];
text.focus();
text.select();
break;
}
case 'number': {
let number = $(`#${activeTrackId}_${name}_number`)[0];
number.focus();
number.select();
break;
for (let helpRow of help) {
$(`<label> ${helpRow} <label> <br>`).appendTo(this._aamHelpContainer);
}
}
default:
this._blurAll();
window.getSelection().removeAllRanges();
else {
if (this._trackManagement.hasClass('hidden')) {
this._aamMenu.addClass('hidden');
this._trackManagement.removeClass('hidden');
}
}
// blur on change text attribute to other or on exit from aam
blurAllElements();
}
}

@ -1,3 +1,9 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported confirm showMessage showOverlay dumpAnnotationRequest */
"use strict";
@ -5,6 +11,7 @@ Math.clamp = function(x, min, max) {
return Math.min(Math.max(x, min), max);
};
function confirm(message, onagree, ondisagree) {
let template = $('#confirmTemplate');
let confirmWindow = $(template.html()).css('display', 'block');
@ -26,6 +33,12 @@ function confirm(message, onagree, ondisagree) {
if (ondisagree) ondisagree();
});
disagreeConfirm.focus();
confirmWindow.on('keydown', (e) => {
e.stopPropagation();
});
function hideConfirm() {
agreeConfirm.off('click');
disagreeConfirm.off('click');
@ -44,10 +57,17 @@ function showMessage(message) {
messageText.text(message);
$('body').append(messageWindow);
messageWindow.on('keydown', (e) => {
e.stopPropagation();
});
okButton.on('click', function() {
okButton.off('click');
messageWindow.remove();
});
okButton.focus();
return messageWindow;
}
@ -70,6 +90,7 @@ function showOverlay(message) {
function dumpAnnotationRequest(dumpButton, taskID) {
dumpButton = $(dumpButton);
dumpButton.attr('disabled', true);
$.ajax({
@ -157,7 +178,7 @@ $.ajaxSetup({
$(document).ready(function(){
$('body').css({
width: window.screen.width * 0.97 + 'px',
height: window.screen.height * 0.97 + 'px'
width: window.screen.width * 0.95 + 'px',
height: window.screen.height * 0.95 + 'px'
});
});

@ -1,15 +1,14 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
"use strict";
String.prototype.normalize = function() {
let target = this;
target = target.charAt(0).toUpperCase() + target.substr(1);
target = target.replace(/_/g, " ");
return target;
};
String.prototype.toJSId = function() {
let target = this;
target = target.replace(/\W/g,'_');
return target;
};

@ -1,10 +1,16 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
// legacy syntax for IE support
var supportedPlatforms = ['Chrome', 'Firefox'];
var supportedPlatforms = ['Chrome'];
if (supportedPlatforms.indexOf(platform.name) == -1) {
try {
document.documentElement.innerHTML = "<center><h1> You browser detected as " + platform.name +
". This tool not supports it. Please use latest version Google Chrome or Mozilla Firefox.</h1></center>";
". This tool not supports it. Please use latest version of Google Chrome.</h1></center>";
window.stop();
}
catch (err) {

@ -1,213 +0,0 @@
/* exported CollectionController */
"use strict";
class CollectionController {
constructor(collectionModel) {
this._collectionModel = collectionModel;
this._playerScale = 1;
this._moveMode = false;
this._resizeMode = false;
this._drawMode = false;
this._pasteMode = false;
this._AAM = false;
this._lastMousePos = {
x: 0,
y: 0
};
this._collectionHash = this._collectionModel.getHash();
setupInteract.call(this);
setupCollectionShortkeys.call(this);
function setupCollectionShortkeys() {
let deleteHandler = Logger.shortkeyLogDecorator(function(e) {
if (this._AAM) return;
this._collectionModel.removeactivetrack(e.shiftKey);
}.bind(this));
let lockHandler = Logger.shortkeyLogDecorator(function() {
if (this._AAM) return;
this._collectionModel.switchLockForActive();
}.bind(this));
let lockAllHandler = Logger.shortkeyLogDecorator(function() {
if (this._AAM) return;
this._collectionModel.switchLockForAll();
}.bind(this));
let occludedHandler = Logger.shortkeyLogDecorator(function() {
this._collectionModel.switchOccludedForActive();
}.bind(this));
let changeColorHandler = Logger.shortkeyLogDecorator(function() {
this._collectionModel.switchColorForActive();
}.bind(this));
let changeLabelHandler = Logger.shortkeyLogDecorator(function(e) {
let labelIdx = e.keyCode - '1'.charCodeAt(0);
this._collectionModel.reinitializeActive(labelIdx);
let fakeKeyState = {
ctrl: false,
shift: false,
alt: false
};
if (!this._AAM) this._collectionModel.onmousemove(this._lastMousePos.x, this._lastMousePos.y, fakeKeyState);
}.bind(this));
let shortkeys = userConfig.shortkeys;
Mousetrap.bind(shortkeys["delete_track"].value.split(','), deleteHandler, 'keydown');
Mousetrap.bind(shortkeys["switch_lock_property"].value, lockHandler, 'keyup');
Mousetrap.bind(shortkeys["switch_all_lock_property"].value, lockAllHandler, 'keydown');
Mousetrap.bind(shortkeys["switch_occluded_property"].value.split(','), occludedHandler, 'keydown');
Mousetrap.bind(shortkeys["change_shape_color"].value, changeColorHandler, 'keydown');
Mousetrap.bind(shortkeys["change_track_label"].value.split(','), changeLabelHandler, 'keydown');
}
function setupInteract() {
interact('.highlightedShape').draggable({
onstart: function() {
this._moveMode = true;
}.bind(this),
onmove: function(event) {
let scale = this._playerScale;
let target = $(event.target);
if (this._drawMode || !target.hasClass('highlightedShape') || event.shiftKey) {
return;
}
target.attr('x', +target.attr('x') + event.dx/scale);
target.attr('y', +target.attr('y') + event.dy/scale);
target.trigger('drag', scale);
}.bind(this),
onend: function () {
this._moveMode = false;
}.bind(this)
}).resizable({
margin: 8,
edges: { left: true, right: true, bottom: true, top: true },
restrict: {
restriction: $('#frameContent')[0]
},
onstart: function() {
this._resizeMode = true;
}.bind(this),
onmove: function(event) {
if (this._drawMode) return;
let target = $(event.target);
let scale = this._playerScale;
if (this._drawMode || !target.hasClass('highlightedShape') || event.shiftKey) {
return;
}
let newX = +target.attr('x') + event.deltaRect.left / scale;
let newY = +target.attr('y') + event.deltaRect.top / scale;
let newHeight = +target.attr('height') + event.deltaRect.height / scale;
let newWidth = +target.attr('width') + event.deltaRect.width / scale;
if (newHeight < 1) newHeight = 1;
if (newWidth < 1) newWidth = 1;
target.attr('x', newX );
target.attr('y', newY );
target.attr('height', newHeight );
target.attr('width', newWidth);
target.trigger('resize', scale);
}.bind(this),
onend: function () {
this._resizeMode = false;
}.bind(this)
});
} // end of setupInteract
}
setShowAllInterTracks(value) {
this._collectionModel.allInterTracks = value;
}
setHiddenForAll(value) {
this._collectionModel.setHiddenForAll(value);
}
setHiddenLabelForAll(value) {
this._collectionModel.setHiddenLabelForAll(value);
}
onchangeframe(newframe) {
this._collectionModel.onchangeframe(newframe);
if (this._AAM) return;
let fakeKeyState = {
ctrl: false,
shift: false,
alt: false
};
this._collectionModel.onmousemove(this._lastMousePos.x, this._lastMousePos.y, fakeKeyState);
}
updateFrame() {
this._collectionModel.updateFrame();
}
resetactivetrack(e) {
if (this._moveMode || this._resizeMode || this._AAM || e.ctrlKey) return;
this._collectionModel.resetactivetrack();
}
setactivetrack(id, e) {
if (this._pasteMode || this._AAM || e.ctrlKey) return;
this._collectionModel.setactivetrack(id);
}
getmodifierkeysstate(e) {
return {
ctrl: e.ctrlKey,
shift: e.shiftKey,
alt: e.altKey,
};
}
onmousemove(e) {
let pos = translateSVGPos($('#frameContent')[0], e.clientX, e.clientY, this._playerScale);
this._lastMousePos = {
x: pos.x,
y: pos.y
};
if (this._moveMode || this._resizeMode || this._drawMode || this._pasteMode || this._AAM) return;
let modKeysState = this.getmodifierkeysstate(e);
this._collectionModel.onmousemove(pos.x, pos.y, modKeysState);
}
onDrawerUpdate(drawer) {
this._drawMode = drawer.drawMode;
if (this._drawMode) this._collectionModel.resetactivetrack();
}
onBufferUpdate(buffer) {
this._pasteMode = buffer.pasteMode;
if (this._pasteMode) {
this._collectionModel.resetactivetrack();
}
}
onAAMUpdate(aam) {
if (aam.activeAAM && !this._AAM) {
this._collectionModel.resetactivetrack();
interact('.highlightedShape').draggable(false);
interact('.highlightedShape').resizable(false);
}
else if (this._AAM && !aam.activeAAM) {
interact('.highlightedShape').draggable(true);
interact('.highlightedShape').resizable(true);
}
this._AAM = aam.activeAAM;
}
set playerScale(value) {
this._playerScale = value;
}
hasUnsavedChanges() {
return this._collectionModel.getHash() !== this._collectionHash;
}
updateHash() {
this._collectionHash = this._collectionModel.getHash();
}
}

@ -1,437 +0,0 @@
/* exported CollectionModel */
"use strict";
class CollectionModel extends Listener {
constructor(labelsInfo, job, trackFilterModel) {
super('onCollectionUpdate', getState);
this._labelsInfo = labelsInfo;
this._curFrame = null;
this._frameChanged = false;
this._stopFrame = job.stop;
this._startFrame = job.start;
this._trackCounter = 0;
this._allTracks = [];
this._annotationTracks = new Object();
this._interpolationTracks = new Object();
this._currentTracks = [];
this._activeTrack = null;
this._drawing = false;
this._allInterTracks = false;
this._trackFilter = trackFilterModel;
this._trackFilter._updateCollection = () => this.update();
this._colorIndex = 0;
this._colorSets = {
background: ["#FFFFCC", "#FFFF66", "#FFCC66", "#FF9900", "#FF6633", "#FF6666", "#FF9999",
"#FF6699", "#27EBF9", "#FF99CC", "#FF99FF", "#CC66FF", "#CC99FF", "#16E532",
"#6666FF", "#0099FF", "#66CCCC", "#99FFFF", "#99FFCC", "#66FF99", "#CCFF99"],
border: ["#FFFF66", "#FFFF00", "#FFCC00", "#FF6600", "#FF3300", "#CC0033", "#FF3333",
"#FF0066", "#4EF0FC", "#CC0066", "#FF00FF", "#9900CC", "#9933FF", "#02F423",
"#3300CC", "#0033FF", "#006666", "#00CCCC", "#00FFCC", "#00FF66", "#66CC00"],
length: 21
};
let self = this;
function getState() {
return self;
}
}
_interpolate(tracks, frame) {
let result = [];
for (let trackId in tracks) {
let track = tracks[trackId];
if (track.removed) continue;
let interpolation = track.interpolate(frame);
let outsided = interpolation.position.outsided;
/* !outsided is provide the adding all tracks with non-outsided box
* track.isKeyFrame(frame) is provide the adding tracks if frame is keyframe (needed in cases when track become outsided on this frame)
* this._allInterTracks is provide the adding any tracks (in mode when right panel show all tracks) */
if (!outsided || track.isKeyFrame(frame) || this._allInterTracks) {
result.push({
trackModel: track,
interpolation: interpolation
});
}
}
return result;
}
collectStatistic() {
let statByLabel = new Object();
let labels = this._labelsInfo.labels();
for (let labelId in labels) {
let labelName =labels[labelId];
statByLabel[labelName] = {
tracks: 0,
manuallyShapes: 0,
interpolatedShapes: 0
};
}
for (let track of this._allTracks) {
if (!track.removed) {
track.getStatToObject(statByLabel);
}
}
let totalTracks = 0;
let totalManually = 0;
let totalInterpolated = 0;
for (let key in statByLabel) {
totalTracks += statByLabel[key].tracks;
totalManually += statByLabel[key].manuallyShapes;
totalInterpolated += statByLabel[key].interpolatedShapes;
}
Object.defineProperty(statByLabel, 'totalObjectCount', {
enumerable: false,
value: {
tracks: totalTracks,
manuallyShapes: totalManually,
interpolatedShapes: totalInterpolated
}
});
return statByLabel;
}
removeTracks() {
for (let i = 0; i < this._allTracks.length; i ++) {
this._allTracks[i].remove(true);
}
this._allTracks = [];
this._annotationTracks = new Object();
this._interpolationTracks = new Object();
this._currentTracks = [];
this._activeTrack = null;
this._trackCounter = 0;
this.notify();
}
importTracks(data) {
let tracks = [];
for (let box of data.boxes) {
let box0 = [box.xtl, box.ytl, box.xbr, box.ybr, box.frame, false, box.occluded];
let box1 = box0.slice();
box1[4] += 1; // next frame
box1[5] = true; // outside property
let attributes = [];
for (let attr of box.attributes) {
attributes.push([attr.id, box.frame, attr.value]);
}
tracks.push({
label: box.label_id,
boxes: [box0, box1],
attributes: attributes
});
}
for (let track of data.tracks) {
let attributes = [];
for (let attr of track.attributes) {
attributes.push([attr.id, 0, attr.value]);
}
let boxes = [];
for (let box of track.boxes) {
boxes.push([box.xtl, box.ytl, box.xbr, box.ybr, box.frame,
box.outside, box.occluded]);
for (let attr of box.attributes) {
attributes.push([attr.id, box.frame, attr.value]);
}
}
tracks.push({
label: track.label_id,
boxes: boxes,
attributes: attributes
});
}
for (let i = 0; i < tracks.length; i ++) {
if (!tracks[i].boxes.length) continue; // Remove saved wrong tracks with empty path
this.add(tracks[i]);
}
}
exportTracks() {
let response = {"boxes": [], "tracks": []};
for (let i = 0; i < this._allTracks.length; i ++ ) {
let track = this._allTracks[i];
// !Number.isInteger(track._firstFrame) need for remove fully outsided tracks. First frame for such tracks is undefined.
if (track.removed || !Number.isInteger(track._firstFrame)) continue;
if (track.trackType == "annotation") {
response["boxes"].push(track.export());
} else {
response["tracks"].push(track.export());
}
}
return JSON.stringify(response);
}
add(data) {
let colors = this.getColors();
let trackModel = new TrackModel('box', data, this._trackCounter,
this._labelsInfo, this._stopFrame, this._startFrame, colors);
let trackType = trackModel.trackType;
if (trackType === 'interpolation') {
this._interpolationTracks[this._trackCounter] = trackModel;
}
else {
let firstFrame = trackModel.firstFrame;
if (! (firstFrame in this._annotationTracks)) {
this._annotationTracks[firstFrame] = new Object();
}
this._annotationTracks[firstFrame][this._trackCounter] = trackModel;
}
this._allTracks.push(trackModel);
this._trackCounter ++;
}
createFromPos(pos, label, type) {
let boxes = [];
let current = [pos.xtl, pos.ytl, pos.xbr, pos.ybr, this._curFrame, 0, 0];
boxes.push(current);
if (type === 'annotation') {
let next = [pos.xtl, pos.ytl, pos.xbr, pos.ybr, this._curFrame + 1, 1, 0];
boxes.push(next);
}
this.add({
attributes: [],
boxes: boxes,
label: label
});
this.onchangeframe(this._curFrame);
}
update() {
if (this._curFrame != null) {
this.onchangeframe(this._curFrame);
}
}
onchangeframe(newframe) {
for (let i = 0; i < this._currentTracks.length; i ++ ) {
this._currentTracks[i].trackModel.active = false;
this._currentTracks[i].trackModel.unsubscribeAll();
}
this._currentTracks = [];
this._activeTrack = null;
this._frameChanged = this._curFrame != newframe;
this._curFrame = newframe;
let annotationTracks = [];
let interpolationTracks = [];
if (newframe in this._annotationTracks) {
for (let trackId in this._annotationTracks[newframe]) {
this._annotationTracks[newframe][trackId].curFrame = newframe;
}
annotationTracks = this._interpolate(this._annotationTracks[newframe], newframe);
}
for (let trackId in this._interpolationTracks) {
this._interpolationTracks[trackId].curFrame = newframe;
}
interpolationTracks = this._interpolate(this._interpolationTracks, newframe);
this._currentTracks = annotationTracks.concat(interpolationTracks);
this._trackFilter.filter(this._currentTracks);
this.notify();
}
findFilterFrame(direction) {
if (direction != 1 && direction != -1) {
throw Error('Bad direction value: ', direction);
}
let frame = this._curFrame;
while (frame <= this._stopFrame && frame >= this._startFrame) {
frame += direction;
if (frame in this._annotationTracks) {
let annotationTracks = this._interpolate(this._annotationTracks[frame], frame);
let filteredIndexes = this._trackFilter.filter(annotationTracks);
if (filteredIndexes.length) return frame;
}
let interpolationTracks = this._interpolate(this._interpolationTracks, frame);
let filteredIndexes = this._trackFilter.filter(interpolationTracks);
if (filteredIndexes.length) return frame;
}
return null;
}
updateFrame() {
if (this._curFrame != null) {
this.onchangeframe(this._curFrame);
}
}
switchLockForActive() {
if (this._activeTrack != null) {
let value = this._allTracks[this._activeTrack].lock;
this._allTracks[this._activeTrack].lock = !value;
Logger.addEvent(Logger.EventType.lockObject, {value: !value, mode: 'single object'});
}
}
switchLockForAll() {
let value = true;
for (let track of this._currentTracks) {
value = value && track.trackModel.lock;
if (!value) break;
}
for (let track of this._currentTracks) {
track.trackModel.lock = !value;
}
if (this._currentTracks.length > 0) {
Logger.addEvent(Logger.EventType.lockObject, {value: !value, mode: 'multy object'});
}
}
switchOccludedForActive() {
if (this._activeTrack != null) {
this._allTracks[this._activeTrack].occluded ^= true;
}
}
switchColorForActive() {
if (this._activeTrack != null) {
this._allTracks[this._activeTrack].colors = this.getColors();
}
}
setHiddenForAll(value) {
for (let track of this._allTracks) {
track.hidden = value;
}
}
setHiddenLabelForAll(value) {
for (let track of this._allTracks) {
track.hiddenLabel = value;
}
}
reinitializeActive(newLabelIdx) {
let keys = Object.keys(this._labelsInfo.labels());
if (this._activeTrack != null && newLabelIdx < keys.length) {
this._allTracks[this._activeTrack].reinitialize(keys[newLabelIdx]);
this.updateFrame();
}
}
setactivetrack(trackID) {
if (trackID != this._activeTrack) {
this.resetactivetrack();
this._activeTrack = trackID;
this._allTracks[this._activeTrack].active = true;
}
}
resetactivetrack() {
if (this._activeTrack != null) {
this._allTracks[this._activeTrack].active = false;
this._activeTrack = null;
}
}
removeactivetrack(force) {
if (this._activeTrack != null) {
this._allTracks[this._activeTrack].remove(force);
}
}
onmousemove(x, y, modKeysStates) {
if (this._activeTrack != null && modKeysStates.ctrl) return;
let activeTrack = null;
let minArea = Number.MAX_SAFE_INTEGER;
this._currentTracks.forEach((item) => {
if (item.trackModel.removed) return;
let pos = item.trackModel._shape.interpolatePosition(this._curFrame, item.trackModel.firstFrame);
let type = item.trackModel.shapeType;
if (pos.outsided || item.trackModel.hidden) return;
if (TrackModel.ShapeContain(pos, x, y, type)) {
let area = TrackModel.ShapeArea(pos, type);
if (area < minArea || this._allTracks[activeTrack].lock) {
if (activeTrack != null) {
if (!this._allTracks[activeTrack].lock && this._allTracks[item.trackModel.id].lock) {
return;
}
}
minArea = area;
activeTrack = item.trackModel.id;
}
}
});
if (activeTrack != null) {
this.setactivetrack(activeTrack);
}
else this.resetactivetrack();
return activeTrack;
}
getColors() {
let oldColorIndex = this._colorIndex;
this._colorIndex ++;
if (this._colorIndex >= this._colorSets.length) {
this._colorIndex = 0;
}
var colors = {
background: this._colorSets.background[oldColorIndex],
border: this._colorSets.border[oldColorIndex]
};
return colors;
}
set allInterTracks(value) {
if (value != true && value != false) {
throw new Error(`Value must be boolean, but ${typeof(value)} extracted.`);
}
this._allInterTracks = value;
this.update();
}
get currentTracks() {
return this._currentTracks;
}
get activeTrack() {
if (this._activeTrack != null) {
return this._allTracks[this._activeTrack];
}
else return null;
}
get allTracks() {
return this._allTracks;
}
get frameChanged() {
return this._frameChanged;
}
get allInterTracks() {
return this._allInterTracks;
}
getHash() {
return objectHash.sha1(this.exportTracks());
}
}

@ -1,140 +0,0 @@
/*exported CollectionView */
"use strict";
class CollectionView {
constructor(collectionController, collectionModel, playerModel, labelsInfo) {
this._frameContent = $('#frameContent');
this._createTrackButton = $('#createTrackButton');
this._mergeTracksButton = $('#mergeTracksButton');
this._labelSelect = $('#labelSelect');
this._trackTypeSelect = $('#trackTypeSelect');
this._hideBoxesCheck = $('#hideBoxesBox');
this._hideLabelsCheck = $('#hideLabelsBox');
this._showAllInterBox = $('#showAllInterBox');
this._uiContent = $('#uiContent');
this._collectionController = collectionController;
this._playerScale = 1;
this._revPlayerScale = 1;
this._tracks = [];
this._labelsInfo = labelsInfo;
this._playerModel = playerModel;
this._hideLabelsCheck.prop('checked', true);
this._showAllInterBox.prop('checked', collectionModel.allInterTracks);
playerModel.subscribe(this);
collectionModel.subscribe(this);
this._frameContent.on('mousemove', collectionController.onmousemove.bind(collectionController));
this._hideBoxesCheck.on('change', this.hideBoxes.bind(this));
this._hideLabelsCheck.on('change', this.hideLabels.bind(this));
this._showAllInterBox.on('change', (e) => collectionController.setShowAllInterTracks(e.target.checked));
}
hideBoxes(e) {
let value = e.target.checked;
this._collectionController.setHiddenForAll(value);
}
hideLabels(e) {
let value = e.target.checked;
this._collectionController.setHiddenLabelForAll(value);
}
onPlayerUpdate(player) {
let geometry = player.geometry;
let frames = player.frames;
if (this._playerScale != geometry.scale) {
this._playerScale = geometry.scale;
this._revPlayerScale = 1 / geometry.scale;
for (let i = 0; i < this._tracks.length; i ++ ) {
this._tracks[i].view.revscale = this._revPlayerScale;
this._tracks[i].view.updateViewGeometry();
}
this._collectionController.playerScale = this._playerScale;
}
if (frames.previous != frames.current) {
this._collectionController.onchangeframe(frames.current);
}
}
onCollectionUpdate(collection) {
let offset = this._uiContent.prop('scrollTop');
let numOfTracksBefore = this._tracks.length;
for (let i = 0; i < this._tracks.length; i ++) {
this._tracks[i].view.removeView();
}
this._tracks = [];
this._frameContent.find('defs').remove();
this._frameContent.find('rect.outsideRect').remove();
let newcollection = collection.currentTracks;
newcollection.sort(trackComparator);
for (let i = 0; i < newcollection.length; i ++ ) {
let interpolation = newcollection[i].interpolation;
let trackModel = newcollection[i].trackModel;
let colors = trackModel.colors;
let trackController = new TrackController(trackModel);
let trackView = new TrackView(trackController, trackModel, interpolation, this._labelsInfo, colors);
trackModel.notify();
trackView.revscale = this._revPlayerScale;
trackView.updateViewGeometry();
trackView.onoverUI = (id, e) => this._collectionController.setactivetrack(id, e);
trackView.onoutUI = (e) => this._collectionController.resetactivetrack(e);
trackView.onshift = (frame) => this._playerModel.shift(frame, true);
trackView.onchangelabel = function(trackModel, newLabelId) {
trackModel.reinitialize(newLabelId);
this._collectionController.updateFrame();
}.bind(this);
this._tracks.push({
model: trackModel,
controller: trackController,
view: trackView
});
}
if (numOfTracksBefore === this._tracks.length && !collection.frameChanged) {
this._uiContent.prop('scrollTop', offset);
}
function trackComparator(a,b) {
return b.trackModel.id - a.trackModel.id;
}
}
onstartdraw() {
this._createTrackButton.prop('disabled', true);
this._mergeTracksButton.prop('disabled', true);
this._labelSelect.prop('disabled', true);
this._trackTypeSelect.prop('disabled', true);
this._frameContent.css('cursor', 'crosshair');
}
onDrawerUpdate(drawer) {
if (drawer.drawMode && !drawer.clicks.length) {
this.lockChange(true);
}
else if (!drawer.drawMode) {
this.lockChange(false);
}
}
lockChange(value) {
if (value) {
for (let track of this._tracks) {
track.view._shape.removeClass('changeable');
}
}
else {
for (let track of this._tracks) {
track.view._shape.addClass('changeable');
}
}
}
}

@ -1,285 +0,0 @@
/* exported DrawerModel DrawerController DrawerView */
"use strict";
class DrawerModel extends Listener {
constructor(trackCollection) {
super('onDrawerUpdate', getState);
this._drawMode = false;
this._drawShape = 'rect';
this._collection = trackCollection;
this._trackType = null;
this._label = null;
this._clicks = [];
this._drawObjectEvent = null;
this._pasteMode = false;
this._mergeMode = false;
this._AAM = false;
let self = this;
function getState() {
return self;
}
}
switchDraw() {
if (this._drawMode) this.endDraw();
else this.startDraw();
this.notify();
}
endDraw() {
this._drawMode = false;
this._clicks = [];
this._trackType = null;
this._label = null;
this._drawObjectEvent = null;
}
startDraw() {
if (this._pasteMode || this._mergeMode || this._AAM) return;
this._drawMode = true;
this._drawObjectEvent = Logger.addContinuedEvent(Logger.EventType.drawObject);
}
addPoint(pos) {
if (!this._drawMode) return;
if (this._drawShape === 'rect') {
for (let i = 0; i < this._clicks.length; i ++) {
if (!this._clicks[i].fixed) {
this._clicks.splice(i,1);
}
}
if (this._clicks.length) {
let diffX = Math.abs(this._clicks[0].x - pos.x);
let diffY = Math.abs(this._clicks[0].y - pos.y);
if (diffX >= MIN_BOX_SIZE && diffY >= MIN_BOX_SIZE) {
this._clicks.push(pos);
if (pos.fixed) {
let rectPos = {
xtl: Math.min(this._clicks[0].x, this._clicks[1].x),
ytl: Math.min(this._clicks[0].y, this._clicks[1].y),
xbr: Math.min(this._clicks[0].x, this._clicks[1].x) + Math.abs(this._clicks[0].x - this._clicks[1].x),
ybr: Math.min(this._clicks[0].y, this._clicks[1].y) + Math.abs(this._clicks[0].y - this._clicks[1].y)
};
this._collection.createFromPos(rectPos, this._label, this._trackType);
Logger.addEvent(Logger.EventType.addObject, {count: 1});
this._drawObjectEvent.close();
this.endDraw();
}
}
}
else {
this._clicks.push(pos);
}
this.notify();
}
else throw new Error('Unknown shape type when draw');
}
get drawMode() {
return this._drawMode;
}
get clicks() {
return this._clicks;
}
get drawShape() {
return this._drawShape;
}
set label(value) {
this._label = value;
}
set trackType(value) {
this._trackType = value;
}
onBufferUpdate(buffer) {
this._pasteMode = buffer.pasteMode;
}
onMergerUpdate(merger) {
this._mergeMode = merger.mergeMode;
}
onAAMUpdate(aam) {
this._AAM = aam.activeAAM;
if (this._AAM && this._drawMode) {
this.endDraw();
this.notify();
}
}
}
class DrawerController {
constructor(drawerModel) {
this._model = drawerModel;
setupDrawerShortkeys.call(this);
function setupDrawerShortkeys() {
let drawHandler = Logger.shortkeyLogDecorator(function() {
this.onDrawPressed();
}.bind(this));
let shortkeys = userConfig.shortkeys;
Mousetrap.bind(shortkeys["switch_draw_mode"].value, drawHandler, 'keydown');
}
}
onDrawPressed() {
this._model.switchDraw();
}
onAddPoint(pos) {
this._model.addPoint(pos);
}
}
class DrawerView {
constructor(drawerController) {
this._controller = drawerController;
this._drawButton = $('#createTrackButton');
this._drawLabelSelect = $('#labelSelect');
this._drawTrackTypeSelect = $('#trackTypeSelect');
this._frameContent = $('#frameContent');
this._drawMode = false;
this._drawShapeType = null;
this._aim = {
aimX: null,
aimY: null
};
this._drawShape = null;
this._playerScale = 1;
this._drawButton.on('click', () => this._controller.onDrawPressed.call(this._controller));
}
onDrawerUpdate(drawer) {
if (drawer.drawMode) {
if (!this._drawMode) {
this._drawButton.text('Cancel Draw (N)');
this._frameContent.on('mousemove.drawer', mousemoveHandler.bind(this));
this._frameContent.on('mouseleave.drawer', mouseleaveHandler.bind(this));
this._frameContent.on('mousedown.drawer', mousedownHandler.bind(this));
this._drawMode = true;
this._drawShapeType = drawer.drawShape;
if (this._drawShapeType == 'rect') {
this._drawShape = $(document.createElementNS('http://www.w3.org/2000/svg', 'rect')).attr({
'stroke': '#ffffff'
}).addClass('shape').css({
'stroke-width': 2 / this._playerScale,
}).appendTo(this._frameContent);
}
this._aim = {
aimX: $(document.createElementNS('http://www.w3.org/2000/svg', 'line')).attr({
'stroke': 'red',
}).css({
'stroke-width': 2 / this._playerScale
}).appendTo(this._frameContent)
,
aimY: $(document.createElementNS('http://www.w3.org/2000/svg', 'line')).attr({
'stroke': 'red',
}).css({
'stroke-width': 2 / this._playerScale
}).appendTo(this._frameContent)
};
drawer.label = +this._drawLabelSelect.prop('value');
drawer.trackType = this._drawTrackTypeSelect.prop('value');
}
else {
let clicks = drawer.clicks;
if (this._drawShapeType == 'rect' && clicks.length == 2) {
this._drawShape.attr({
x: Math.min(clicks[0].x, clicks[1].x),
y: Math.min(clicks[0].y, clicks[1].y),
width: Math.abs(clicks[0].x - clicks[1].x),
height: Math.abs(clicks[0].y - clicks[1].y)
});
}
}
}
else {
if (this._drawMode) {
this._drawButton.text('Create Track');
this._frameContent.off('mousemove.drawer');
this._frameContent.off('mouseleave.drawer');
this._frameContent.off('mousedown.drawer');
this._drawMode = false;
this._drawShapeType = null;
this._drawShape.remove();
this._drawShape = null;
this._aim.aimX.remove();
this._aim.aimX = null;
this._aim.aimY.remove();
this._aim.aimY = null;
}
}
this._drawLabelSelect.prop('disabled', this._drawMode);
this._drawTrackTypeSelect.prop('disabled', this._drawMode);
function mousemoveHandler(e) {
let pos = translateSVGPos(this._frameContent['0'], e.clientX, e.clientY, this._playerScale);
pos.fixed = false;
this._controller.onAddPoint(pos);
this._aim.aimX.attr({
x1: 0,
y1: pos.y,
x2: this._frameContent.css('width'),
y2: pos.y
}).css('display', '');
this._aim.aimY.attr({
x1: pos.x,
y1: 0,
x2: pos.x,
y2: this._frameContent.css('height')
}).css('display', '');
}
function mouseleaveHandler() {
if (this._drawMode) {
this._aim.aimX.css('display', 'none');
this._aim.aimY.css('display', 'none');
}
}
function mousedownHandler(e) {
if (e.shiftKey) return;
let pos = translateSVGPos(this._frameContent['0'], e.clientX, e.clientY, this._playerScale);
pos.fixed = true;
this._controller.onAddPoint(pos);
}
}
onPlayerUpdate(player) {
this._playerScale = player.geometry.scale;
if (this._drawMode) {
this._aim.aimX.css({
'stroke-width': 2 / this._playerScale
});
this._aim.aimY.css({
'stroke-width': 2 / this._playerScale
});
this._drawShape.css({
'stroke-width': 2 / this._playerScale
});
}
}
}

@ -0,0 +1,207 @@
/* exported HistoryModel HistoryController HistoryView */
"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,3 +1,9 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported LabelsInfo */
"use strict";
@ -5,6 +11,7 @@ class LabelsInfo {
constructor(job) {
this._labels = new Object;
this._attributes = new Object;
this._colorIdxs = new Object;
for (let labelKey in job.labels) {
let label = {
@ -18,6 +25,7 @@ class LabelsInfo {
}
this._labels[labelKey] = label;
this._colorIdxs[labelKey] = +labelKey;
}
function parseAttributeRow(attrRow) {
@ -37,6 +45,16 @@ class LabelsInfo {
}
}
labelColorIdx(labelId) {
return this._colorIdxs[labelId];
}
updateLabelColorIdx(labelId) {
if (labelId in this._colorIdxs) {
this._colorIdxs[labelId] += 1;
}
}
normalize() {
let labels = "";
for (let labelId in this._labels) {
@ -117,8 +135,6 @@ class LabelsInfo {
return null;
}
strToValues(type, string) {
switch (type) {
case 'checkbox':
@ -126,7 +142,7 @@ class LabelsInfo {
case 'text':
return [string];
default:
return string.split(',');
return string.toString().split(',');
}
}
}

@ -1,3 +1,9 @@
/*
* Copyright (C) 2018 Intel Corporation
*
* SPDX-License-Identifier: MIT
*/
/* exported Listener */
"use strict";

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save