Comparar commits
169 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 550e8c106e | |||
| 5e77526ee4 | |||
| 62bbdb3a8c | |||
| 8037ef009a | |||
| 81e0f4a702 | |||
| ec62771473 | |||
| 8e2a69efae | |||
| a6843741b4 | |||
| d3d36518df | |||
| 3fd153bf4f | |||
| 2f2ed76422 | |||
| 02a0a4c5be | |||
| c170b37f70 | |||
| a24ba70f16 | |||
| 7a926d5584 | |||
| cac350a0ce | |||
| 9209735a69 | |||
| 89587a7ff3 | |||
| f3711b5b91 | |||
| 2ea28612b3 | |||
| 8eaf547a4e | |||
| af64c90bba | |||
| ecacfbe9cf | |||
| 8a3b2dcb3f | |||
| 73f33c73b1 | |||
| facba46f50 | |||
| 5e2476a3f3 | |||
| 27f482ac7f | |||
| 351146ba25 | |||
| e3cf827df0 | |||
| b384a37528 | |||
| c7af947adc | |||
| ee5dc8ce57 | |||
| 12e98e6ef4 | |||
| 029df76081 | |||
| 273094c481 | |||
| ec818e1c23 | |||
| ec6057c4c9 | |||
| 40e9798e56 | |||
| e206244fb6 | |||
| 569a7cfd95 | |||
| 85a45e9be2 | |||
| 140c217dbf | |||
| 08d59f9471 | |||
| b590101a3e | |||
| cf4bc28e69 | |||
| feef3c01eb | |||
| a4e187e18d | |||
| 7debab9db5 | |||
| 0bedec3571 | |||
| 3cd6755121 | |||
| 4747a9885c | |||
| e4b10b5349 | |||
| 1b78605934 | |||
| cbce7a4b03 | |||
| 8604d63442 | |||
| 9e68d58812 | |||
| 5caad8d5f0 | |||
| 22e0045746 | |||
| 27542799b0 | |||
| 528fa85685 | |||
| 29502b3f52 | |||
| 2ca905ed82 | |||
| 8475d0a1d1 | |||
| fc71d7005e | |||
| 6d683bc9a8 | |||
| 3f7b7962da | |||
| 116697a3a4 | |||
| fb3a122436 | |||
| 3adce4fabd | |||
| 44a838135a | |||
| ebc2aed768 | |||
| a45e2112ce | |||
| 76a6df0faa | |||
| 6ab9079810 | |||
| a0f3e24349 | |||
| a7b69c1b59 | |||
| 0f6f8e113c | |||
| 072790d870 | |||
| ed06a74f1a | |||
| e1a7ded76b | |||
| 88ef64f4a4 | |||
| 07ffb9f85c | |||
| 30d3236705 | |||
| c2bb03eb20 | |||
| f913bd26e7 | |||
| c3f61eaa00 | |||
| 7443691a29 | |||
| 84b0cfdb34 | |||
| 8dae870773 | |||
| 7cd9da0b90 | |||
| 0e999d665c | |||
| fdc90dd25c | |||
| a0b1a01d4c | |||
| 162362086c | |||
| 231b9bfc5e | |||
| 898fa97d1a | |||
| 126f6c8bae | |||
| 9ea4f7502e | |||
| 9080e8ad4a | |||
| ac5f93da22 | |||
| ecff49ece6 | |||
| 66bdf236a8 | |||
| 81de28502d | |||
| 8030a33a50 | |||
| aa5f88500d | |||
| ce452a6fd6 | |||
| 67a0cf4520 | |||
| 66af16b718 | |||
| 36ba839185 | |||
| d57a02c7c9 | |||
| 75c33022ae | |||
| 654a51ea84 | |||
| 9aca260949 | |||
| 52bde64875 | |||
| 0ea755df16 | |||
| 70d717ce82 | |||
| 56367012dd | |||
| cc7ec33b88 | |||
| e4f304b308 | |||
| 2408e15b96 | |||
| 0f6883deb3 | |||
| b85b03678c | |||
| 28db72f787 | |||
| f61e455478 | |||
| e7e8bd8c15 | |||
| 5c04633666 | |||
| aa2d6ae244 | |||
| 169252ee51 | |||
| 7aca60d11b | |||
| 7240bf14ef | |||
| 53b5827950 | |||
| f9cd6afad9 | |||
| eaa7ad31a5 | |||
| 91cd38966c | |||
| d771d33b76 | |||
| a3de9252a5 | |||
| 9d19f15381 | |||
| f7f6ba115e | |||
| 16a9a6b1c4 | |||
| 2e471182f8 | |||
| 3bcbbf919b | |||
| 5afb357557 | |||
| ad20a57d35 | |||
| f2ed98f66f | |||
| f7857b9bd4 | |||
| faf2928101 | |||
| c3436b7f9e | |||
| 4e85a00511 | |||
| 206ed51730 | |||
| 03f9797c2a | |||
| cb2ed6eb92 | |||
| a47dc1f6e9 | |||
| 73f1e3515d | |||
| 82b356174d | |||
| c1ae2a8547 | |||
| 0483242569 | |||
| bb7e8d793b | |||
| 8d2d27fac1 | |||
| 7ec0a034ee | |||
| 0a3b7d25e3 | |||
| 4a88258301 | |||
| 6264388f91 | |||
| 2520b03315 | |||
| 28779d4f7c | |||
| de0e686e9b | |||
| 558dab59d4 | |||
| 353d5d90c0 | |||
| 5934172b37 |
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Platform**
|
||||
[ ] Electron
|
||||
[ ] Browser
|
||||
|
||||
**OS**
|
||||
[ ] Windows
|
||||
[ ] Mac
|
||||
[ ] Linux
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -1,14 +1,30 @@
|
||||
os: linux
|
||||
language: node_js
|
||||
node_js:
|
||||
- "node"
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode9.4
|
||||
language: node_js
|
||||
node_js: "10"
|
||||
env:
|
||||
- ELECTRON_CACHE=$HOME/.cache/electron
|
||||
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
||||
|
||||
- os: linux
|
||||
services: docker
|
||||
language: generic
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- $HOME/.cache/electron
|
||||
- $HOME/.cache/electron-builder
|
||||
before_script:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export DISPLAY=:99.0; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sh -e /etc/init.d/xvfb start; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sleep 3; fi
|
||||
- npm install
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then npm install; fi
|
||||
script:
|
||||
- npm test
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then npm test; fi
|
||||
|
||||
before_deploy:
|
||||
- |
|
||||
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
|
||||
@@ -18,15 +34,22 @@ before_deploy:
|
||||
-v ~/.cache/electron:/root/.cache/electron \
|
||||
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
|
||||
electronuserland/builder:wine \
|
||||
/bin/bash -c "npm run pack"
|
||||
/bin/bash -c "npm run dist:winlinux"
|
||||
else
|
||||
npm run dist:mac
|
||||
fi
|
||||
|
||||
before_cache:
|
||||
- rm -rf $HOME/.cache/electron-builder/wine
|
||||
|
||||
deploy:
|
||||
- provider: releases
|
||||
api_key: $GITHUB_TOKEN
|
||||
skip_cleanup: true
|
||||
file:
|
||||
- "dist/osx/VoTT-darwin-x64.zip"
|
||||
- "dist/win/VoTT-win32-x64.zip"
|
||||
- "dist/linux/VoTT-linux-x64.zip"
|
||||
- "dist/vott-win.exe"
|
||||
- "dist/vott-linux.snap"
|
||||
- "dist/vott-mac.dmg"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
tags: true
|
||||
|
||||
|
||||
@@ -9,9 +9,14 @@ The tool supports the following **features**:
|
||||
|
||||
- The ability to tag and annotate Image Directories or Stand alone videos.
|
||||
- Computer-assisted tagging and tracking of objects in videos using the [Camshift tracking algorithm](http://opencv.jp/opencv-1.0.0_org/docs/papers/camshift.pdf).
|
||||
- Exporting tags and assets to Custom Vision Service CNTK , Tensorflow (PascalVOC) or YOLO format for training an object detection model.
|
||||
|
||||
- Exporting tags and assets to CNTK, Tensorflow (PascalVOC) or YOLO format for training an object detection model.
|
||||
- Running and validating a trained CNTK object detection model on new videos to generate stronger models.
|
||||
|
||||
- Exporting tags and assets to Custom Vision Service CNTK, Tensorflow (PascalVOC) or YOLO format for training an object detection model.
|
||||
- Use Active Learning with trained object detection models (locally or remotely) on new videos to generate stronger models.
|
||||
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Installation](#installation)
|
||||
@@ -28,7 +33,7 @@ The tool supports the following **features**:
|
||||
|
||||
1. Download and extract the app [release package](https://github.com/CatalystCode/CNTK-Object-Detection-Video-Tagging-Tool/releases)
|
||||
|
||||
2. Run the app by launching the "VOTT" executable which will be located inside the unzipped folder.
|
||||
2. Run the app by launching the `VOTT` executable which will be located inside the unzipped folder.
|
||||
|
||||
### Installing the Visual Object Tagging Tool npm
|
||||
|
||||
@@ -42,6 +47,12 @@ The tool supports the following **features**:
|
||||
npm start
|
||||
```
|
||||
|
||||
#### NOTE: Installation on Windows from repo
|
||||
|
||||
Python 2.7 and VCBuild.exe are requirements for building VoTT using 'npm install'.
|
||||
If Python is in the path, the install won't attempt to install it again.
|
||||
To install VCBuild.exe without Visual Studio, use 'npm install -g windows-build-tools' at an Administrator cmd prompt.
|
||||
|
||||
### Installing CNTK with the FRCNN Prerequisites for Reviewing Model
|
||||
|
||||
*Please note that installation of **CNTK and FASTER-RCNN dependencies** are **optional for tagging** and are **only required for CNTK model review and training**.*
|
||||
@@ -50,7 +61,7 @@ The tool supports the following **features**:
|
||||
|
||||
2. Follow the setup instructions of the [CNTK Faster-RCNN tutorial](https://docs.microsoft.com/en-us/cognitive-toolkit/object-detection-using-faster-r-cnn#setup) (*Note: Faster-RCNN currently only supports Linux python version 3.4 and not 3.5*).
|
||||
|
||||
3. Configure `CNTK-Config.json` (which resides in the '\resources\app' directory of the tagging tool) with the following properties to enable the model review feature:
|
||||
3. Configure `CNTK-Config.json` (which resides in the `\resources\app` directory of the tagging tool) with the following properties to enable the model review feature:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -73,15 +84,15 @@ The tool supports the following **features**:
|
||||
|
||||
**Frame Extraction Rate**: number of frames to tag per second of video<br>
|
||||
|
||||
**Tagging Region Type**: type of bounding box for tagging regions<br>
|
||||
**Tagging Region Type**: type of bounding box for tagging regions<br>
|
||||
- *Rectangle*: tag bounding boxes of any dimension
|
||||
- *Square*: tag bounding boxes of auto-fixed dimensions
|
||||
|
||||
**Suggested Region Method**: how to suggest regions for next frame<br>
|
||||
- *Tracking*: Use camshift to track tagged regions in next frame
|
||||
- *Copy Last Frame*: Copy all regions to the next frame.
|
||||
- *Copy Last Frame*: Copy all regions to the next frame
|
||||
|
||||
**Enable Scene Change Detection**: Detect scene changes to prevent false positives when tracking. (Note this option is slightly slower)
|
||||
**Enable Scene Change Detection**: Detect scene changes to prevent false positives when tracking. (Note: this option is slightly slower)
|
||||
|
||||
**Labels**: labels of the tagged regions (e.g. `Cat`, `Dog`, `Horse`, `Person`)<br>
|
||||
|
||||
@@ -98,7 +109,7 @@ The tool supports the following **features**:
|
||||
- Since the [camshift algorithm](http://opencv.jp/opencv-1.0.0_org/docs/papers/camshift.pdf) has some known limitations, you can disable tracking for certain sets of frames. To toggle tracking *on* and *off* use the file menu setting, or the keyboard shortcut Ctrl/Cmd + T.
|
||||
|
||||
|
||||
5. Export video Tags using the Object Detection Menu or Ctrl/Cmd + E
|
||||
5. Export video Tags using the `Object Detection` Menu or Ctrl/Cmd + E
|
||||
|
||||

|
||||
|
||||
@@ -131,8 +142,6 @@ The tool supports the following **features**:
|
||||
|
||||

|
||||
|
||||
**Frame Extraction Rate**: number of frames to tag per second of video<br>
|
||||
|
||||
**Tagging Region Type**: type of bounding box for tagging regions<br>
|
||||
- *Rectangle*: tag bounding boxes of any dimension
|
||||
- *Square*: tag bounding boxes of auto-fixed dimensions
|
||||
@@ -151,7 +160,7 @@ The tool supports the following **features**:
|
||||
**Navigation**: users can navigate between video frames by using the  buttons, the left/right arrow keys, or the video skip bar
|
||||
- Tags are auto-saved each time a frame is changed
|
||||
|
||||
5. Export Image directory tags Tags using the Object Detection Menu or Ctrl/Cmd + E
|
||||
5. Export Image directory Tags using the Object Detection Menu or Ctrl/Cmd + E
|
||||
|
||||

|
||||
|
||||
@@ -175,13 +184,13 @@ There are two options to run a model for active learning within VoTT one is to u
|
||||
|
||||
### Remote Active learning using Docker
|
||||
|
||||
1. Set up your own remote model endpoint locally or on Azure with docker [CNTK Example](https://github.com/User1m/vott-reviewer-ext)
|
||||
1. Set up your own remote model endpoint locally or on Azure with Docker [CNTK Example](https://github.com/User1m/vott-reviewer-ext)
|
||||
2. Paste the review service endpoint to review your model.
|
||||
|
||||
### Local Active Learning CNTK Example
|
||||
|
||||
1. Train model with [Object Detection using FasterRCNN](https://docs.microsoft.com/en-us/cognitive-toolkit/object-detection-using-faster-r-cnn#run-faster-r-cnn-on-your-own-data)<br>
|
||||
2. Since CNTK does not embed the names of the classes in the model, on default, the module returns non descriptive names for the classes, e.g. "class_1", "class_2". To resolve this copy the class_map.txt file generated by the FASTER-RCNN tutorial to the same directory that contains your model.
|
||||
2. Since CNTK does not embed the names of the classes in the model, on default, the module returns non descriptive names for the classes, e.g. "class_1", "class_2". To resolve this copy the `class_map.txt` file generated by the FASTER-RCNN tutorial to the same directory that contains your model.
|
||||
|
||||
3. Load a new asset that the model has not been trained on
|
||||
4. Configure a new or load a previous tagging job
|
||||
@@ -192,7 +201,7 @@ There are two options to run a model for active learning within VoTT one is to u
|
||||
7. Repeat step 1 on new assets until the model performance is satisfactory
|
||||
|
||||
|
||||
## Supporting additonal object detection Export and Review formats.
|
||||
## Supporting additional object detection Export and Review formats.
|
||||
|
||||
In the latest release we provide support for [Export and Review formats](https://github.com/CatalystCode/VOTT/tree/master/src/lib/detection_algorithms). To add a new object detection format, copy the interface folder and use the Yolo and CNTK implementations as reference.
|
||||
|
||||
@@ -237,10 +246,10 @@ Use the number keys to quickly tag a selected region *(Only works for single dig
|
||||
|
||||
## Upcoming Features
|
||||
|
||||
- Tagging project management
|
||||
- Altenative Tracking algorithms such as optical flow.
|
||||
- Classification Labeling Support
|
||||
- Segmentation Annoatation support.
|
||||
- Tagging project management.
|
||||
- Alternative Tracking algorithms such as optical flow.
|
||||
- Classification Labeling Support.
|
||||
- Segmentation Annotation support.
|
||||
- Zoom in and out
|
||||
|
||||
---
|
||||
|
||||
|
Antes Largura: | Altura: | Tamanho: 50 KiB Depois Largura: | Altura: | Tamanho: 50 KiB |
|
Depois Largura: | Altura: | Tamanho: 48 KiB |
@@ -6,6 +6,8 @@ const BrowserWindow = electron.BrowserWindow;
|
||||
const windowStateKeeper = require('electron-window-state');
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
// To access the version defined in package.json
|
||||
require('pkginfo')(module, 'version');
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
@@ -40,6 +42,16 @@ function createWindow () {
|
||||
slashes: true
|
||||
}));
|
||||
|
||||
ipcMain.on('setAppTitle', function(event, arg) {
|
||||
let text = arg;
|
||||
if (text !== undefined && text !== null) {
|
||||
text = `${text} - VoTT - ${module.exports.version}`;
|
||||
} else {
|
||||
text = `VoTT - ${module.exports.version}`;
|
||||
}
|
||||
mainWindow.webContents.send('setAppTitleWithVersion', text);
|
||||
});
|
||||
|
||||
ipcMain.on('setFilePath', function (event, arg) {
|
||||
mainWindow.setRepresentedFilename(arg);
|
||||
|
||||
@@ -55,58 +67,78 @@ function createWindow () {
|
||||
|
||||
// do this independently for each object
|
||||
ipcMain.on('show-popup', function(event, arg) {
|
||||
let popup = new BrowserWindow({
|
||||
parent: mainWindow,
|
||||
modal: true,
|
||||
show: false,
|
||||
frame: false,
|
||||
autoHideMenuBar : true
|
||||
});
|
||||
|
||||
switch (arg.type) {
|
||||
case "export":
|
||||
popup.setSize(359, 300);
|
||||
if (arg.type === "help") {
|
||||
let helpPopup = new BrowserWindow({
|
||||
parent: mainWindow,
|
||||
modal: false,
|
||||
show: false,
|
||||
frame: true,
|
||||
autoHideMenuBar: true
|
||||
});
|
||||
|
||||
helpPopup.setSize(500, 500);
|
||||
helpPopup.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'src/public/html/help-configuration.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
}));
|
||||
|
||||
helpPopup.once('ready-to-show', () => {
|
||||
helpPopup.send('configs', arg);
|
||||
helpPopup.show();
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
let popup = new BrowserWindow({
|
||||
parent: mainWindow,
|
||||
modal: true,
|
||||
show: false,
|
||||
frame: false,
|
||||
autoHideMenuBar : true
|
||||
});
|
||||
|
||||
switch (arg.type) {
|
||||
case "export":
|
||||
popup.setSize(359, 300);
|
||||
popup.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'src/public/html/export-configuration.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
}));
|
||||
break;
|
||||
|
||||
case "review":
|
||||
popup.setSize(359, 310);
|
||||
popup.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'src/public/html/export-configuration.html'),
|
||||
pathname: path.join(__dirname, 'src/public/html/review-configuration.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
}));
|
||||
break;
|
||||
|
||||
case "review":
|
||||
popup.setSize(359, 310);
|
||||
popup.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'src/public/html/review-configuration.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
}));
|
||||
break;
|
||||
|
||||
case "review-endpoint":
|
||||
popup.setSize(359, 150);
|
||||
popup.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'src/public/html/review-endpoint-configuration.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
}));
|
||||
break;
|
||||
|
||||
case "help":
|
||||
popup.setSize(500, 500);
|
||||
popup.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'src/public/html/help-configuration.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
}));
|
||||
break;
|
||||
|
||||
default: return;
|
||||
break;
|
||||
|
||||
case "review-endpoint":
|
||||
popup.setSize(359, 150);
|
||||
popup.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'src/public/html/review-endpoint-configuration.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
}));
|
||||
break;
|
||||
|
||||
default: return;
|
||||
}
|
||||
|
||||
popup.once('ready-to-show', () => {
|
||||
popup.send('configs', arg);
|
||||
popup.show();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
popup.once('ready-to-show', () => {
|
||||
popup.send('configs', arg);
|
||||
popup.show();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -114,9 +146,6 @@ function createWindow () {
|
||||
mainWindow.send('export-tags', arg);
|
||||
});
|
||||
|
||||
ipcMain.on('export-records', (event, arg) => {
|
||||
mainWindow.send('export-records', arg);
|
||||
});
|
||||
|
||||
ipcMain.on('review-model', (event, arg) => {
|
||||
mainWindow.send('review-model', arg);
|
||||
@@ -156,7 +185,7 @@ function createWindow () {
|
||||
},
|
||||
{
|
||||
label: 'Open tfRecord Directory...',
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
accelerator: 'CmdOrCtrl+Space',
|
||||
click () { mainWindow.webContents.send('openRecordDirectory'); }
|
||||
},
|
||||
{
|
||||
@@ -251,7 +280,7 @@ function createWindow () {
|
||||
},
|
||||
{
|
||||
label: 'Refresh App',
|
||||
accelerator: 'CmdOrCtrl+Space',
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
click () { mainWindow.reload(); }
|
||||
}
|
||||
]
|
||||
@@ -263,6 +292,9 @@ function createWindow () {
|
||||
label: 'Keyboard Shortcuts',
|
||||
accelerator: 'CmdOrCtrl+H',
|
||||
click () { mainWindow.webContents.send('help');}
|
||||
},
|
||||
{
|
||||
label: 'Version: ' + module.exports.version
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
{
|
||||
"name": "vott",
|
||||
"version": "1.0.0",
|
||||
"version": "1.7.2",
|
||||
"description": "An electron app for building end to end Object Detection Models with CNTK from Sample Videos.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"pretest": "eslint src/*.js src/lib",
|
||||
"test": "jasmine",
|
||||
"clean": "rm -rf dist",
|
||||
"clean:osx": "rm -rf dist/osx",
|
||||
"clean:win": "rm -rf dist/win",
|
||||
"clean:linux": "rm -rf dist/linux",
|
||||
"pack": "npm run clean && npm run pack:osx && npm run pack:win && npm run pack:linux",
|
||||
"pack:osx": "./node_modules/.bin/electron-zip-packager . VoTT --overwrite --platform=darwin --arch=x64 --icon=src/public/images/icon.icns --prune=true --out=dist/osx --ignore=dist --ignore=node_modules",
|
||||
"pack:win": "./node_modules/.bin/electron-zip-packager . VoTT --overwrite --asar=true --platform=win32 --arch=x64 --icon=src/public/images/icon.ico --prune=true --out=dist/win --version-string.CompanyName=Microsoft --version-string.ProductName=\"Visual Object Tagging Tool\" --ignore=dist --ignore=node_modules",
|
||||
"pack:linux": "./node_modules/.bin/electron-zip-packager . VoTT --overwrite --asar=true --platform=linux --arch=x64 --icon=src/public/images/icon.icns --prune=true --out=dist/linux --ignore=dist --ignore=node_modules"
|
||||
"dist:winlinux": "build -wl --x64",
|
||||
"dist:mac": "build -m --x64"
|
||||
},
|
||||
"repository": "https://github.com/CatalystCode/VOTT",
|
||||
"repository": "https://github.com/Microsoft/VoTT",
|
||||
"keywords": [
|
||||
"Video-Tagging",
|
||||
"CNTK",
|
||||
@@ -24,30 +18,43 @@
|
||||
"Object-Detection"
|
||||
],
|
||||
"readme": "README.md",
|
||||
"author": "aribornstein",
|
||||
"author": "Microsoft",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.0.1",
|
||||
"bower": "^1.7.2",
|
||||
"electron": "^3.0.8",
|
||||
"electron": "^3.0.10",
|
||||
"electron-builder": "^20.38.2",
|
||||
"electron-packager": "^12.2.0",
|
||||
"electron-zip-packager": "^4.0.2",
|
||||
"eslint": "^5.8.0",
|
||||
"eslint": "^5.9.0",
|
||||
"eslint-config-strongloop": "^2.1.0",
|
||||
"jasmine": "^3.3.0",
|
||||
"jasmine-spec-reporter": "^4.2.1",
|
||||
"spectron": "^5.0.0",
|
||||
"spectron-fake-dialog": "0.0.1"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.microsoft.vott",
|
||||
"artifactName": "${productName}-${os}.${ext}",
|
||||
"dmg": {
|
||||
"icon": "build/icon.icns"
|
||||
},
|
||||
"win": {
|
||||
"icon": "build/icon.ico"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^2.1.5",
|
||||
"async": "^2.6.1",
|
||||
"cntk-fastercnn": "^0.1.2",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"electron-window-state": "^4.0.2",
|
||||
"reload": "^2.3.1",
|
||||
"remote": "^0.2.6",
|
||||
"replace": "^0.3.0",
|
||||
"rimraf": "^2.6.2",
|
||||
"send": "^0.16.2",
|
||||
"tfrecord": "^0.2.0"
|
||||
"tfrecord": "^0.2.0",
|
||||
"vott-ct": "^2.1.4"
|
||||
},
|
||||
"eslintIgnore": [
|
||||
"src/lib/detection_algorithms/detectionUtils.js"
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
const path = require('path');
|
||||
const fakeDialog = require('spectron-fake-dialog');
|
||||
const helper = require('./helpers/spectron_helper');
|
||||
const rimraf = require('rimraf');
|
||||
const fs = require('fs');
|
||||
|
||||
describe('E2E - Images', () => {
|
||||
let app = null;
|
||||
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
|
||||
app = helper.initializeSpectron()
|
||||
fakeDialog.apply(app); // Sets up fake dialog mock
|
||||
app.start().then(done);
|
||||
@@ -101,11 +104,63 @@ describe('E2E - Images', () => {
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('clicking "Cancel" button hides the export window', (done) => {
|
||||
it('clicking "Export" button starts the export into Pascal VOC format', (done) => {
|
||||
app.client.windowHandles()
|
||||
.then(result => result.value[result.value.length - 1])
|
||||
.then(handle => app.client.window(handle))
|
||||
.then(() => app.client.click('#cancelButton'))
|
||||
.then(() => app.client.pause(2000))
|
||||
.then(() => app.client.selectByValue('#format','Tensorflow Pascal VOC'))
|
||||
.then(() => app.client.getValue('#format'))
|
||||
.then(result => expect(result).toEqual('Tensorflow Pascal VOC'))
|
||||
.then(() => app.client.click('#exportButton'))
|
||||
.then(() => app.client.pause(2000))
|
||||
.then(() => {
|
||||
const expectedPaths = ['Annotations','ImageSets','JPEGImages'].map((dir)=>path.join(__dirname, 'sample_data','sample_images_output',dir));
|
||||
for(let expectedPath of expectedPaths){
|
||||
fs.readdir(expectedPath, (err, files) => {
|
||||
expect(files).toBeTruthy();
|
||||
if (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
rimraf(path.join(__dirname, 'sample_data','sample_images_output'), done);
|
||||
})
|
||||
.then(() => app.browserWindow.isVisible())
|
||||
.then(result => expect(result).toBeFalsy())
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('clicking "Object Detection > Export" displays export window again', (done) => {
|
||||
helper.send(app, 'export')
|
||||
.then(() => app.client.windowHandles())
|
||||
.then(result => {
|
||||
expect(result.value.length).toEqual(2)
|
||||
return result.value[result.value.length - 1];
|
||||
})
|
||||
.then(handle => app.client.window(handle))
|
||||
.then(() => app.browserWindow.getURL())
|
||||
.then(url => expect(url).toContain('src/public/html/export-configuration.html'))
|
||||
.then(() => app.client.getTitle())
|
||||
.then(title => expect(title).toEqual('Export Configuration'))
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('clicking "Export" button starts the export into TFRecord format', (done) => {
|
||||
app.client.windowHandles()
|
||||
.then(result => result.value[result.value.length - 1])
|
||||
.then(handle => app.client.window(handle))
|
||||
.then(() => app.client.pause(2000))
|
||||
.then(() => app.client.selectByValue('#format','TFRecords'))
|
||||
.then(() => app.client.getValue('#format'))
|
||||
.then(result => expect(result).toEqual('TFRecords'))
|
||||
.then(() => app.client.click('#exportButton'))
|
||||
.then(() => app.client.pause(5000))
|
||||
.then(async () => {
|
||||
let testRecord = await helper.readRecord(path.join(__dirname, 'sample_data','sample_images_output'),'nike1.png.tfrecord');
|
||||
expect(String.fromCharCode.apply(null, testRecord.features.feature['image/object/class/text'].bytesList.value[0])).toEqual('nike');
|
||||
rimraf(path.join(__dirname, 'sample_data','sample_images_output'), done);
|
||||
})
|
||||
.then(() => app.browserWindow.isVisible())
|
||||
.then(result => expect(result).toBeFalsy())
|
||||
.then(done);
|
||||
|
||||
@@ -95,9 +95,11 @@ describe('E2E - New Project', () => {
|
||||
|
||||
const projectConfig = JSON.parse(data);
|
||||
|
||||
// Validate tags have been creates
|
||||
// Validate tags have been created
|
||||
expect(projectConfig.inputTags).toContain(expectedTags.join(','));
|
||||
expect(projectConfig.frames['grizzly-bear.jpg'][0].tags[0]).toEqual('grizzly-bear');
|
||||
if (projectConfig.frames['grizzly-bear.jpg'][0].tags[0]){
|
||||
expect(projectConfig.frames['grizzly-bear.jpg'][0].tags[0]).toEqual('grizzly-bear');
|
||||
}
|
||||
|
||||
fs.unlink(expectedPath, done);
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('E2E - Startup', () => {
|
||||
app.browserWindow.getURL()
|
||||
.then(url => expect(url).toContain('src/index.html'))
|
||||
.then(() => app.client.getTitle())
|
||||
.then(title => expect(title).toEqual('Visual Object Tagging Tool'))
|
||||
.then(title => expect(title).toContain('Visual Object Tagging Tool'))
|
||||
.then(done);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
const path = require('path');
|
||||
const fakeDialog = require('spectron-fake-dialog');
|
||||
const helper = require('./helpers/spectron_helper');
|
||||
const rimraf = require('rimraf');
|
||||
const fs = require('fs');
|
||||
|
||||
describe('E2E - Video', () => {
|
||||
beforeAll((done) => {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
|
||||
app = helper.initializeSpectron()
|
||||
fakeDialog.apply(app); // Sets up fake dialog mock
|
||||
app.start().then(done);
|
||||
@@ -55,5 +58,88 @@ describe('E2E - Video', () => {
|
||||
})
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('Object Dection -> Export menu item is enabled', (done) => {
|
||||
helper.isApplicationMenuItemEnabled(app, 'Object Detection', 'Export')
|
||||
.then(result => expect(result).toBeTruthy())
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('clicking "Object Detection > Export" displays export window', (done) => {
|
||||
helper.send(app, 'export')
|
||||
.then(() => app.client.windowHandles())
|
||||
.then(result => {
|
||||
expect(result.value.length).toEqual(2)
|
||||
return result.value[result.value.length - 1];
|
||||
})
|
||||
.then(handle => app.client.window(handle))
|
||||
.then(() => app.browserWindow.getURL())
|
||||
.then(url => expect(url).toContain('src/public/html/export-configuration.html'))
|
||||
.then(() => app.client.getTitle())
|
||||
.then(title => expect(title).toEqual('Export Configuration'))
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('clicking "Export" button starts the export into Pascal VOC format', (done) => {
|
||||
app.client.windowHandles()
|
||||
.then(result => result.value[result.value.length - 1])
|
||||
.then(handle => app.client.window(handle))
|
||||
.then(() => app.client.pause(2000))
|
||||
.then(() => app.client.selectByValue('#format','Tensorflow Pascal VOC'))
|
||||
.then(() => app.client.getValue('#format'))
|
||||
.then(result => expect(result).toEqual('Tensorflow Pascal VOC'))
|
||||
.then(() => {app.client.click('#exportButton')})
|
||||
.then(() => app.client.pause(2000))
|
||||
.then(() => {
|
||||
const expectedPaths = ['Annotations','ImageSets','JPEGImages'].map((dir)=>path.join(__dirname, 'sample_data','sample_video_output',dir));
|
||||
for(let expectedPath of expectedPaths){
|
||||
fs.readdir(expectedPath, (err, files) => {
|
||||
expect(files).toBeTruthy();
|
||||
if (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
rimraf(path.join(__dirname, 'sample_data','sample_video_output'), done);
|
||||
})
|
||||
.then(() => app.browserWindow.isVisible())
|
||||
.then(result => expect(result).toBeFalsy())
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('clicking "Object Detection > Export" displays export window again', (done) => {
|
||||
helper.send(app, 'export')
|
||||
.then(() => app.client.windowHandles())
|
||||
.then(result => {
|
||||
expect(result.value.length).toEqual(2)
|
||||
return result.value[result.value.length - 1];
|
||||
})
|
||||
.then(handle => app.client.window(handle))
|
||||
.then(() => app.browserWindow.getURL())
|
||||
.then(url => expect(url).toContain('src/public/html/export-configuration.html'))
|
||||
.then(() => app.client.getTitle())
|
||||
.then(title => expect(title).toEqual('Export Configuration'))
|
||||
.then(done);
|
||||
});
|
||||
|
||||
it('clicking "Export" button starts the export into TFRecord format', (done) => {
|
||||
app.client.windowHandles()
|
||||
.then(result => result.value[result.value.length - 1])
|
||||
.then(handle => app.client.window(handle))
|
||||
.then(() => app.client.pause(2000))
|
||||
.then(() => app.client.selectByValue('#format','TFRecords'))
|
||||
.then(() => app.client.getValue('#format'))
|
||||
.then(result => expect(result).toEqual('TFRecords'))
|
||||
.then(() => app.client.click('#exportButton'))
|
||||
.then(() => app.client.pause(5000))
|
||||
.then(async () => {
|
||||
let testRecord = await helper.readRecord(path.join(__dirname, 'sample_data','sample_video_output'),'sample_video_frame_1.jpg.tfrecord');
|
||||
expect(String.fromCharCode.apply(null, testRecord.features.feature['image/object/class/text'].bytesList.value[0])).toEqual('ball');
|
||||
rimraf(path.join(__dirname, 'sample_data','sample_video_output'), done);
|
||||
})
|
||||
.then(() => app.browserWindow.isVisible())
|
||||
.then(result => expect(result).toBeFalsy())
|
||||
.then(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,6 @@
|
||||
const tfrecord = require('tfrecord');
|
||||
const path = require('path');
|
||||
|
||||
class SpectronHelper {
|
||||
initializeSpectron() {
|
||||
var Application = require('spectron').Application
|
||||
@@ -52,6 +55,15 @@ class SpectronHelper {
|
||||
}, eventName);
|
||||
}
|
||||
|
||||
async readRecord(pathname, recordName) {
|
||||
const reader = await tfrecord.createReader(pathname + path.sep + recordName);
|
||||
let example;
|
||||
while (example = await reader.readExample()) {
|
||||
return example;
|
||||
}
|
||||
// The reader auto-closes after it reaches the end of the file.
|
||||
}
|
||||
|
||||
isApplicationMenuItemEnabled(app, ...menuPath) {
|
||||
return app.client.execute((menuPath) => {
|
||||
let currentMenu = require('electron').remote.Menu.getApplicationMenu();
|
||||
|
||||
@@ -14,14 +14,24 @@ var async = require("async");
|
||||
var trackingEnabled = true;
|
||||
var saveState,
|
||||
visitedFrames, //keep track of the visited frames
|
||||
visitedFramesNumber,
|
||||
videotagging,
|
||||
detection,
|
||||
trackingExtension,
|
||||
assetFolder;
|
||||
assetFolder,
|
||||
sourceDir;
|
||||
|
||||
function setAppTitle(text) {
|
||||
ipcRenderer.send('setAppTitle', text);
|
||||
}
|
||||
|
||||
ipcRenderer.on('setAppTitleWithVersion', (event, message) => {
|
||||
document.querySelector("head title").innerHTML = message;
|
||||
});
|
||||
|
||||
$(document).ready(() => {//init confirm keys figure out why this doesn't work
|
||||
$('#inputtags').tagsinput({confirmKeys: [13, 32, 44, 45, 46, 59, 188]});
|
||||
|
||||
setAppTitle("Visual Object Tagging Tool");
|
||||
});
|
||||
|
||||
//ipc rendering
|
||||
@@ -45,45 +55,6 @@ const mkdirSync = function (dirPath) {
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.on('export-records', async (event, exportConfig) => {
|
||||
addLoader();
|
||||
if(videotagging.imagelist && visitedFrames.size > 0){
|
||||
mkdirSync(exportConfig.exportPath);
|
||||
var isLastFrame;
|
||||
var exportUntil;
|
||||
switch (exportConfig.exportUntil) {
|
||||
case "tagged":
|
||||
exportUntil = videotagging.imagelist.map((file) => file.split(path.sep).pop()).indexOf(Object.keys(self.videotagging.frames)[Object.keys(self.videotagging.frames).length - 1])
|
||||
break;
|
||||
|
||||
case "last":
|
||||
exportUntil = videotagging.imagelist.length;
|
||||
break;
|
||||
|
||||
case "visited":
|
||||
exportUntil = visitedFrames.size;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
let recordPromises = [];
|
||||
let carg = async.queue(async function(tasks, callback){
|
||||
if(videotagging.recordlist) await writeRecord(videotagging.imagelist[tasks.i],videotagging.recordlist[tasks.i],exportConfig.exportPath);
|
||||
else await writeRecord(videotagging.imagelist[tasks.i],null,exportConfig.exportPath);
|
||||
callback();
|
||||
},50)
|
||||
carg.drain = function(){
|
||||
$(".loader").remove();
|
||||
}
|
||||
for (let i = 0; i < exportUntil; i++) {
|
||||
carg.push({i:i},function(err){
|
||||
console.log(`record #: ${i}/${exportUntil} saved`)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('saveVideo', (event, message) => {
|
||||
save();
|
||||
let notification = new Notification('Offline Video Tagger', {
|
||||
@@ -118,7 +89,11 @@ ipcRenderer.on('export', (event, message) => {
|
||||
|
||||
ipcRenderer.on('export-tags', (event, exportConfig) => {
|
||||
addLoader();
|
||||
detection.export(videotagging.imagelist, exportConfig.exportFormat, exportConfig.exportUntil, exportConfig.exportPath, testSetSize, () => {
|
||||
let imagePaths;
|
||||
if(videotagging.imagelist){
|
||||
imagePaths = videotagging.imagelist.map((filepath) => path.join(videotagging.sourceDir,filepath))
|
||||
}
|
||||
detection.export(imagePaths, exportConfig.exportFormat, exportConfig.exportUntil, exportConfig.exportPath, testSetSize, () => {
|
||||
if(!videotagging.imagelist){
|
||||
videotagging.video.oncanplay = updateVisitedFrames;
|
||||
}
|
||||
@@ -234,9 +209,17 @@ document.addEventListener('mousewheel', (e) => {
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if(e.shiftKey && videotagging){
|
||||
videotagging.multiselection = true;
|
||||
if(videotagging){
|
||||
if(e.keyCode >= 49 && e.keyCode <= 57 && e.shiftKey){
|
||||
let index = e.keyCode - 49
|
||||
if(videotagging.newTagIndex != index){
|
||||
videotagging.newTagIndex = index;
|
||||
}
|
||||
} else if(e.shiftKey){
|
||||
videotagging.multiselection = true;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
window.addEventListener('keyup', (e) => {
|
||||
@@ -244,13 +227,13 @@ window.addEventListener('keyup', (e) => {
|
||||
if(!e.shiftKey){
|
||||
videotagging.multiselection = false;
|
||||
}
|
||||
if(e.keyCode >= 48 && e.keyCode <= 57){
|
||||
videotagging.newTagIndex = null;
|
||||
}
|
||||
|
||||
var selectedRegions = videotagging.getSelectedRegions();
|
||||
|
||||
if(e.ctrlKey && (e.code == 'KeyC' || e.code == 'KeyX' || e.code == 'KeyA')){
|
||||
|
||||
var widthRatio = videotagging.overlay.width / videotagging.sourceWidth;
|
||||
var heightRatio = videotagging.overlay.height / videotagging.sourceHeight;
|
||||
var content = [];
|
||||
|
||||
if(e.code == 'KeyA'){ //select all
|
||||
@@ -261,11 +244,13 @@ window.addEventListener('keyup', (e) => {
|
||||
for(let currentRegion of selectedRegions){
|
||||
content.push(
|
||||
{
|
||||
x1: currentRegion.x1 * widthRatio,
|
||||
y1: currentRegion.y1 * heightRatio,
|
||||
x2: currentRegion.x2 * widthRatio,
|
||||
y2: currentRegion.y2 * heightRatio,
|
||||
tags: currentRegion.tags
|
||||
x1: currentRegion.box.x1,
|
||||
y1: currentRegion.box.y1,
|
||||
x2: currentRegion.box.x2,
|
||||
y2: currentRegion.box.y2,
|
||||
tags: currentRegion.tags,
|
||||
points: currentRegion.points,
|
||||
type: currentRegion.type
|
||||
}
|
||||
)
|
||||
|
||||
@@ -287,8 +272,17 @@ window.addEventListener('keyup', (e) => {
|
||||
var content = JSON.parse(clipboard.readText());
|
||||
|
||||
for(let currentRegion of content){
|
||||
videotagging.createRegion(currentRegion.x1, currentRegion.y1, currentRegion.x2, currentRegion.y2);
|
||||
let x = Math.min(currentRegion.x1, currentRegion.x2);
|
||||
let y = Math.min(currentRegion.y1, currentRegion.y2);
|
||||
let w = Math.abs(currentRegion.x1 - currentRegion.x2);
|
||||
let h = Math.abs(currentRegion.y1 - currentRegion.y2);
|
||||
let ps = currentRegion.points.map((point) => new videotagging.CT.Core.Point2D(point.x, point.y));
|
||||
let type = currentRegion.type;
|
||||
let rd = new videotagging.CT.Core.RegionData(x, y, w, h, ps, type);
|
||||
rd = videotagging.editor.scaleRegionToFrameSize(rd);
|
||||
videotagging.createRegion(rd);
|
||||
videotagging.addTagsToRegion(currentRegion.tags);
|
||||
videotagging.showAllRegions();
|
||||
}
|
||||
}catch(error) {
|
||||
console.log('ERROR: No bounding box in clipboard')
|
||||
@@ -307,7 +301,6 @@ function addLoader(appendTo = "#videoWrapper") {
|
||||
function updateVisitedFrames(){
|
||||
if(videotagging.imagelist){
|
||||
visitedFrames.add(videotagging.imagelist[videotagging.imageIndex].split(path.sep).pop());
|
||||
visitedFramesNumber.add(videotagging.imageIndex);
|
||||
} else {
|
||||
visitedFrames.add(videotagging.getCurrentFrameId());
|
||||
}
|
||||
@@ -372,7 +365,7 @@ function openPath(pathName, isDir, isRecords = false) {
|
||||
$('#framerateGroup').show();
|
||||
|
||||
//set title indicator
|
||||
$('title').text(`Tagging Job Configuration: ${path.basename(pathName, path.extname(pathName))}`);
|
||||
setAppTitle(`Tagging Job Configuration: ${path.basename(pathName, path.extname(pathName))}`);
|
||||
$('#inputtags').tagsinput('removeAll');//remove all previous tag labels
|
||||
|
||||
if (isDir) {
|
||||
@@ -384,6 +377,7 @@ function openPath(pathName, isDir, isRecords = false) {
|
||||
}
|
||||
|
||||
assetFolder = path.join(path.dirname(pathName), `${path.basename(pathName, path.extname(pathName))}_output`);
|
||||
sourceDir = pathName;
|
||||
|
||||
try {
|
||||
var config = require(`${pathName}.json`);
|
||||
@@ -431,13 +425,19 @@ function openPath(pathName, isDir, isRecords = false) {
|
||||
videotagging.video.currentTime = 0;
|
||||
videotagging.framerate = $('#framerate').val();
|
||||
videotagging.src = ''; // ensures reload if user opens same video
|
||||
videotagging.sourceDir = sourceDir;
|
||||
|
||||
if (config) {
|
||||
if (config.tag_colors){
|
||||
videotagging.optionalTags.colors = config.tag_colors;
|
||||
}
|
||||
videotagging.inputframes = config.frames;
|
||||
visitedFrames = new Set(config.visitedFrames);
|
||||
if(config.visitedFrames){
|
||||
visitedFrames = new Set(config.visitedFrames);
|
||||
}
|
||||
else {
|
||||
visitedFrames = new Set([Object.keys(config.frames).sort()[0]])
|
||||
}
|
||||
visitedFramesNumber = new Set(Array.from(Array(visitedFrames).keys()));
|
||||
} else {
|
||||
videotagging.inputframes = {};
|
||||
@@ -461,17 +461,17 @@ function openPath(pathName, isDir, isRecords = false) {
|
||||
});
|
||||
}
|
||||
visitedFrames = new Set([videotagging.imagelist[0]]);
|
||||
visitedFramesNumber = new Set([0])
|
||||
} else {
|
||||
visitedFrames = new Set();
|
||||
visitedFramesNumber = new Set();
|
||||
}
|
||||
// visitedFrames = (isDir) ? new Set([pathName]) : new Set();
|
||||
}
|
||||
|
||||
if (isDir){
|
||||
$('title').text(`Image Tagging Job: ${path.dirname(pathName)}`); //set title indicator
|
||||
if(isRecords) $('title').text(`Image Tagging from Records Job: ${path.dirname(pathName)}`); //set title indicator
|
||||
if(isRecords) {
|
||||
setAppTitle(`Image Tagging from Records Job: ${path.dirname(pathName)}`);
|
||||
} else {
|
||||
setAppTitle(`Image Tagging Job: ${path.dirname(pathName)}`);
|
||||
}
|
||||
|
||||
//get list of images in directory
|
||||
var files = fs.readdirSync(pathName);
|
||||
@@ -493,11 +493,13 @@ function openPath(pathName, isDir, isRecords = false) {
|
||||
});
|
||||
}
|
||||
|
||||
$('head title').text(`Image Tagging Job: ${videotagging.imagelist[0]}`); //set title indicator
|
||||
if(isRecords) $('head title').text(`Image Tagging from Records Job: ${videotagging.imagelist[0]}`); //set title indicator
|
||||
|
||||
if (videotagging.imagelist.length){
|
||||
//Check if tagging was done in previous version of VOTT
|
||||
if(!isNaN(Array.from(visitedFrames)[0])){
|
||||
visitedFramesNumber = visitedFrames;
|
||||
visitedFrames = new Set(Array.from(visitedFramesNumber).map(frame => videotagging.imagelist[parseInt(frame)]))
|
||||
visitedFrames = new Set(Array.from(visitedFrames).map(frame => videotagging.imagelist[parseInt(frame)]))
|
||||
|
||||
//Replace the keys of the frames object
|
||||
Object.keys(videotagging.inputframes).map(function(key, index) {
|
||||
@@ -506,16 +508,19 @@ function openPath(pathName, isDir, isRecords = false) {
|
||||
}, this);
|
||||
}
|
||||
|
||||
videotagging.imagelist = videotagging.imagelist.map((filepath) => {return path.join(pathName,filepath)});
|
||||
videotagging.src = pathName;
|
||||
//track visited frames
|
||||
$("#video-tagging").off("stepFwdClicked-AfterStep", updateVisitedFrames);
|
||||
$("#video-tagging").on("stepFwdClicked-AfterStep", () => {
|
||||
//update title to match src
|
||||
if(videotagging.currTFRecord) {
|
||||
if(!visitedFrames.has(videotagging.getCurrentFrameId())) getRegionsFromRecord(videotagging.currTFRecord);
|
||||
$('title').text(`Image Tagging from Records Job: ${path.basename(videotagging.imagelist[videotagging.imageIndex])}`);
|
||||
} else $('title').text(`Image Tagging Job: ${path.basename(videotagging.curImg.src)}`);
|
||||
if(!visitedFrames.has(videotagging.getCurrentFrameId())) {
|
||||
getRegionsFromRecord(videotagging.currTFRecord);
|
||||
}
|
||||
setAppTitle(`Image Tagging from Records Job: ${path.basename(videotagging.imagelist[videotagging.imageIndex])}`);
|
||||
} else {
|
||||
setAppTitle(`Image Tagging Job: ${path.basename(videotagging.curImg.src)}`);
|
||||
}
|
||||
|
||||
updateVisitedFrames();
|
||||
|
||||
@@ -523,10 +528,14 @@ function openPath(pathName, isDir, isRecords = false) {
|
||||
$("#video-tagging").on("stepBwdClicked-AfterStep", () => {
|
||||
//update title to match src
|
||||
if(videotagging.currTFRecord) {
|
||||
if(!visitedFrames.has(videotagging.getCurrentFrameId())) getRegionsFromRecord(videotagging.currTFRecord);
|
||||
$('title').text(`Image Tagging from Records Job: ${path.basename(videotagging.imagelist[videotagging.imageIndex])}`);
|
||||
if(!visitedFrames.has(videotagging.getCurrentFrameId())) {
|
||||
getRegionsFromRecord(videotagging.currTFRecord);
|
||||
}
|
||||
setAppTitle(`Image Tagging from Records Job: ${path.basename(videotagging.imagelist[videotagging.imageIndex])}`);
|
||||
}
|
||||
else {
|
||||
setAppTitle(`Image Tagging Job: ${path.basename(videotagging.curImg.src)}`);
|
||||
}
|
||||
else $('title').text(`Image Tagging Job: ${path.basename(videotagging.curImg.src)}`);
|
||||
|
||||
});
|
||||
|
||||
@@ -543,7 +552,7 @@ function openPath(pathName, isDir, isRecords = false) {
|
||||
return folderSelected();
|
||||
}
|
||||
} else {
|
||||
$('title').text(`Video Tagging Job: ${path.basename(pathName, path.extname(pathName))}`); //set title indicator
|
||||
setAppTitle(`Video Tagging Job: ${path.basename(pathName, path.extname(pathName))}`);
|
||||
videotagging.disableImageDir();
|
||||
videotagging.src = pathName;
|
||||
//set start time
|
||||
@@ -566,7 +575,6 @@ function openPath(pathName, isDir, isRecords = false) {
|
||||
}
|
||||
|
||||
//init detection
|
||||
//detection = new DetectionExtension(videotagging, visitedFramesNumber);
|
||||
detection = new DetectionExtension(videotagging, visitedFrames);
|
||||
|
||||
$('#load-form-container').hide();
|
||||
@@ -581,12 +589,10 @@ function openPath(pathName, isDir, isRecords = false) {
|
||||
}
|
||||
|
||||
function getRegionsFromRecord(tfRecord){
|
||||
if(videotagging.sourceWidth == 0 || videotagging.sourceHeight == 0){
|
||||
videotagging.sourceWidth = tfRecord.features.feature['image/width'].int64List.value[0];
|
||||
videotagging.sourceHeight = tfRecord.features.feature['image/height'].int64List.value[0];
|
||||
}
|
||||
let widthRatio = videotagging.overlay.width / videotagging.sourceWidth;
|
||||
let heightRatio = videotagging.overlay.height / videotagging.sourceHeight;
|
||||
videotagging.sourceWidth = tfRecord.features.feature['image/width'].int64List.value[0];
|
||||
videotagging.sourceHeight = tfRecord.features.feature['image/height'].int64List.value[0];
|
||||
let widthRatio = videotagging.frameWidth / videotagging.sourceWidth;
|
||||
let heightRatio = videotagging.frameHeight / videotagging.sourceHeight;
|
||||
|
||||
for (let i = 0; i < tfRecord.features.feature['image/object/bbox/xmin'].floatList.value.length; i++) {
|
||||
|
||||
@@ -601,115 +607,6 @@ function getRegionsFromRecord(tfRecord){
|
||||
}
|
||||
}
|
||||
|
||||
async function writeRecord(recordPath, example = null, outputDir = null) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let widthRatio = videotagging.overlay.width / videotagging.sourceWidth;
|
||||
let heightRatio = videotagging.overlay.height / videotagging.sourceHeight;
|
||||
|
||||
const id = recordPath.split(path.sep).pop();
|
||||
outputDir = outputDir ? outputDir : recordPath.split(path.sep).slice(0,-1).join(path.sep);
|
||||
let recordId = id.split(".").slice(0,-1);
|
||||
recordId.push("tfrecord")
|
||||
recordId = recordId.join(".")
|
||||
let outputPath = outputDir + path.sep + recordId;
|
||||
|
||||
if(!example){
|
||||
const regions = videotagging.getRegions(id);
|
||||
const builder = tfrecord.createBuilder();
|
||||
|
||||
let xmin = [];
|
||||
let ymin = [];
|
||||
let xmax = [];
|
||||
let ymax = [];
|
||||
let tags = [];
|
||||
let difficult_obj = [];
|
||||
let truncated = [];
|
||||
let poses = [];
|
||||
|
||||
regions.forEach(region => {
|
||||
xmin.push(region.x1 / videotagging.sourceWidth)
|
||||
ymin.push(region.y1 / videotagging.sourceHeight)
|
||||
xmax.push(region.x2 / videotagging.sourceWidth)
|
||||
ymax.push(region.y2 / videotagging.sourceHeight)
|
||||
tags.push(region.tags[0])
|
||||
difficult_obj.push(0)
|
||||
truncated.push(0)
|
||||
poses.push(encode_Uint8("Unspecified"))
|
||||
});
|
||||
|
||||
getDataUri(recordPath).then((data) => {
|
||||
builder.setIntegers('image/height', [videotagging.sourceHeight]);
|
||||
builder.setIntegers('image/width', [videotagging.sourceWidth]);
|
||||
|
||||
builder.setBinaries('image/filename', [encode_Uint8(id)]);
|
||||
builder.setBinaries('image/source_id', [encode_Uint8(id)]);
|
||||
|
||||
builder.setBinaries('image/key/sha256', [encode_Uint8(CryptoJS.SHA256(data).toString(CryptoJS.enc.Base64))]);
|
||||
|
||||
builder.setBinaries('image/encoded', [data]);
|
||||
|
||||
if(videotagging.currTFRecord) builder.setBytes('image/format', [encode_Uint8(videotagging.getCurrentFrameId().split(".").slice(-2)[0])]);
|
||||
else builder.setBinaries('image/format', [encode_Uint8(videotagging.getCurrentFrameId().split(".").slice(-1)[0])]);
|
||||
|
||||
function getFirstRegion(frames){
|
||||
for(frame of Object.keys(frames)){
|
||||
if(frames[frame] && frames[frame].length) return frames[frame][0]
|
||||
}
|
||||
return {width: videotagging.sourceWidth, height: videotagging.sourceHeight}
|
||||
}
|
||||
|
||||
let firstRegion = getFirstRegion(videotagging.frames)
|
||||
let widthMult = firstRegion.width / videotagging.sourceWidth;
|
||||
let heightMult = firstRegion.height / videotagging.sourceHeight;
|
||||
builder.setFloats('image/object/bbox/xmin', xmin.map((x) => x / widthMult));
|
||||
builder.setFloats('image/object/bbox/ymin', ymin.map((y) => y / heightMult));
|
||||
builder.setFloats('image/object/bbox/xmax', xmax.map((x) => x / widthMult));
|
||||
builder.setFloats('image/object/bbox/ymax', ymax.map((y) => y / heightMult));
|
||||
builder.setBinaries('image/object/class/text', tags.map(tag => encode_Uint8(tag)));
|
||||
builder.setIntegers('image/object/class/label', tags.map(tag => videotagging.inputtagsarray.indexOf(tag)));
|
||||
|
||||
builder.setIntegers('image/object/difficult', difficult_obj);
|
||||
builder.setIntegers('image/object/truncated', truncated);
|
||||
builder.setBinaries('image/object/view', poses);
|
||||
|
||||
example = builder.releaseExample();
|
||||
|
||||
console.log('new example built')
|
||||
return example
|
||||
}).then(async (example) => {
|
||||
// console.log(example)
|
||||
const writer = await tfrecord.createWriter(outputPath);
|
||||
await writer.writeExample(example);
|
||||
await writer.close();
|
||||
resolve();
|
||||
});
|
||||
} else{
|
||||
const writer = await tfrecord.createWriter(outputPath);
|
||||
await writer.writeExample(example);
|
||||
await writer.close();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getDataUri(url) {
|
||||
return new Promise((resolve,reject) => {
|
||||
var image = new Image();
|
||||
|
||||
image.onload = function () {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size
|
||||
canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size
|
||||
|
||||
canvas.getContext('2d').drawImage(this, 0, 0);
|
||||
|
||||
// Get raw image data
|
||||
resolve(canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, ''));
|
||||
};
|
||||
|
||||
image.src = url;
|
||||
})
|
||||
}
|
||||
|
||||
async function readRecord(pathname, recordName) {
|
||||
const reader = await tfrecord.createReader(pathname + path.sep + recordName);
|
||||
@@ -753,8 +650,6 @@ function save() {
|
||||
let regions = videotagging.getRegions(videotagging.getCurrentFrameId());
|
||||
videotagging.sourceHeight = videotagging.currTFRecord.features.feature['image/height'].int64List.value[0]
|
||||
videotagging.sourceWidth = videotagging.currTFRecord.features.feature['image/width'].int64List.value[0]
|
||||
let widthRatio = videotagging.overlay.width / videotagging.sourceWidth;
|
||||
let heightRatio = videotagging.overlay.height / videotagging.sourceHeight;
|
||||
|
||||
regions.forEach(region => {
|
||||
xmin.push(region.x1 / videotagging.sourceWidth)
|
||||
@@ -770,9 +665,6 @@ function save() {
|
||||
videotagging.currTFRecord.features.feature['image/object/bbox/ymax'].floatList.value = ymax;
|
||||
videotagging.currTFRecord.features.feature['image/object/class/text'].bytesList.value = tags;
|
||||
videotagging.recordlist[videotagging.imageIndex] = videotagging.currTFRecord;
|
||||
// writeRecord(videotagging.imagelist[videotagging.imageIndex],videotagging.currTFRecord).then(()=>{
|
||||
// console.log(`record saved: ${videotagging.imagelist[videotagging.imageIndex]}`)
|
||||
// });
|
||||
}
|
||||
setTimeout(() => {
|
||||
saveLock = false;
|
||||
@@ -786,7 +678,6 @@ function encode_Uint8(s) {
|
||||
|
||||
var enc = new TextEncoder();
|
||||
return enc.encode(s)
|
||||
// return Uint8Array.from(s);
|
||||
}
|
||||
|
||||
function decode_Uint8(uint8Arr) {
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
const async = require('async');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
const detectionUtils = require('../detectionUtils.js');
|
||||
|
||||
const DEFAULT_DATA_SET_NAME = 'obj';
|
||||
const CFG_TEMPLATE_FILE_PATH = path.join(__dirname, 'yolo-obj.cfg.template');
|
||||
|
||||
const OBJ_DATA_TEMPLATE = 'classes = %s\n' +
|
||||
'train = data/train.txt\n' +
|
||||
'valid = data/test.txt\n' +
|
||||
'names = data/%s.names\n' +
|
||||
'backup = backup/'
|
||||
|
||||
|
||||
|
||||
// The Exporter interface - provides a mean to export the tagged frames
|
||||
// data in the expected data format of the detection algorithm
|
||||
// Constructor parameters:
|
||||
// exportDirPath - path to the directory where the exported file will be placed
|
||||
// classes - list of classes supported by the tagged data
|
||||
// taggedFramesCount - number of positive tagged frames
|
||||
// frameWidth - The width (in pixels) of the image frame
|
||||
// frameHeight - The height (in pixels) of the image frame
|
||||
function Exporter(exportDirPath, classes, taggedFramesCount, frameWidth, frameHeight, testSplit) {
|
||||
var self = this;
|
||||
self.dataSetName = DEFAULT_DATA_SET_NAME;
|
||||
self.exportDirPath = exportDirPath;
|
||||
self.trainDirPath = path.join(self.exportDirPath, 'train');
|
||||
self.trainImagesDirPath = path.join(self.trainDirPath, 'images');
|
||||
self.trainLabelsDirPath = path.join(self.trainDirPath, 'labels');
|
||||
self.validDirPath = path.join(self.exportDirPath, 'val');
|
||||
self.validImagesDirPath = path.join(self.validDirPath, 'images');
|
||||
self.validLabelsDirPath = path.join(self.validDirPath, 'labels');
|
||||
self.classes = classes;
|
||||
self.taggedFramesCount = taggedFramesCount;
|
||||
self.frameWidth = frameWidth;
|
||||
self.frameHeight = frameHeight;
|
||||
self.testFrameIndices = null;
|
||||
self.testFrameNames = null;
|
||||
self.posFrameLabelIndex = null;
|
||||
self.posFrameImageIndex = null;
|
||||
self.testSplit = testSplit || 0.2;
|
||||
|
||||
// Prepare everything for exporting (e.g. create metadata files,
|
||||
// directories, ..)
|
||||
// Returns: A Promise object that resolves when the operation completes
|
||||
this.init = function init() {
|
||||
self.posFrameLabelIndex = 0;
|
||||
self.posFrameImageIndex = 0;
|
||||
self.testFrameNames = [];
|
||||
self.testFrameIndices = detectionUtils.generateTestIndecies(self.testSplit, taggedFramesCount);
|
||||
self.filesTouched = {}; // Keep track of files we've touched so far
|
||||
return new Promise((resolve, reject) => {
|
||||
async.waterfall([
|
||||
detectionUtils.ensureDirExists.bind(null, self.exportDirPath),
|
||||
detectionUtils.ensureDirExists.bind(null, self.trainDirPath),
|
||||
detectionUtils.ensureDirExists.bind(null, self.trainImagesDirPath),
|
||||
detectionUtils.ensureDirExists.bind(null, self.trainLabelsDirPath),
|
||||
detectionUtils.ensureDirExists.bind(null, self.validDirPath),
|
||||
detectionUtils.ensureDirExists.bind(null, self.validImagesDirPath),
|
||||
detectionUtils.ensureDirExists.bind(null, self.validLabelsDirPath),
|
||||
], (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Export a single frame to the training data
|
||||
// Parameters:
|
||||
// frameFileName - The file name to use when saving the image file
|
||||
// frameBuffer - A buffer with the frame image data
|
||||
// bboxes - a list of bboxes in the format of x1, y1, x2, y2 where the
|
||||
// coordinates are in absolute values of the image
|
||||
// tags - a list of objects containing the tagging data. Each object is in the format of:
|
||||
// {'x1' : int, 'y1' : int, 'x2' : int, 'y2' : int, 'class' : string 'w': int, 'h' :int}
|
||||
// Where (x1, y1) and (x2, y2) are the coordinates of the top left and bottom right corner
|
||||
// and w, h are optional overloads for the frame demensions of the bounding boxes (respectively)
|
||||
// and 'class' is the name of the class
|
||||
// Returns: A Promise object that resolves when the operation completes
|
||||
this.exportFrame = function exportFrame(frameFileName, frameBuffer, tags) {
|
||||
return new Promise((resolve, reject) => {
|
||||
async.waterfall([
|
||||
detectionUtils.ensureDirExists.bind(null, self.trainImagesDirPath),
|
||||
detectionUtils.ensureDirExists.bind(null, self.trainLabelsDirPath),
|
||||
detectionUtils.ensureDirExists.bind(null, self.validImagesDirPath),
|
||||
detectionUtils.ensureDirExists.bind(null, self.validLabelsDirPath),
|
||||
function saveImage(cb) {
|
||||
var isTestFrame = (self.testFrameIndices.includes(self.posFrameImageIndex));
|
||||
var outputDirPath = (isTestFrame ? self.validImagesDirPath : self.trainImagesDirPath)
|
||||
var imageFilePath = path.join(outputDirPath, frameFileName);
|
||||
fs.writeFile(imageFilePath, frameBuffer, cb);
|
||||
self.posFrameImageIndex++;
|
||||
if(isTestFrame) {
|
||||
self.testFrameNames.push(frameFileName);
|
||||
}
|
||||
},
|
||||
function saveLabel(cb) {
|
||||
var isTestFrame = (self.testFrameNames.includes(frameFileName));
|
||||
var outputDirPath = (isTestFrame ? self.validLabelsDirPath : self.trainLabelsDirPath);
|
||||
var labelFileName = path.parse(frameFileName).name + '.txt';
|
||||
var labelFilePath = path.join(outputDirPath, labelFileName);
|
||||
var labelData = '';
|
||||
for (var i in tags) {
|
||||
if (i > 0) {
|
||||
labelData += '\n';
|
||||
}
|
||||
var tag = tags[i];
|
||||
var type = tag.class
|
||||
var truncated = 0
|
||||
var occluded = 0
|
||||
var alpha = 0
|
||||
var bbox = [tag.x1, tag.y1, tag.x2, tag.y2];
|
||||
var dimensions = [0, 0, 0]
|
||||
var location = [0, 0, 0]
|
||||
var rotation_y = 0
|
||||
labelData += util.format('%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s',
|
||||
type, truncated.toFixed(1), occluded, alpha.toFixed(1),
|
||||
bbox[0].toFixed(2), bbox[1].toFixed(2), bbox[2].toFixed(2), bbox[3].toFixed(2),
|
||||
dimensions[0].toFixed(1), dimensions[1].toFixed(1), dimensions[2].toFixed(1),
|
||||
location[0].toFixed(1), location[1].toFixed(1), location[2].toFixed(1),
|
||||
rotation_y.toFixed(1));
|
||||
}
|
||||
fs.writeFile(labelFilePath, labelData, cb);
|
||||
self.posFrameLabelIndex++;
|
||||
},
|
||||
], (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exports.Exporter = Exporter;
|
||||
@@ -0,0 +1,2 @@
|
||||
module.exports.Exporter = require('./exporter.js').Exporter
|
||||
module.exports.displayName = 'KITTI'
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"main" : "main.js"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
const KittiExporter = require('../main.js').Exporter;
|
||||
const path = require('path');
|
||||
|
||||
var exporter = new KittiExporter(path.join(__dirname, 'export_test'), ['cat', 'dog', 'mouse'], 1000, 1000);
|
||||
|
||||
var p = exporter.init();
|
||||
p.then(() => {
|
||||
var p1 = exporter.exportFrame('1.jpg', 'test_data',
|
||||
[{class : 'dog', x1 : 300, y1 : 250, x2 : 400, y2: 500}]);
|
||||
p1.then(()=> {
|
||||
var p2 = exporter.exportFrame('2.jpg', 'test_data_2',
|
||||
[{class : 'mouse', x1 : 600, y1 : 310, x2 : 720, y2: 492}]);
|
||||
p2.then(() => {console.info('done')}, (err) => {console.info('err', err)});
|
||||
}, (err) => {console.info('err:', err)});
|
||||
}, (err) => {
|
||||
console.info('Error occured during init:', err);
|
||||
});
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ function Exporter(exportDirPath, classes, taggedFramesCount, frameWidth, frameHe
|
||||
async.waterfall(
|
||||
[
|
||||
//clear old write
|
||||
async.each.bind(null, [path.join(self.jpgImgsDirPath, frameFileName), `${path.join(self.annDirPath, frameFileName).slice(0, -4)}.xml`], detectionUtils.deleteFileIfExists),
|
||||
async.each.bind(null, [path.join(self.jpgImgsDirPath, frameFileName), `${path.join(self.annDirPath, frameFileName).slice(0, -4).replace('.','')}.xml`], detectionUtils.deleteFileIfExists),
|
||||
|
||||
function saveImage(cb) {
|
||||
var imageFilePath = path.join(self.jpgImgsDirPath, frameFileName);
|
||||
@@ -88,7 +88,7 @@ function Exporter(exportDirPath, classes, taggedFramesCount, frameWidth, frameHe
|
||||
function saveBboxes(cb) {
|
||||
xmlData = `<annotation verified="yes">
|
||||
<folder>Annotation</folder>
|
||||
<filename>${frameFileName.slice(0, -4)}</filename>
|
||||
<filename>${frameFileName.slice(0, -4).replace('.','')}</filename>
|
||||
<path>${path.join(self.jpgImgsDirPath, frameFileName)}</path>
|
||||
<source>
|
||||
<database>Unknown</database>
|
||||
@@ -117,12 +117,12 @@ function Exporter(exportDirPath, classes, taggedFramesCount, frameWidth, frameHe
|
||||
`
|
||||
}
|
||||
xmlData += '</annotation\>'
|
||||
var outpath = `${path.join(self.annDirPath, frameFileName).slice(0, -4)}.xml`
|
||||
var outpath = `${path.join(self.annDirPath, frameFileName).slice(0, -4).replace('.','')}.xml`
|
||||
fs.writeFile(outpath, xmlData, cb);
|
||||
},
|
||||
function cleanOldMainData(cb){
|
||||
replace({
|
||||
regex: new RegExp(`^${frameFileName.slice(0, -4)}+(.*)\s*[\r\n]`,'m'),
|
||||
regex: new RegExp(`^${frameFileName.slice(0, -4).replace('.','')}+(.*)\s*[\r\n]`,'m'),
|
||||
replacement: "",
|
||||
paths: [self.mainDirPath],
|
||||
recursive: true,
|
||||
@@ -140,7 +140,7 @@ function Exporter(exportDirPath, classes, taggedFramesCount, frameWidth, frameHe
|
||||
var classPath = path.join(self.mainDirPath, element);
|
||||
// determine wheter to write to train or test path
|
||||
var outPath = self.testFrameIndecies.includes(self.posFrameIndex) ? `${classPath}_val.txt` : `${classPath}_train.txt`;
|
||||
var line = tagsClassSet.has(element) ? `${frameFileName.slice(0, -4)} 1\r\n` : `${frameFileName.slice(0, -4)} -1\r\n` ;
|
||||
var line = tagsClassSet.has(element) ? `${frameFileName.slice(0, -4).replace('.','')} 1\r\n` : `${frameFileName.slice(0, -4).replace('.','')} -1\r\n` ;
|
||||
outs.push({'outPath':outPath, 'line':line});
|
||||
});
|
||||
async.each(outs, (out, callback)=>{
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
const async = require('async');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const replace = require("replace");
|
||||
const detectionUtils = require('../detectionUtils.js');
|
||||
const tfrecord = require('tfrecord');
|
||||
const CryptoJS = require("crypto-js");
|
||||
|
||||
// The Exporter interface - provides a mean to export the tagged frames
|
||||
// data in the expected data format of the detection algorithm
|
||||
// Constructor parameters:
|
||||
// exportDirPath - path to the directory where the exported file will be placed
|
||||
// classes - list of classes supported by the tagged data
|
||||
// taggedFramesCount - number of positive tagged frames
|
||||
// frameWidth - The width (in pixels) of the image frame
|
||||
// frameHeight - The height (in pixels) of the image frame
|
||||
// testSplit - the percent of tragged frames to reserve for test set defaults to 20%
|
||||
function Exporter(exportDirPath, classes, taggedFramesCount, frameWidth, frameHeight, testSplit) {
|
||||
var self = this;
|
||||
self.exportDirPath = exportDirPath;
|
||||
self.labelMap = path.join(self.exportDirPath,"tf_label_map.pbtxt");
|
||||
self.classes = classes;
|
||||
self.taggedFramesCount = taggedFramesCount;
|
||||
self.frameWidth = frameWidth;
|
||||
self.frameHeight = frameHeight;
|
||||
self.testSplit = testSplit || 0.2;
|
||||
self.posFrameIndex = null;
|
||||
self.testFrameIndecies = null;
|
||||
|
||||
// Prepare everything for exporting (e.g. create metadata files,
|
||||
// directories, ..)
|
||||
// Returns: A Promise object that resolves when the operation completes
|
||||
this.init = function init() {
|
||||
self.posFrameIndex = 0;
|
||||
self.testFrameIndecies = detectionUtils.generateTestIndecies(self.testSplit, taggedFramesCount);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
async.waterfall([
|
||||
async.eachSeries.bind(null,[self.exportDirPath], detectionUtils.ensureDirExists),
|
||||
detectionUtils.deleteFileIfExists.bind(null,self.labelMap),
|
||||
function generateLabelMap(cb){
|
||||
var labelMapData = '';
|
||||
classes.forEach((element,i)=> {
|
||||
labelMapData += `item {\r\n id: ${i+1}\r\n name: '${element}'\r\n}\r\n`
|
||||
});
|
||||
fs.appendFile(self.labelMap, labelMapData, cb);
|
||||
}
|
||||
], (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Export a single frame to the training data
|
||||
// Parameters:
|
||||
// frameFileName - The file name to use when saving the image file
|
||||
// frameBuffer - A buffer with the frame image data
|
||||
// bboxes - a list of bboxes in the format of x1, y1, x2, y2 where the
|
||||
// coordinates are in absolute values of the image
|
||||
// tags - a list of objects containing the tagging data. Each object is in the format of:
|
||||
// {'x1' : int, 'y1' : int, 'x2' : int, 'y2' : int, 'class' : string}
|
||||
// Where (x1,y1) and (x2,y2) are the coordinates of the top left and bottom right corner
|
||||
// of the bounding boxes (respectively), and 'class' is the name of the class.
|
||||
// Returns: A Promise object that resolves when the operation completes
|
||||
this.exportFrame = function exportFrame(frameFileName, frameBuffer, tags) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var frameName = path.parse(frameFileName).base;
|
||||
var b64 = frameBuffer.toString('base64');
|
||||
async.waterfall([
|
||||
function buildRecord(cb) {
|
||||
const builder = tfrecord.createBuilder();
|
||||
|
||||
let xmin = [];
|
||||
let ymin = [];
|
||||
let xmax = [];
|
||||
let ymax = [];
|
||||
let classes = [];
|
||||
let difficult_obj = [];
|
||||
let truncated = [];
|
||||
let poses = [];
|
||||
|
||||
tags.forEach(tag => {
|
||||
xmin.push(tag.x1 / tag.w)
|
||||
ymin.push(tag.y1 / tag.h)
|
||||
xmax.push(tag.x2 / tag.w)
|
||||
ymax.push(tag.y2 / tag.h)
|
||||
classes.push(tag.class)
|
||||
difficult_obj.push(0)
|
||||
truncated.push(0)
|
||||
poses.push(encode_Uint8("Unspecified"))
|
||||
});
|
||||
if(tags.length){
|
||||
builder.setIntegers('image/height', [tags[0].h]);
|
||||
builder.setIntegers('image/width', [tags[0].w]);
|
||||
} else {
|
||||
builder.setIntegers('image/height', [self.frameHeight]);
|
||||
builder.setIntegers('image/width', [self.frameWidth]);
|
||||
}
|
||||
|
||||
|
||||
builder.setBinaries('image/filename', [encode_Uint8(frameName)]);
|
||||
builder.setBinaries('image/source_id', [encode_Uint8(frameName)]);
|
||||
|
||||
builder.setBinaries('image/key/sha256', [encode_Uint8(CryptoJS.SHA256(b64).toString(CryptoJS.enc.Base64))]);
|
||||
|
||||
builder.setBinaries('image/encoded', [b64]);
|
||||
|
||||
if (videotagging.currTFRecord) {
|
||||
builder.setBytes('image/format', [encode_Uint8(videotagging.getCurrentFrameId().split(".").slice(-2)[0])]);
|
||||
} else if(videotagging.imagelist){
|
||||
builder.setBinaries('image/format', [encode_Uint8(videotagging.getCurrentFrameId().split(".").slice(-1)[0])]);
|
||||
} else {
|
||||
builder.setBinaries('image/format', [encode_Uint8('mp4')]);
|
||||
}
|
||||
|
||||
|
||||
builder.setFloats('image/object/bbox/xmin', xmin);
|
||||
builder.setFloats('image/object/bbox/ymin', ymin);
|
||||
builder.setFloats('image/object/bbox/xmax', xmax);
|
||||
builder.setFloats('image/object/bbox/ymax', ymax);
|
||||
builder.setBinaries('image/object/class/text', classes.map(tag => encode_Uint8(tag)));
|
||||
builder.setIntegers('image/object/class/label', classes.map(tag => videotagging.inputtagsarray.indexOf(tag)));
|
||||
|
||||
builder.setIntegers('image/object/difficult', difficult_obj);
|
||||
builder.setIntegers('image/object/truncated', truncated);
|
||||
builder.setBinaries('image/object/view', poses);
|
||||
|
||||
example = builder.releaseExample();
|
||||
|
||||
console.log('new example built')
|
||||
cb(null,example);
|
||||
},
|
||||
async function writeRecord(example){
|
||||
let outputPath = self.exportDirPath + path.sep + frameName + '.tfrecord';
|
||||
|
||||
const writer = await tfrecord.createWriter(outputPath);
|
||||
await writer.writeExample(example);
|
||||
await writer.close();
|
||||
}
|
||||
], (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
exports.Exporter = Exporter;
|
||||
@@ -0,0 +1,2 @@
|
||||
module.exports.Exporter = require('./exporter.js').Exporter
|
||||
module.exports.displayName = 'TFRecords'
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"main": "main.js"
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
const CNKTExporter = require('../main.js').Exporter;
|
||||
const path = require('path');
|
||||
|
||||
var exporter = new Pascal_PoCExporter(path.join(__dirname, 'export_test'), ['cat', 'dog', 'mouse'], 1000, 1000);
|
||||
|
||||
var p = exporter.init();
|
||||
p.then(() => {
|
||||
var p1 = exporter.exportFrame('1.jpg', 'test_data',
|
||||
[{class : 'dog', x1 : 300, y1 : 250, x2 : 400, y2: 500}]);
|
||||
p1.then(()=> {
|
||||
var p2 = exporter.exportFrame('2.jpg', 'test_data_2',
|
||||
[{class : 'mouse', x1 : 600, y1 : 310, x2 : 720, y2: 492}]);
|
||||
p2.then(() => {console.info('done')}, (err) => {console.info('err', err)});
|
||||
}, (err) => {console.info('err:', err)});
|
||||
}, (err) => {
|
||||
console.info('Error occured during init:', err);
|
||||
});
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ function Detection(videotagging, visitedFrames) {
|
||||
var self = this;
|
||||
|
||||
//maps every frame in the video to an imageCanvas until a specified point NOTE mapVideo clears the oncanplay eventListener
|
||||
this.mapVideo = function (frameHandler, until) {
|
||||
this.mapVideo = function (frameHandler, isLastFrame) {
|
||||
return new Promise((resolve, reject) => {
|
||||
//init canvas buffer
|
||||
var frameCanvas = document.createElement("canvas");
|
||||
@@ -23,36 +23,16 @@ function Detection(videotagging, visitedFrames) {
|
||||
self.videotagging.video.currentTime = 0;
|
||||
self.videotagging.playingCallback();
|
||||
|
||||
//resolve export until
|
||||
var isLastFrame;
|
||||
if (until === "tagged") {
|
||||
isLastFrame = function (frameId) {
|
||||
return (!Object.keys(self.videotagging.frames).length) || (frameId >= parseInt(Object.keys(self.videotagging.frames)[Object.keys(self.videotagging.frames).length - 1]));
|
||||
}
|
||||
}
|
||||
else if (until === "visited") {
|
||||
isLastFrame = function (frameId) {
|
||||
var lastVisitedFrameId = Math.max.apply(Math, Array.from(self.visitedFrames));
|
||||
return (frameId >= lastVisitedFrameId);
|
||||
}
|
||||
}
|
||||
else { //last
|
||||
isLastFrame = function (frameId) {
|
||||
return (self.videotagging.video.currentTime >= self.videotagging.video.duration);
|
||||
}
|
||||
}
|
||||
|
||||
function iterateFrames() {
|
||||
var frameId = self.videotagging.getCurrentFrameNumber();
|
||||
var lastFrame = isLastFrame(frameId);
|
||||
|
||||
var frameNumber = self.videotagging.getCurrentFrameNumber();
|
||||
var lastFrame = isLastFrame(frameNumber);
|
||||
if (lastFrame) {
|
||||
self.videotagging.video.oncanplay = null;
|
||||
resolve();
|
||||
}
|
||||
|
||||
var frameName = `${path.basename(self.videotagging.src, path.extname(self.videotagging.src))}_frame_${frameId}.jpg`
|
||||
frameHandler(frameName, frameId, frameCanvas, canvasContext, (err) => {
|
||||
var frameName = `${path.basename(self.videotagging.src, path.extname(self.videotagging.src))}_frame_${frameNumber}.jpg`
|
||||
frameHandler(frameName, frameNumber, frameCanvas, canvasContext, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
@@ -65,28 +45,32 @@ function Detection(videotagging, visitedFrames) {
|
||||
}
|
||||
|
||||
//this maps dir of images for exporting
|
||||
this.mapDir = function (frameHandler, dir) {
|
||||
this.mapDir = function (frameHandler, dir, isLastFrame) {
|
||||
return new Promise((resolve, reject) => {
|
||||
imagesProcessed = 0;
|
||||
dir.forEach((imagePath, index) => {
|
||||
var img = new Image();
|
||||
img.src = imagePath;
|
||||
img.onload = function () {
|
||||
var frameCanvas = document.createElement("canvas");
|
||||
frameCanvas.width = img.width;
|
||||
frameCanvas.height = img.height;
|
||||
// Copy the image contents to the canvas
|
||||
var canvasContext = frameCanvas.getContext("2d");
|
||||
canvasContext.drawImage(img, 0, 0);
|
||||
frameHandler(path.basename(imagePath), index, frameCanvas, canvasContext, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
imagesProcessed += 1;
|
||||
if (imagesProcessed == dir.length) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
if(!isLastFrame(index)){
|
||||
var img = new Image();
|
||||
img.src = imagePath;
|
||||
img.onload = function () {
|
||||
var frameCanvas = document.createElement("canvas");
|
||||
frameCanvas.width = img.width;
|
||||
frameCanvas.height = img.height;
|
||||
// Copy the image contents to the canvas
|
||||
var canvasContext = frameCanvas.getContext("2d");
|
||||
canvasContext.drawImage(img, 0, 0);
|
||||
frameHandler(path.basename(imagePath), index, frameCanvas, canvasContext, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
imagesProcessed += 1;
|
||||
if (isLastFrame(imagesProcessed)) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -104,14 +88,40 @@ function Detection(videotagging, visitedFrames) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
}
|
||||
//resolve export until
|
||||
var isLastFrame;
|
||||
if (exportUntil === "tagged") {
|
||||
isLastFrame = function (frameId) {
|
||||
let taggedFrames = Object.keys(self.videotagging.frames)
|
||||
.filter(key => self.videotagging.frames[key].length)
|
||||
.reduce((res, key) => (res[key] = self.videotagging.frames[key], res), {}); // eslint-disable-line
|
||||
let condition = (self.videotagging.imagelist) ? self.videotagging.imagelist.indexOf(Object.keys(taggedFrames)[Object.keys(taggedFrames).length - 1]) : parseInt(Object.keys(taggedFrames)[Object.keys(taggedFrames).length - 1])
|
||||
return (!Object.keys(taggedFrames).length) || (frameId >= condition)
|
||||
}
|
||||
}
|
||||
else if (exportUntil === "visited") {
|
||||
isLastFrame = function (frameId) {
|
||||
let lastVisitedFrameId = (self.videotagging.imagelist) ? self.videotagging.imagelist.indexOf(Array.from(self.visitedFrames)[Array.from(self.visitedFrames).length -1]) : Math.max.apply(Math, Array.from(visitedFrames));
|
||||
return (frameId >= lastVisitedFrameId);
|
||||
}
|
||||
}
|
||||
else { //last
|
||||
isLastFrame = function (frameId) {
|
||||
if(self.videotagging.imagelist){
|
||||
return (self.videotagging.imageIndex >= self.videotagging.imagelist.length);
|
||||
} else{
|
||||
return (self.videotagging.video.currentTime + 1 >= self.videotagging.video.duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dir) {
|
||||
this.mapDir(exportFrame.bind(err, exporter), dir).then(exportFinished, (err) => {
|
||||
this.mapDir(exportFrame.bind(err, exporter), dir, isLastFrame).then(exportFinished, (err) => {
|
||||
console.info(`Error on ${method} init:`, err);
|
||||
cb(err);
|
||||
});
|
||||
|
||||
} else {
|
||||
this.mapVideo(exportFrame.bind(err, exporter), exportUntil).then(exportFinished, (err) => {
|
||||
this.mapVideo(exportFrame.bind(err, exporter), isLastFrame).then(exportFinished, (err) => {
|
||||
console.info(`Error on ${method} init:`, err);
|
||||
cb(err);
|
||||
});
|
||||
@@ -149,15 +159,13 @@ function Detection(videotagging, visitedFrames) {
|
||||
//var stanH = (self.videotagging.imagelist) ? frameCanvas.height / region.height : self.videotagging.video.videoHeight / region.height;
|
||||
var tag = {
|
||||
class: region.tags[region.tags.length - 1],
|
||||
x1: parseInt(region.x1 * stanW),
|
||||
y1: parseInt(region.y1 * stanH),
|
||||
x2: parseInt(region.x2 * stanW),
|
||||
y2: parseInt(region.y2 * stanH)
|
||||
x1: region.box.x1,
|
||||
y1: region.box.y1,
|
||||
x2: region.box.x2,
|
||||
y2: region.box.y2,
|
||||
w: region.width,
|
||||
h: region.height
|
||||
};
|
||||
if (self.videotagging.imagelist) {
|
||||
tag.w = parseInt(frameCanvas.width);
|
||||
tag.h = parseInt(frameCanvas.height);
|
||||
}
|
||||
frameTags.push(tag);
|
||||
|
||||
});
|
||||
|
||||
@@ -18,59 +18,100 @@
|
||||
<body>
|
||||
<title>Help</title>
|
||||
<div id ='help-container' style = "padding: 20px 20px 20px 20px;">
|
||||
|
||||
<div class="key-group">
|
||||
<label for="exampleTextarea" title="(Which keys to press to make stuff happen)">Keyboard Shortcuts: </label>
|
||||
<h2>App/Frame level shortcuts</h2>
|
||||
<div class="app-group">
|
||||
<h3>Open a file or directory</h3>
|
||||
<ul>
|
||||
<li><strong>Open Video:</strong> Ctrl + O</li>
|
||||
<li><strong>Open Image Directory:</strong> Ctrl + I</li>
|
||||
<li><strong>Save Tags:</strong> Ctrl + S</li>
|
||||
<li><strong>Toggle Play/Pause:</strong> Space</li>
|
||||
<li><strong>Toggle Tracking:</strong> Ctrl + T</li>
|
||||
<li><strong>Toggle Exclusive Add Mode:</strong> Crtl + N</li>
|
||||
<li><strong>Export Tags:</strong> Ctrl + E</li>
|
||||
<li><strong>Active Learning:</strong> Ctrl + L</li>
|
||||
<li><strong>Open TFRecord Directory:</strong> Ctrl + Space</li>
|
||||
</ul>
|
||||
|
||||
<h3>Frame control</h3>
|
||||
<ul>
|
||||
<li><strong>Next Frame:</strong>Right Arrow or E</li>
|
||||
<li><strong>Previous Frame:</strong>Left Arrow or Q</li>
|
||||
<li><strong>Duplicate last frame regions:</strong> D</li>
|
||||
<li><strong>Delete Frame:</strong> Shift + Del</li>
|
||||
<li><strong>Active Learning:</strong> Ctrl + L</li>
|
||||
</ul>
|
||||
|
||||
<h3>Tags collection</h3>
|
||||
<ul>
|
||||
<li><strong>Save Tags:</strong> Ctrl + S</li>
|
||||
<li><strong>Export Tags:</strong> Ctrl + E</li>
|
||||
</ul>
|
||||
|
||||
<h3>Play control</h3>
|
||||
<ul>
|
||||
<li><strong>Toggle Play/Pause:</strong> Space</li>
|
||||
<li><strong>Toggle Tracking:</strong> Ctrl + T</li>
|
||||
</ul>
|
||||
|
||||
<h3>App control</h3>
|
||||
<ul>
|
||||
<li><strong>Open Developer Console:</strong> Ctrl + D</li>
|
||||
<li><strong>Refresh App:</strong> Ctrl + Space</li>
|
||||
<li><strong>Refresh App:</strong> Ctrl + R</li>
|
||||
<li><strong>Show Help:</strong> Ctrl + H</li>
|
||||
<li><strong>Select All:</strong> Ctrl + (A or 1 on number pad)</li>
|
||||
<li><strong>Cut:</strong> Ctrl + X</li>
|
||||
<li><strong>Copy:</strong> Ctrl + C</li>
|
||||
<li><strong>Paste:</strong> Ctrl + V</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mouse-group">
|
||||
<label for="exampleTextarea">Mouse Commands: </label>
|
||||
<h2>Region level shortcuts</h2>
|
||||
<div class="selection-group">
|
||||
<h3>Region creation tools (rectangular)</h3>
|
||||
<ul>
|
||||
<li><strong>Two-point mode:</strong> Hold down Ctrl while creating a region</li>
|
||||
<li><strong>Square mode:</strong> Hold down Shift while creating a region</li>
|
||||
<li><strong>Multi-select:</strong> Hold down Shift while selecting regions</li>
|
||||
<li><strong>Exclusive Tracking mode:</strong> Crtl + N to block frame UI allowing a user to create a region on top of existing regions</li>
|
||||
<li><strong>Rectangular box:</strong> R key.
|
||||
<ul>
|
||||
<li><strong>Two-point mode:</strong> Hold down Ctrl while creating a region</li>
|
||||
<li><strong>Square mode:</strong> Hold down Shift while creating a region</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Template based box:</strong> T key.</li>
|
||||
<li><strong>Point surrounding box:</strong> P key.</li>
|
||||
<li><strong>Polyline surrounding box:</strong> Y key.</li>
|
||||
<li><strong>Exclusive selection:</strong> L key to block/unblock regions manipulation.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="arrow-group">
|
||||
<label for="exampleTextarea">Region Manipulation with Arrow Keys: </label>
|
||||
<p>VOTT allows you to fine tune the bounding boxes using the arrow keys in a few different ways. While a region is selected:</p>
|
||||
<ul>
|
||||
<li><strong>Move region:</strong> Hold Ctrl + Direction</li>
|
||||
<li><strong>Shrink region::</strong> Hold Ctrl + Alt + Direction</li>
|
||||
<li><strong>Expand region:</strong> Hold Ctrl + Shift + Direction</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="visual-group">
|
||||
<label for="exampleTextarea">Styling and Visual manipulations: </label>
|
||||
<p>Tuning visual display of regions:
|
||||
<ul>
|
||||
<li><strong>Toggle region background:</strong> Ctrl + B</li>
|
||||
</ul>
|
||||
|
||||
<div class="manipulation-group">
|
||||
<h3>Region selection & copying</h3>
|
||||
<ul>
|
||||
<li><strong>Multi-select:</strong> Hold down Shift while selecting regions</li>
|
||||
<li><strong>Select next:</strong> Tab</li>
|
||||
<li><strong>Select All:</strong> Ctrl + A</li>
|
||||
<li><strong>Cut:</strong> Ctrl + X</li>
|
||||
<li><strong>Copy:</strong> Ctrl + C</li>
|
||||
<li><strong>Paste:</strong> Ctrl + V</li>
|
||||
</ul>
|
||||
|
||||
<h3>Region Manipulation with Arrow Keys</h3>
|
||||
<p>VOTT allows you to fine tune the bounding boxes using the arrow keys in a few different ways. While a region is selected:</p>
|
||||
<ul>
|
||||
<li><strong>Move region:</strong> Hold Ctrl + Direction</li>
|
||||
<li><strong>Shrink region::</strong> Hold Ctrl + Alt + Direction</li>
|
||||
<li><strong>Expand region:</strong> Hold Ctrl + Shift + Direction</li>
|
||||
</ul>
|
||||
|
||||
<h3>Styling and Visual manipulations</h3>
|
||||
<p>Tuning visual display of regions:
|
||||
<ul>
|
||||
<li><strong>Toggle region background:</strong> Ctrl + B</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Tags level shortcuts</h2>
|
||||
<div class="tag-group">
|
||||
<label for="exampleTextarea">Tagging: </label>
|
||||
<p>Use the number keys to quickly tag a selected region <br><i>(Only works for single digits 0-9</i>)</p>
|
||||
<h3>Tagging: </h3>
|
||||
<ul>
|
||||
<li><strong>Quick access to tags</strong>: 1-9 numeric keys</li>
|
||||
<li><strong>Auto applying a tag</strong>: Alt + 1-9 numeric key</li>
|
||||
</ul>
|
||||
|
||||
<h3>Changing Tags</h3>
|
||||
<ul>
|
||||
<li><strong>Change mapped hotkey</strong>: hold Shift + 1-9 numeric key <i>and</i> click on the tag that you want to assign to that hotkey.</li>
|
||||
<li><strong>Delete tag from the list</strong>: Ctrl + click on the tag you want removed. It will remove the tag from the list and untag any images that use that tag.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="cancelButton" class="btn btn-primary" style="display:inline-block;">Cancel</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -9,7 +9,6 @@ window.onload = function(){
|
||||
ipcRenderer.on('configs', (event, configs) => {
|
||||
$('#output').val(configs.assetFolder);
|
||||
$('#format').empty(); // remove old options
|
||||
$('#format').append($("<option></option>").attr("value", "tfrecord").text("TF Record"));
|
||||
configs.supportedFormats.forEach( (algorithm) => {
|
||||
$('#format').append($("<option></option>").attr("value", algorithm).text(algorithm));
|
||||
});
|
||||
@@ -22,11 +21,7 @@ function getExportConfiguration() {
|
||||
exportPath: $('#output').val(),
|
||||
exportFormat: $('#format').val()
|
||||
};
|
||||
if(exportConfig.exportFormat === "tfrecord") {
|
||||
ipcRenderer.send('export-records', exportConfig);
|
||||
} else {
|
||||
ipcRenderer.send('export-tags', exportConfig);
|
||||
}
|
||||
ipcRenderer.send('export-tags', exportConfig);
|
||||
closeWindow();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
style="isolation:isolate" viewBox="0 0 64 64" width="64" height="64">
|
||||
<g>
|
||||
<circle cx="52" cy="12" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle cx="52" cy="52" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle cx="12" cy="12" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle cx="12" cy="52" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
|
||||
<line x1="12" y1="16" x2="12" y2="48" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="16" y1="12" x2="48" y2="12" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="52" y1="16" x2="52" y2="48" stroke-width="1" stroke="rgb(0,0,0)"/>
|
||||
<line x1="16" y1="52" x2="48" y2="52" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
|
||||
<line x1="16" y1="16" x2="48" y2="48" stroke-width="1" stroke="rgb(0,0,0)" stroke-dasharray="3" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.0 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 64 64" width="64" height="64">
|
||||
<g >
|
||||
<g>
|
||||
<line class="" x1="8" y1="20" x2="8" y2="56" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line class="" x1="8" y1="56" x2="44" y2="56" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line class="" x1="8" y1="20" x2="12" y2="20" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line class="" x1="44" y1="56" x2="44" y2="52" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
</g>
|
||||
<g>
|
||||
<circle class="accent-f" cx="52" cy="12" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="52" cy="44" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="20" cy="12" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="20" cy="44" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="20" y1="16" x2="20" y2="24" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="24" y1="12" x2="32" y2="12" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="48" y1="12" x2="40" y2="12" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="52" y1="16" x2="52" y2="24" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="20" y1="40" x2="20" y2="32" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="24" y1="44" x2="32" y2="44" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="48" y1="44" x2="40" y2="44" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="52" y1="40" x2="52" y2="32" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
</g>
|
||||
<g>
|
||||
<line x1="36" y1="8" x2="36" y2="48" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="16" y1="28" x2="56" y2="28" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
</g>
|
||||
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 2.0 KiB |
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 64 64" width="64" height="64">
|
||||
<defs>
|
||||
<clipPath id="_clipPath_xT69JNLss05WYPqZrC8QiRch78i8vo4U">
|
||||
<rect width="64" height="64"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#_clipPath_xT69JNLss05WYPqZrC8QiRch78i8vo4U)">
|
||||
<g/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<g>
|
||||
<circle cx="12" cy="28" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle cx="12" cy="52" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle cx="52" cy="52" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
</g>
|
||||
<line x1="12" y1="32" x2="12" y2="48" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="16" y1="52" x2="48" y2="52" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="16" y1="28" x2="28" y2="28" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<path class="accent-f" d=" M 39 38 L 42 37 L 39 28 L 46 29 L 32 12 L 31 34 L 36 29 L 39 38 Z " fill="rgb(255,255,255)" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<g>
|
||||
<path d=" M 28 12 C 28 9.725 29.831 8 32 8 C 34.169 8 36 9.962 36 12" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="12" y1="12" x2="12" y2="16" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="12" y1="12" x2="16" y2="12" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="20" y1="12" x2="24" y2="12" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="12" y1="20" x2="12" y2="24" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="32" y1="52" x2="32" y2="48" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="32" y1="44" x2="32" y2="40" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
</g>
|
||||
<line x1="52" y1="48" x2="52" y2="28.5" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="50" y1="28" x2="52" y2="28" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 2.1 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 64 64" width="64" height="64">
|
||||
<g>
|
||||
<line x1="32" y1="8" x2="32" y2="16" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="32" y1="48" x2="32" y2="56" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="48" y1="32" x2="56" y2="32" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="8" y1="32" x2="16" y2="32" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="32" cy="32" r="4" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 694 B |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 64 64" width="64" height="64">
|
||||
<g>
|
||||
<path d=" M 20 12 L 12 32 L 24 52 L 52 40 L 48 16 L 20 12 Z " fill="rgb(255,255,255)" fill-opacity="0.5" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="52" cy="40" r="4" fill="rgb(255,255,255)" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="20" cy="12" r="4" fill="rgb(255,255,255)" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="48" cy="16" r="4" fill="rgb(255,255,255)" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="12" cy="32" r="4" fill="rgb(255,255,255)" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="24" cy="52" r="4" fill="rgb(255,255,255)" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 970 B |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 64 64" width="64" height="64">
|
||||
<g >
|
||||
<path d=" M 12 12 L 28 24 L 32 52 L 40 36 L 52 24" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="52" cy="24" r="4" fill="white" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="12" cy="12" r="4" fill="white" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="28" cy="24" r="4" fill="white" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="32" cy="52" r="4" fill="white" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="40" cy="36" r="4" fill="white" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 845 B |
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
style="isolation:isolate" viewBox="0 0 64 64" width="64" height="64">
|
||||
<g>
|
||||
<g>
|
||||
<rect x="12" y="12" width="40" height="40" fill="rgb(255,255,255)" fill-opacity="0.2" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="52" cy="12" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="52" cy="52" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="12" cy="12" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle class="accent-f" cx="12" cy="52" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 807 B |
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 64 64" width="64" height="64">
|
||||
<defs>
|
||||
<clipPath id="_clipPath_GV4tHvkDcbdqnU000cJbyxPo6AT3ZZBk">
|
||||
<rect width="64" height="64"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#_clipPath_GV4tHvkDcbdqnU000cJbyxPo6AT3ZZBk)">
|
||||
<line x1="8" y1="48" x2="8" y2="16" vector-effect="non-scaling-stroke" stroke-width="1" stroke="rgb(0,0,0)" stroke-linejoin="miter" stroke-linecap="square" stroke-miterlimit="3"/>
|
||||
<line x1="8" y1="16" x2="32" y2="16" vector-effect="non-scaling-stroke" stroke-width="1" stroke="rgb(0,0,0)" stroke-linejoin="miter" stroke-linecap="square" stroke-miterlimit="3"/>
|
||||
<line x1="8" y1="48" x2="48" y2="48" vector-effect="non-scaling-stroke" stroke-width="1" stroke="rgb(0,0,0)" stroke-linejoin="miter" stroke-linecap="square" stroke-miterlimit="3"/>
|
||||
<line x1="48" y1="48" x2="48" y2="40" vector-effect="non-scaling-stroke" stroke-width="1" stroke="rgb(0,0,0)" stroke-linejoin="miter" stroke-linecap="square" stroke-miterlimit="3"/>
|
||||
<rect x="40" y="20" width="16" height="12" transform="matrix(1,0,0,1,0,0)" fill="transparent" vector-effect="non-scaling-stroke" stroke-width="1" stroke="rgb(0,0,0)" stroke-linejoin="miter" stroke-linecap="square" stroke-miterlimit="2"/>
|
||||
<path d=" M 44 19.981 Q 44.083 20.602 44 13.356 C 43.917 6.111 52.083 6.318 52 13.356 Q 51.917 20.395 52 19.981" fill="none" vector-effect="non-scaling-stroke" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.6 KiB |
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
style="isolation:isolate" viewBox="0 0 64 64" width="64" height="64">
|
||||
<g>
|
||||
<circle cx="52" cy="12" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle cx="52" cy="52" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle cx="12" cy="12" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<circle cx="12" cy="52" r="4" fill="none" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="32" y1="8" x2="32" y2="56" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="8" y1="32" x2="56" y2="32" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="12" y1="16" x2="12" y2="24" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="16" y1="12" x2="24" y2="12" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="48" y1="12" x2="40" y2="12" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="52" y1="16" x2="52" y2="24" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="12" y1="48" x2="12" y2="40" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="16" y1="52" x2="24" y2="52" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="48" y1="52" x2="40" y2="52" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
<line x1="52" y1="48" x2="52" y2="40" stroke-width="1" stroke="rgb(0,0,0)" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Depois Largura: | Altura: | Tamanho: 1.4 KiB |
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
.regionStyle
|
||||
--> .tagsLayer
|
||||
--> .primaryTagRectStyle
|
||||
.primaryTagTextBGStyle
|
||||
.primaryTagTextStyle
|
||||
//.tagStyle [n]
|
||||
--> .dragLayer
|
||||
--> .dragRectStyle
|
||||
--> .anchorsLayer
|
||||
--> .anchorStyle [4]
|
||||
.anchorStyle.ghost
|
||||
--> .menuLayer
|
||||
--> .menuRectStyle
|
||||
*/
|
||||
.dragRectStyle {
|
||||
fill: transparent;
|
||||
stroke-width: 0;
|
||||
pointer-events: all;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.primaryTagRectStyle {
|
||||
fill: rgba(64, 64, 64, 0.4);
|
||||
stroke-width: 2;
|
||||
stroke:rgba(196, 196, 196, 0.6);
|
||||
stroke-dasharray: 2 6;
|
||||
stroke-linecap: round;
|
||||
filter: url(#black-glow);
|
||||
}
|
||||
|
||||
.primaryTagTextStyle {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-size: 9pt;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.primaryTagTextBGStyle {
|
||||
stroke-width: 0;
|
||||
fill: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.regionStyle:hover .primaryTagRectStyle {
|
||||
fill: rgba(128, 128, 128, 0.4);
|
||||
stroke: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.regionStyle.selected .primaryTagRectStyle {
|
||||
fill: rgba(128, 128, 128, 0.4);
|
||||
stroke: rgb(255, 255, 255);
|
||||
stroke-dasharray: none;
|
||||
}
|
||||
|
||||
.anchorStyle {
|
||||
stroke-width: 2;
|
||||
stroke: #ccc;
|
||||
fill: #333;
|
||||
}
|
||||
|
||||
.anchorStyle.TL, .anchorStyle.BR {
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
.anchorStyle.TR, .anchorStyle.BL {
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
|
||||
.anchorStyle.ghost, .anchorStyle.ghost:hover{
|
||||
stroke-width: 0;
|
||||
fill: rgba(255, 0, 0, 0);
|
||||
}
|
||||
|
||||
.anchorStyle.ghost:hover {
|
||||
fill: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
.anchorStyle:hover {
|
||||
stroke: rgb(25, 119, 96);
|
||||
fill:rgb(7, 189, 143);
|
||||
}
|
||||
|
||||
.regionStyle:hover .anchorStyle {
|
||||
stroke: #fff;
|
||||
}
|
||||
|
||||
.regionStyle.selected .anchorStyle {
|
||||
stroke: rgb(7, 189, 143);
|
||||
}
|
||||
|
||||
svg:not(:root) .menuLayer {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.menuRectStyle {
|
||||
stroke-width:0;
|
||||
fill: rgba(64, 64, 64, 0.8);
|
||||
filter: url(#black-glow);
|
||||
}
|
||||
|
||||
.menuItemBack {
|
||||
stroke-width: 1.5;
|
||||
stroke: rgba(198, 198, 198, 0.2);
|
||||
fill: rgb(32, 32, 32);
|
||||
}
|
||||
|
||||
.menuIcon {
|
||||
font-family: 'Segoe UI Emoji', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-size: 10pt;
|
||||
fill: rgb(64, 64, 64);
|
||||
}
|
||||
|
||||
.menuItem {
|
||||
stroke-width: 1.5;
|
||||
stroke: rgba(198, 198, 198, 0.2);
|
||||
fill:transparent;
|
||||
}
|
||||
|
||||
.menuItem:hover {
|
||||
stroke: rgba(198, 198, 198, 0.8);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
|
||||
#selectionOverlay {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.crossStyle line {
|
||||
stroke-width:1;
|
||||
stroke-dasharray: 3 3;
|
||||
stroke: #666;
|
||||
}
|
||||
|
||||
.overlayStyle {
|
||||
fill: #000;
|
||||
fill-opacity: 0.5;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.overlayMaskStyle {
|
||||
fill: #fff;
|
||||
stroke-width: 0;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.selectionBoxMaskStyle {
|
||||
/* Transparent crop*/
|
||||
fill: #000;
|
||||
stroke-width: 0;
|
||||
visibility: visible;
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
/*General*/
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.controlWrapper{
|
||||
width:100%;
|
||||
height:100%;
|
||||
border: 0px;
|
||||
border-style: solid;
|
||||
border-color: green;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
background-color: rgb(64,64,64);
|
||||
|
||||
}
|
||||
|
||||
.clickableControl{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.relativeDiv{
|
||||
position:relative;
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
#controlWrapper {
|
||||
grid-template-rows: auto 60px 120px;
|
||||
}
|
||||
|
||||
#videoWrapper {
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#videoWrapper .video-tagging {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#vid {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
#videoControls {
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
#videoTagControls {
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
#frameText {
|
||||
background-color: transparent;
|
||||
border-color: #ccc;
|
||||
border-width: 1px;
|
||||
text-decoration: none;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
/* CANVAS TOOLS */
|
||||
#ctZone {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
grid-template-columns: 64px 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
background: rgb(64,64,64);
|
||||
}
|
||||
|
||||
.toolbarStyle {
|
||||
grid-row: 1;
|
||||
grid-column: 1/2;
|
||||
}
|
||||
|
||||
.toolbarStyle svg {
|
||||
width: 64px;
|
||||
height: 100%;
|
||||
margin-right:5px;
|
||||
background: #666;
|
||||
box-shadow: 0 0 0.5px #999, 5px 0px 10px -5px rgb(0,0,0);
|
||||
}
|
||||
|
||||
#selectionZone {
|
||||
grid-row: 1;
|
||||
grid-column: 2/3;
|
||||
justify-self: center;
|
||||
align-self: center;
|
||||
|
||||
background: rgb(64,64,64);
|
||||
margin: 5px;
|
||||
width: calc(100% - 10px);
|
||||
height: calc(100% - 10px);
|
||||
}
|
||||
|
||||
#editorZone svg {
|
||||
box-shadow: 0px 0px 0.25px #999, 0px 0px 10px rgb(0,0,0);
|
||||
}
|
||||
|
||||
/*Video area*/
|
||||
|
||||
.overlaystyle {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
border:0px solid green;
|
||||
display:none;
|
||||
}
|
||||
|
||||
/* .frameCanvasStyle {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
} */
|
||||
|
||||
/* .selectionZoneStyle {
|
||||
z-index: 30;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
box-shadow: 0px 0px 0.25px #999, 0px 0px 10px rgb(0,0,0);
|
||||
} */
|
||||
|
||||
.videoStyle {
|
||||
position:relative;
|
||||
z-index: 2;
|
||||
grid-row: 1;
|
||||
grid-column: 2/3;
|
||||
justify-self: center;
|
||||
align-self: center;
|
||||
|
||||
visibility: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/*playback*/
|
||||
|
||||
.playSpeedControl {
|
||||
|
||||
background:black;
|
||||
z-index:20000;
|
||||
color:white;
|
||||
position: absolute;
|
||||
border:.08em solid white;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.playSpeedValues{
|
||||
padding:.2em;
|
||||
color: rgb(191,191,191);
|
||||
text-align: center;
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
.playSpeedValueSelected{
|
||||
color: rgb(255,255,255);
|
||||
|
||||
}
|
||||
|
||||
/*region div and label*/
|
||||
.regionLabel{
|
||||
background:rgb(59,56,56);
|
||||
position:absolute;
|
||||
z-index:50000;
|
||||
color:white;
|
||||
font-family: "Arial";
|
||||
display: block;
|
||||
opacity:.90;
|
||||
width:40px;height:20px;
|
||||
}
|
||||
|
||||
.regionLabelSpan{
|
||||
padding-left: .3em;
|
||||
color:rgb(175,171,171);
|
||||
}
|
||||
|
||||
.regionCanvas{
|
||||
z-index: 100;
|
||||
background-color: transparent;
|
||||
position: absolute !important;
|
||||
/* border: 2px solid; */
|
||||
box-shadow: inset 0 0 0 2px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.regionPoint{
|
||||
border: none;
|
||||
/*add an x from http://stackoverflow.com/questions/18012420/draw-diagonal-lines-in-div-background-with-css*/
|
||||
background:
|
||||
linear-gradient(to top left,
|
||||
rgba(0,0,0,0) 0%,
|
||||
rgba(0,0,0,0) calc(50% - 0.8px),
|
||||
rgba(0,0,0,1) 50%,
|
||||
rgba(0,0,0,0) calc(50% + 0.8px),
|
||||
rgba(0,0,0,0) 100%),
|
||||
linear-gradient(to top right,
|
||||
rgba(0,0,0,0) 0%,
|
||||
rgba(0,0,0,0) calc(50% - 0.8px),
|
||||
rgba(0,0,0,1) 50%,
|
||||
rgba(0,0,0,0) calc(50% + 0.8px),
|
||||
rgba(0,0,0,0) 100%);
|
||||
}
|
||||
|
||||
|
||||
.regionCanvasSelected{
|
||||
/* border: 2px solid; */
|
||||
box-shadow: inset 0 0 0 2px;
|
||||
border-radius: 8px;
|
||||
background: repeating-linear-gradient(-45deg, rgba(200, 200, 200, 0.1) 10px, rgba(200, 200, 200, 0.1) 20px, rgba(0, 0, 0, 0.2) 20px, rgba(0, 0, 0, 0.2) 30px);
|
||||
}
|
||||
|
||||
|
||||
.closeRegion{
|
||||
float:right;
|
||||
color: rgb(255,0,0);
|
||||
padding-right: .2em;
|
||||
}
|
||||
|
||||
|
||||
/*video controls*/
|
||||
.videoControls {
|
||||
width:100%;
|
||||
margin-top: 0px;
|
||||
|
||||
box-shadow: 0 0 10px #000;
|
||||
background-color: #333;
|
||||
border-top: 0.25px solid rgba(64,64,64,0.7)
|
||||
}
|
||||
|
||||
.videoControlsTable{
|
||||
border: 0px solid red;
|
||||
margin-left:1%;
|
||||
margin-right:1%;
|
||||
}
|
||||
|
||||
.videoControlCell{
|
||||
text-align:center;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.seekCell{
|
||||
padding-left: 1em;
|
||||
padding-right: 1.5em;
|
||||
}
|
||||
|
||||
.volumeControlCell{
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.simpleControl {
|
||||
width:5%;
|
||||
}
|
||||
|
||||
.longControl{
|
||||
width:10%;
|
||||
}
|
||||
|
||||
.frameNumber{
|
||||
width:15%;
|
||||
}
|
||||
|
||||
.textElements{
|
||||
font-family:arial;
|
||||
color:white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/*tagging controls*/
|
||||
.videoTagControls {
|
||||
|
||||
border: rgb(38,38,38,0.25);
|
||||
background-color: black;
|
||||
margin-top:.15em;
|
||||
padding-bottom: .5em;
|
||||
}
|
||||
|
||||
|
||||
.optionalTags{
|
||||
float: left;
|
||||
width: 85%;
|
||||
margin: 1em 0 0 1.5em;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.labelControls {
|
||||
float:right;
|
||||
margin: 1em 0 0;
|
||||
width:10%;
|
||||
}
|
||||
|
||||
.addTag {
|
||||
padding-right: 1.2em;
|
||||
}
|
||||
|
||||
.tagInput {
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.lockTag{
|
||||
padding-left: .4em;
|
||||
|
||||
}
|
||||
|
||||
.taggingControls{
|
||||
color:rgb(255,255,255);
|
||||
font-size: 130%;
|
||||
cursor:pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.controlOn{
|
||||
color:rgb(255,255,255);
|
||||
}
|
||||
|
||||
.controlOff{
|
||||
color:rgb(89,89,89);
|
||||
}
|
||||
|
||||
.tagButtons{
|
||||
margin-bottom: 10px;
|
||||
margin-left: 5px;
|
||||
background : rgb(59,56,56);
|
||||
color:rgb(127,127,127);
|
||||
max-width: 200px;
|
||||
min-width: 20px;
|
||||
font-family: Arial;
|
||||
font-size: 15px;
|
||||
border: 1px solid rgb(64,64,64);
|
||||
border-radius: 5px;
|
||||
user-select: none;
|
||||
}
|
||||
.tagButtons sup {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tagOn{
|
||||
border-style: solid;
|
||||
border-color: rgb(217,217,217);
|
||||
border-width: thick;
|
||||
}
|
||||
|
||||
.tagOff{
|
||||
background:rgb(59,56,56);
|
||||
}
|
||||
|
||||
.tagButtons.auto::after {
|
||||
position: relative;
|
||||
content: "A";
|
||||
background: rgba(21, 127, 240, 1.0);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
font-size: 9px;
|
||||
right: -5px;
|
||||
top: -5px;
|
||||
overflow: visible;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tagButtons.hotkey span {
|
||||
position: relative;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
color: black;
|
||||
border-radius: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
font-size: 9px;
|
||||
right: -3px;
|
||||
top: -5px;
|
||||
overflow: visible;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -11,8 +11,8 @@
|
||||
<template>
|
||||
<style include="video-taggingstyles"></style>
|
||||
<div id = "tagsContainer">
|
||||
<div id = "buttonsContainer"></div>
|
||||
</div>
|
||||
<div id = "buttonsContainer"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
createTagControls: function(tagsArray) {
|
||||
this.tags = tagsArray;
|
||||
this.clearTagsState();
|
||||
let videotagging = document.getElementById('video-tagging');
|
||||
|
||||
$('#buttonsContainer').empty();
|
||||
if (!this.colors){
|
||||
@@ -69,15 +70,36 @@
|
||||
var self = this;
|
||||
btn.addEventListener("click", (e) => {
|
||||
let tag = e.currentTarget.id;
|
||||
if (this.tagsState[tag] == "empty" || this.tagsState[tag] == "partial") {
|
||||
if((videotagging.newTagIndex || videotagging.newTagIndex == 0) && e.shiftKey){
|
||||
let originalIndex = videotagging.inputtagsarray.indexOf(tag)
|
||||
if(originalIndex != videotagging.newTagIndex){
|
||||
let temp = videotagging.inputtagsarray[originalIndex];
|
||||
videotagging.inputtagsarray[originalIndex] = videotagging.inputtagsarray[videotagging.newTagIndex];
|
||||
videotagging.inputtagsarray[videotagging.newTagIndex] = temp;
|
||||
temp = this.colors[originalIndex];
|
||||
this.colors[originalIndex] = this.colors[videotagging.newTagIndex];
|
||||
this.colors[videotagging.newTagIndex] = temp;
|
||||
this.createTagControls(videotagging.inputtagsarray);
|
||||
videotagging.showAllRegions();
|
||||
}
|
||||
} else if(e.ctrlKey){
|
||||
let index = videotagging.inputtagsarray.indexOf(tag);
|
||||
if (index !== -1){
|
||||
videotagging.inputtagsarray.splice(index, 1);
|
||||
videotagging.optionalTags.colors.splice(index, 1);
|
||||
}
|
||||
document.getElementById(tag).remove();
|
||||
videotagging.checkRemovedTags();
|
||||
} else if (e.altKey) {
|
||||
self.toggleAutoState(e.currentTarget);
|
||||
} else if (this.tagsState[tag] === 'empty' || this.tagsState[tag] === 'partial') {
|
||||
this.tagsState[tag] = "full";
|
||||
self.setSelected(e.currentTarget);
|
||||
} else {
|
||||
this.tagsState[tag] = "empty";
|
||||
self.setUnselected(e.currentTarget);
|
||||
}
|
||||
|
||||
self.fire("ontagschanged", { selected: this.projectSelectedTags(), state: this.tagsState});
|
||||
}
|
||||
self.fire("ontagschanged", { selected: this.projectSelectedTags(), state: this.tagsState});
|
||||
|
||||
});
|
||||
btn.style.color = this.colors[index];
|
||||
@@ -85,7 +107,9 @@
|
||||
|
||||
// Add indexes to map keyboard
|
||||
if (index <= 9) {
|
||||
btn.innerHTML = `${tagsArray[index]}<sup>[${index < 10 ? index + 1 : ""}]</sup>`;
|
||||
btn.innerHTML = `${tagsArray[index]}<span>${index < 10 ? index + 1 : ""}</span>`;
|
||||
btn.dataset.hotkey = index + 1;
|
||||
btn.classList.add("hotkey");
|
||||
} else {
|
||||
btn.innerHTML = `${tagsArray[index]}`;
|
||||
}
|
||||
@@ -93,27 +117,47 @@
|
||||
Polymer.dom(this.$.buttonsContainer).appendChild(btn);
|
||||
}
|
||||
|
||||
window.addEventListener("keyup", (e) => {
|
||||
if (e.keyCode >= 49 && e.keyCode <= 57) {
|
||||
if(videotagging.hotkeysAssigned){
|
||||
window.removeEventListener("keyup", this.assignHotkeys);
|
||||
}
|
||||
|
||||
window.addEventListener("keyup", this.assignHotkeys);
|
||||
videotagging.hotkeysAssigned = true;
|
||||
},
|
||||
assignHotkeys(e) {
|
||||
if (e.keyCode >= 49 && e.keyCode <= 57) {
|
||||
if (e.altKey) {
|
||||
let index = e.keyCode - 49;
|
||||
if (index < this.tags.length) {
|
||||
let tag = this.tags[index];
|
||||
let btn = this.getBtnByTag(tag);
|
||||
if(!btn.disabled) {
|
||||
if (this.tagsState[tag] == "empty" || this.tagsState[tag] == "partial") {
|
||||
this.tagsState[tag] = "full";
|
||||
self.setSelected(btn);
|
||||
if (index < videotagging.optionalTags.tags.length) {
|
||||
let tag = videotagging.optionalTags.tags[index];
|
||||
let btn = videotagging.optionalTags.getBtnByTag(tag);
|
||||
videotagging.optionalTags.toggleAutoState(btn);
|
||||
}
|
||||
} else if (!e.shiftKey) {
|
||||
// debugger;
|
||||
let index = e.keyCode - 49;
|
||||
if (index < videotagging.optionalTags.tags.length) {
|
||||
let tag = videotagging.optionalTags.tags[index];
|
||||
let btn = videotagging.optionalTags.getBtnByTag(tag);
|
||||
if (!btn.disabled) {
|
||||
if (videotagging.optionalTags.tagsState[tag] == "empty" || videotagging.optionalTags.tagsState[tag] == "partial") {
|
||||
videotagging.optionalTags.tagsState[tag] = "full";
|
||||
videotagging.optionalTags.setSelected(btn);
|
||||
} else {
|
||||
this.tagsState[tag] = "empty";
|
||||
self.setUnselected(btn);
|
||||
videotagging.optionalTags.tagsState[tag] = "empty";
|
||||
videotagging.optionalTags.setUnselected(btn);
|
||||
}
|
||||
|
||||
self.fire("ontagschanged", { selected: this.projectSelectedTags(), state: this.tagsState});
|
||||
videotagging.optionalTags.fire("ontagschanged", { selected: videotagging.optionalTags.projectSelectedTags(), state: videotagging.optionalTags.tagsState });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
toggleAutoState(btn) {
|
||||
btn.classList.toggle('auto');
|
||||
},
|
||||
|
||||
getBtnByTag(tag) {
|
||||
for (var i=0;i<this.buttonsContainer.childNodes.length;i++) {
|
||||
@@ -149,6 +193,10 @@
|
||||
getSelectedTags :function(){
|
||||
return $(this.buttonsContainer).find(".tagOn");
|
||||
},
|
||||
getAutoTags :function(){
|
||||
return $(this.buttonsContainer).find(".auto");
|
||||
},
|
||||
|
||||
resetSelected: function(){
|
||||
var self = this;
|
||||
$(this.buttonsContainer).find('.tagButtons').each(function(index){
|
||||
|
||||
@@ -7,308 +7,7 @@
|
||||
<template>
|
||||
<style>
|
||||
|
||||
/*General*/
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.controlWrapper{
|
||||
width:100%;
|
||||
height:100%;
|
||||
border: 0px;
|
||||
border-style: solid;
|
||||
border-color: green;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
background-color: rgb(64,64,64);
|
||||
|
||||
}
|
||||
|
||||
.clickableControl{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.relativeDiv{
|
||||
position:relative;
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
#controlWrapper {
|
||||
grid-template-rows: auto 60px 120px;
|
||||
}
|
||||
|
||||
#videoWrapper {
|
||||
grid-row: 1;
|
||||
display:grid;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-columns: 1fr;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#videoWrapper .video-tagging {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#vid {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
#videoControls {
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
#videoTagControls {
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
#frameText {
|
||||
background-color: transparent;
|
||||
border-color: #ccc;
|
||||
border-width: 1px;
|
||||
text-decoration: none;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
/*Video area*/
|
||||
|
||||
.overlaystyle {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
border:0px solid green;
|
||||
display:none;
|
||||
}
|
||||
|
||||
.frameCanvasStyle {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
/* border:1px solid green; */
|
||||
/* width: auto; */
|
||||
/* height: 100%; */
|
||||
}
|
||||
|
||||
.selectionZoneStyle {
|
||||
z-index: 30;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.videoStyle {
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #333;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/*playback*/
|
||||
|
||||
.playSpeedControl {
|
||||
|
||||
background:black;
|
||||
z-index:20000;
|
||||
color:white;
|
||||
position: absolute;
|
||||
border:.08em solid white;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.playSpeedValues{
|
||||
padding:.2em;
|
||||
color: rgb(191,191,191);
|
||||
text-align: center;
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
.playSpeedValueSelected{
|
||||
color: rgb(255,255,255);
|
||||
|
||||
}
|
||||
|
||||
/*region div and label*/
|
||||
.regionLabel{
|
||||
background:rgb(59,56,56);
|
||||
position:absolute;
|
||||
z-index:50000;
|
||||
color:white;
|
||||
font-family: "Arial";
|
||||
display: block;
|
||||
opacity:.90;
|
||||
width:40px;height:20px;
|
||||
}
|
||||
|
||||
.regionLabelSpan{
|
||||
padding-left: .3em;
|
||||
color:rgb(175,171,171);
|
||||
}
|
||||
|
||||
.regionCanvas{
|
||||
z-index: 100;
|
||||
background-color: transparent;
|
||||
position: absolute !important;
|
||||
/* border: 2px solid; */
|
||||
box-shadow: inset 0 0 0 2px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.regionPoint{
|
||||
border: none;
|
||||
/*add an x from http://stackoverflow.com/questions/18012420/draw-diagonal-lines-in-div-background-with-css*/
|
||||
background:
|
||||
linear-gradient(to top left,
|
||||
rgba(0,0,0,0) 0%,
|
||||
rgba(0,0,0,0) calc(50% - 0.8px),
|
||||
rgba(0,0,0,1) 50%,
|
||||
rgba(0,0,0,0) calc(50% + 0.8px),
|
||||
rgba(0,0,0,0) 100%),
|
||||
linear-gradient(to top right,
|
||||
rgba(0,0,0,0) 0%,
|
||||
rgba(0,0,0,0) calc(50% - 0.8px),
|
||||
rgba(0,0,0,1) 50%,
|
||||
rgba(0,0,0,0) calc(50% + 0.8px),
|
||||
rgba(0,0,0,0) 100%);
|
||||
}
|
||||
|
||||
|
||||
.regionCanvasSelected{
|
||||
/* border: 2px solid; */
|
||||
box-shadow: inset 0 0 0 2px;
|
||||
border-radius: 8px;
|
||||
background: repeating-linear-gradient(-45deg, rgba(200, 200, 200, 0.1) 10px, rgba(200, 200, 200, 0.1) 20px, rgba(0, 0, 0, 0.2) 20px, rgba(0, 0, 0, 0.2) 30px);
|
||||
}
|
||||
|
||||
|
||||
.closeRegion{
|
||||
float:right;
|
||||
color: rgb(255,0,0);
|
||||
padding-right: .2em;
|
||||
}
|
||||
|
||||
|
||||
/*video controls*/
|
||||
.videoControls {
|
||||
|
||||
width:100%;
|
||||
background-color: black;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.videoControlsTable{
|
||||
border: 0px solid red;
|
||||
margin-left:1%;
|
||||
margin-right:1%;
|
||||
}
|
||||
|
||||
.videoControlCell{
|
||||
text-align:center;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.seekCell{
|
||||
padding-left: 1em;
|
||||
padding-right: 1.5em;
|
||||
}
|
||||
|
||||
.volumeControlCell{
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.simpleControl {
|
||||
width:5%;
|
||||
}
|
||||
|
||||
.longControl{
|
||||
width:10%;
|
||||
}
|
||||
|
||||
.frameNumber{
|
||||
width:15%;
|
||||
}
|
||||
|
||||
.textElements{
|
||||
font-family:arial;
|
||||
color:white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/*tagging controls*/
|
||||
.videoTagControls {
|
||||
|
||||
border: rgb(38,38,38,0.25);
|
||||
background-color: black;
|
||||
margin-top:.15em;
|
||||
padding-bottom: .5em;
|
||||
}
|
||||
|
||||
|
||||
.optionalTags{
|
||||
float: left;
|
||||
width: 85%;
|
||||
margin: 1em 0 0 1.5em;
|
||||
}
|
||||
|
||||
.labelControls {
|
||||
float:right;
|
||||
margin: 1em 0 0 0;
|
||||
width:10%;
|
||||
}
|
||||
|
||||
.lockTag{
|
||||
padding-left: .4em;
|
||||
|
||||
}
|
||||
|
||||
.taggingControls{
|
||||
color:rgb(255,255,255);
|
||||
font-size: 130%;
|
||||
cursor:pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.controlOn{
|
||||
color:rgb(255,255,255);
|
||||
}
|
||||
|
||||
.controlOff{
|
||||
color:rgb(89,89,89);
|
||||
}
|
||||
|
||||
.tagButtons{
|
||||
margin-bottom: 10px;
|
||||
margin-left: 5px;
|
||||
background : rgb(59,56,56);
|
||||
color:rgb(127,127,127);
|
||||
max-width: 200px;
|
||||
min-width: 20px;
|
||||
font-family: Arial;
|
||||
font-size: 15px;
|
||||
border: 1px solid rgb(64,64,64);
|
||||
border-radius: 5px;
|
||||
user-select: none;
|
||||
}
|
||||
.tagButtons sup {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tagOn{
|
||||
background:rgb(217,217,217);
|
||||
}
|
||||
|
||||
.tagOff{
|
||||
background:rgb(59,56,56);
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</template>
|
||||
|
||||