├── public ├── .gitignore ├── jquery-ui │ ├── css │ │ ├── 1 │ │ └── smoothness │ │ │ ├── 1 │ │ │ ├── images │ │ │ ├── 1 │ │ │ ├── animated-overlay.gif │ │ │ ├── ui-icons_222222_256x240.png │ │ │ ├── ui-icons_2e83ff_256x240.png │ │ │ ├── ui-icons_454545_256x240.png │ │ │ ├── ui-icons_888888_256x240.png │ │ │ ├── ui-icons_cd0a0a_256x240.png │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ ├── ui-bg_flat_75_ffffff_40x100.png │ │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ │ ├── ui-bg_glass_95_fef1ec_1x400.png │ │ │ └── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ │ └── jquery-ui-1.10.4.custom.min.css │ ├── Readme.md │ ├── jquery-ui.zip │ └── index.html ├── readme.md ├── frames │ ├── .gitignore │ └── .htaccess ├── box.jpg ├── lock.jpg ├── magic.mp3 ├── favicon.ico ├── secret.png ├── classify.jpg ├── entering1.png ├── entering2.png ├── entering3.png ├── entering4.png ├── outofcar1.png ├── outofcar2.png ├── outofcar3.png ├── sequence1.jpg ├── sequence2.jpg ├── sequence3.jpg ├── sequence4.jpg ├── tight-bad.jpg ├── tight-good.jpg ├── everyobject.jpg ├── outsideoccluded.jpg ├── index.html ├── job.js ├── preload.js ├── videoplayer.js ├── bootstrap.js ├── jquery-migrate-1.2.1.min.js ├── stylesheet.css ├── instructions.js ├── ui.js └── objectui.js ├── arial.ttf ├── .gitignore ├── CONTRIBUTING.md ├── config.py-example ├── vatic-install.sh ├── LICENSE ├── DEVELOPERS ├── match.py ├── qa.py ├── server.py ├── merge.py ├── models.py └── README.md /public/.gitignore: -------------------------------------------------------------------------------- 1 | turkic 2 | -------------------------------------------------------------------------------- /public/jquery-ui/css/1: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/1: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/1: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/readme.md: -------------------------------------------------------------------------------- 1 | https://github.com/cvondrick/vatic 2 | -------------------------------------------------------------------------------- /public/frames/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.htaccess 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /public/jquery-ui/Readme.md: -------------------------------------------------------------------------------- 1 | please zip jquery-ui.zip in here 2 | -------------------------------------------------------------------------------- /arial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/arial.ttf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | config.py 3 | tmp 4 | backups 5 | vatic.tar.gz 6 | .DS_Store 7 | *.swp 8 | -------------------------------------------------------------------------------- /public/box.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/box.jpg -------------------------------------------------------------------------------- /public/lock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/lock.jpg -------------------------------------------------------------------------------- /public/magic.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/magic.mp3 -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/secret.png -------------------------------------------------------------------------------- /public/classify.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/classify.jpg -------------------------------------------------------------------------------- /public/entering1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/entering1.png -------------------------------------------------------------------------------- /public/entering2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/entering2.png -------------------------------------------------------------------------------- /public/entering3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/entering3.png -------------------------------------------------------------------------------- /public/entering4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/entering4.png -------------------------------------------------------------------------------- /public/outofcar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/outofcar1.png -------------------------------------------------------------------------------- /public/outofcar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/outofcar2.png -------------------------------------------------------------------------------- /public/outofcar3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/outofcar3.png -------------------------------------------------------------------------------- /public/sequence1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/sequence1.jpg -------------------------------------------------------------------------------- /public/sequence2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/sequence2.jpg -------------------------------------------------------------------------------- /public/sequence3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/sequence3.jpg -------------------------------------------------------------------------------- /public/sequence4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/sequence4.jpg -------------------------------------------------------------------------------- /public/tight-bad.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/tight-bad.jpg -------------------------------------------------------------------------------- /public/tight-good.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/tight-good.jpg -------------------------------------------------------------------------------- /public/everyobject.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/everyobject.jpg -------------------------------------------------------------------------------- /public/outsideoccluded.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/outsideoccluded.jpg -------------------------------------------------------------------------------- /public/jquery-ui/jquery-ui.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/jquery-ui.zip -------------------------------------------------------------------------------- /public/frames/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Header set Cache-Control "max-age=290304000, public" 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/animated-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/animated-overlay.gif -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please make pull requests against the 'contrib' branch and email me at vondrick@mit.edu if you want it merged. 2 | 3 | Once there is something new in 'contrib', I will announce it in the README. 4 | -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GZQ0723/vatic_BasicFinder-HIVE/HEAD/public/jquery-ui/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /config.py-example: -------------------------------------------------------------------------------- 1 | signature = "" # AWS secret access key 2 | accesskey = "" # AWS access key ID 3 | sandbox = True # if true, put on workersandbox.mturk.com 4 | localhost = "http://localhost/" # your local host 5 | database = "mysql://root@localhost/vatic" # server://user:pass@localhost/dbname 6 | geolocation = "" # api key for ipinfodb.com 7 | maxobjects = 25; 8 | 9 | # probably no need to mess below this line 10 | 11 | import multiprocessing 12 | processes = multiprocessing.cpu_count() 13 | 14 | import os.path 15 | import sys 16 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 17 | -------------------------------------------------------------------------------- /vatic-install.sh: -------------------------------------------------------------------------------- 1 | sudo apt-get install -y git python-setuptools python-dev libavcodec-dev libavformat-dev libswscale-dev libjpeg62 libjpeg62-dev libfreetype6 libfreetype6-dev apache2 libapache2-mod-wsgi mysql-server-5.1 mysql-client-5.1 libmysqlclient-dev gfortran 2 | 3 | sudo easy_install -U SQLAlchemy wsgilog pil cython mysql-python munkres parsedatetime argparse 4 | sudo easy_install -U numpy 5 | 6 | git clone https://github.com/cvondrick/turkic.git 7 | git clone https://github.com/cvondrick/pyvision.git 8 | git clone https://github.com/cvondrick/vatic.git 9 | 10 | cd turkic 11 | sudo python setup.py install 12 | cd .. 13 | 14 | cd pyvision 15 | sudo python setup.py install 16 | cd .. 17 | 18 | echo "*****************************************************" 19 | echo "*** Please consult README to finish installation. ***" 20 | echo "*****************************************************" 21 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vatic 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Carl Vondrick, Deva Ramanan, and Donald Patterson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | 1. The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /DEVELOPERS: -------------------------------------------------------------------------------- 1 | This document will describe how to modify VATIC for your own purposes. If you 2 | only want to use VATIC, please see README instead. 3 | 4 | VATIC is actually broken up into three different packages: 5 | 6 | 1. vatic - video annotation tool 7 | 2. turkic - a platform for easy MTurk management 8 | 3. pyvision - a simple Python computer vision toolkit 9 | 10 | In general, you likely want to only modify VATIC, which houses the essential 11 | tools for video annotation, such as: the JavaScript video player, instructions, 12 | entire user interface, merging paths, quality assurance, etc. 13 | 14 | pyvision contains a couple of routines useful for vision research (e.g., 15 | convolution, tracking, svm, bounding boxes). You probably should only modify 16 | pyvision if you want to tweak our tracking algorithms, linear interpolation 17 | schemes, or support new type of annotations besides bounding boxes. 18 | 19 | turkic is a framework that makes it easy to send jobs to MTurk. It manages 20 | workers, payment, and training. You probably should only modify turkic if you 21 | want to change protocols to how workers are paid and quality control is done. 22 | 23 | More to come later... 24 | -------------------------------------------------------------------------------- /match.py: -------------------------------------------------------------------------------- 1 | import munkres 2 | 3 | def match(first, second, method): 4 | """ 5 | Attempts to match every path in 'first' with a path in 'second'. Returns 6 | the association along with its score. 7 | 8 | Note: if two paths have nothing to do with each other, but there is no 9 | other suitable candidate, the two seeminly unrelated paths will be 10 | associated. It is up to the caller to handle this situation. The 'validate' 11 | method may provide some help. Further, if len(first) != len(second), then 12 | some elements will be associated with None. 13 | """ 14 | if len(first) == len(second) == 0: 15 | return [] 16 | 17 | costs = buildmatrix(first, second, method) 18 | response = [] 19 | 20 | for f, s in munkres.Munkres().compute(costs): 21 | response.append((first[f] if f < len(first) else None, 22 | second[s] if s < len(second) else None, 23 | costs[f][s])) 24 | return response 25 | 26 | def buildmatrix(first, second, method): 27 | """ 28 | Builds the matrix for the Hungarian algorithm. Pads with the worst to make 29 | the matrix square. 30 | """ 31 | costs = [[method(f,s) for s in second] for f in first] 32 | 33 | if len(first) and len(second): 34 | horrible = [max(max(costs)) + 1] 35 | else: 36 | horrible = [1e10] 37 | 38 | if len(first) > len(second): 39 | for row in costs: 40 | row.extend(horrible * (len(first) - len(second))) 41 | elif len(first) < len(second): 42 | costs.extend([horrible * len(second)] * (len(second) - len(first))) 43 | return costs 44 | -------------------------------------------------------------------------------- /public/job.js: -------------------------------------------------------------------------------- 1 | function Job(data) 2 | { 3 | var me = this; 4 | 5 | this.slug = null; 6 | this.start = null; 7 | this.stop = null; 8 | this.width = null; 9 | this.height = null; 10 | this.skip = null; 11 | this.perobject = null; 12 | this.completion = null; 13 | this.blowradius = null; 14 | this.thisid = null; 15 | this.labels = null; 16 | 17 | this.frameurl = function(i) 18 | { 19 | folder1 = parseInt(Math.floor(i / 100)); 20 | folder2 = parseInt(Math.floor(i / 10000)); 21 | return "frames/" + me.slug + 22 | "/" + folder2 + "/" + folder1 + "/" + parseInt(i) + ".jpg"; 23 | } 24 | } 25 | 26 | function job_import(data) 27 | { 28 | var job = new Job(); 29 | job.slug = data["slug"]; 30 | job.start = parseInt(data["start"]); 31 | job.stop = parseInt(data["stop"]); 32 | job.width = parseInt(data["width"]); 33 | job.height = parseInt(data["height"]); 34 | job.skip = parseInt(data["skip"]); 35 | job.perobject = parseFloat(data["perobject"]); 36 | job.completion = parseFloat(data["completion"]); 37 | job.blowradius = parseInt(data["blowradius"]); 38 | job.jobid = parseInt(data["jobid"]); 39 | job.labels = data["labels"]; 40 | job.attributes = data["attributes"]; 41 | job.training = parseInt(data["training"]); 42 | 43 | console.log("Job configured!"); 44 | console.log(" Slug: " + job.slug); 45 | console.log(" Start: " + job.start); 46 | console.log(" Stop: " + job.stop); 47 | console.log(" Width: " + job.width); 48 | console.log(" Height: " + job.height); 49 | console.log(" Skip: " + job.skip); 50 | console.log(" Per Object: " + job.perobject); 51 | console.log(" Blow Radius: " + job.blowradius); 52 | console.log(" Training: " + job.training); 53 | console.log(" Job ID: " + job.jobid); 54 | console.log(" Labels: "); 55 | for (var i in job.labels) 56 | { 57 | console.log(" " + i + " = " + job.labels[i]); 58 | } 59 | console.log(" Attributes:"); 60 | for (var i in job.attributes) 61 | { 62 | for (var j in job.attributes[i]) 63 | { 64 | console.log(" " + job.labels[i] + " = " + job.attributes[i][j]) 65 | } 66 | } 67 | 68 | return job; 69 | } 70 | -------------------------------------------------------------------------------- /public/preload.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Preloads an array of images and calls onprogress. 3 | * 4 | * var list = ["foo.jpg", "bar.jpg"]; 5 | * preload(list, function(progress) { alert(progress); } 6 | */ 7 | function preload(queue, onprogress) 8 | { 9 | onprogress(0.0); 10 | 11 | if ($("#preloadpit").size() == 0) 12 | { 13 | $("
").appendTo("body").hide(); 14 | } 15 | 16 | function process(remaining, count) 17 | { 18 | if (remaining.length >= 1) 19 | { 20 | var image = new Image(); 21 | image.onload = function() { 22 | 23 | // force the browser to cache it 24 | $("").appendTo("#preloadpit"); 25 | 26 | if (onprogress) 27 | { 28 | onprogress((count + 1) / (remaining.length + count)) 29 | } 30 | remaining.shift(); 31 | 32 | 33 | // javascript's version of tail recursion 34 | window.setTimeout(function() { 35 | process(remaining, count + 1); 36 | }, 1); 37 | } 38 | image.src = remaining[0]; 39 | } 40 | else 41 | { 42 | console.log("Preload of " + count + " images finished."); 43 | } 44 | } 45 | 46 | process(queue, 0.0); 47 | } 48 | 49 | function preloadvideo(start, stop, generator, onprogress) 50 | { 51 | var queue = new Array(); 52 | for (var i = start; i <= stop; i++) 53 | { 54 | queue[i-start] = generator(i); 55 | } 56 | preload(queue, onprogress); 57 | } 58 | 59 | /* 60 | * Updates a slider as the progress continues. 61 | * 62 | * var list = ["foo.jpg", "bar.jpg"]; 63 | * preload(list, preloadslider($("#slider"))); 64 | */ 65 | function preloadslider(slider, onprogress) 66 | { 67 | var text = $('
').appendTo(slider); 68 | text.css({ 69 | 'float': 'left', 70 | }); 71 | var pg = $('
').appendTo(slider); 72 | pg.css({ 73 | 'width': '0%', 74 | 'display': 'block', 75 | 'height': '100%', 76 | }); 77 | 78 | function progress(percent) 79 | { 80 | if (onprogress) 81 | { 82 | onprogress(percent); 83 | } 84 | pg.css('width', percent * 100 + '%'); 85 | text.html(Math.floor(percent * 100) + "%"); 86 | } 87 | 88 | return progress; 89 | } 90 | -------------------------------------------------------------------------------- /qa.py: -------------------------------------------------------------------------------- 1 | from match import match 2 | 3 | class tolerable(object): 4 | """ 5 | Tests if two paths agree by tolerable guidelines. 6 | """ 7 | def __init__(self, overlap = 0.5, tolerance = 0.1, mistakes = 0): 8 | self.overlap = overlap 9 | self.tolerance = tolerance 10 | self.mistakes = mistakes 11 | 12 | def __call__(self, first, second): 13 | """ 14 | Allows this object to be called as a function to invoke validation. 15 | """ 16 | return self.validate(first, second) 17 | 18 | def validate(self, first, second): 19 | """ 20 | Compares first to second to determine if they sufficiently agree. 21 | """ 22 | matches = match(first, second, 23 | lambda x, y: self.overlapcost(x, y)) 24 | return sum(x[2] != 0 for x in matches) <= self.mistakes 25 | 26 | def overlapcost(self, first, second): 27 | """ 28 | Computes the overlap cost between first and second. Both will be 29 | linearly filled. 30 | """ 31 | firstboxes = first.getboxes(interpolate = True) 32 | secondboxes = second.getboxes(interpolate = True) 33 | 34 | horrible = max(len(firstboxes), len(secondboxes)) + 1 35 | if first.label != second.label: 36 | return horrible 37 | if len(firstboxes) != len(secondboxes): 38 | return horrible 39 | 40 | cost = 0 41 | for f, s in zip(firstboxes, secondboxes): 42 | if f.lost != s.lost: 43 | cost += 1 44 | elif f.percentoverlap(s) < self.overlap: 45 | cost += 1 46 | return max(0, cost - float(len(firstboxes)) * self.tolerance) 47 | 48 | def __hash__(self): 49 | """ 50 | Computes a hash for this type. Breaks duck typing because we hash on 51 | the type of the object as well. 52 | """ 53 | return hash((type(self), self.overlap, self.tolerance, self.mistakes)) 54 | 55 | def __eq__(self, other): 56 | """ 57 | Checks equality between objects. Breaks duck typing because the types 58 | must now match. 59 | """ 60 | try: 61 | return (self.overlap == other.overlap and 62 | self.tolerance == other.tolerance and 63 | self.mistakes == other.mistakes and 64 | type(self) is type(other)) 65 | except AttributeError: 66 | return False 67 | 68 | def __ne__(self, other): 69 | """ 70 | Checks inequality between classes. See __eq__(). 71 | """ 72 | return not (self == other) 73 | 74 | def __repr__(self): 75 | return "tolerable({0}, {1}, {2})".format(self.overlap, 76 | self.tolerance, 77 | self.mistakes) 78 | -------------------------------------------------------------------------------- /public/videoplayer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * var videoplayer = VideoPlayer($("#frame"), 1000, 3 | * function (x) { return "/images/" + x + ".jpg"; }); 4 | * videoplayer.play(); 5 | */ 6 | function VideoPlayer(handle, job) 7 | { 8 | var me = this; 9 | 10 | this.handle = handle; 11 | this.job = job; 12 | this.frame = job.start; 13 | this.paused = true; 14 | this.fps = 30; 15 | this.playdelta = 1; 16 | 17 | this.onplay = []; 18 | this.onpause = []; 19 | this.onupdate = []; 20 | 21 | /* 22 | * Toggles playing the video. If playing, pauses. If paused, plays. 23 | */ 24 | this.toggle = function() 25 | { 26 | if (this.paused) 27 | { 28 | this.play(); 29 | } 30 | else 31 | { 32 | this.pause(); 33 | } 34 | } 35 | 36 | /* 37 | * Starts playing the video if paused. 38 | */ 39 | this.play = function() 40 | { 41 | if (this.paused) 42 | { 43 | console.log("Playing..."); 44 | this.paused = false; 45 | this.interval = window.setInterval(function() { 46 | if (me.frame >= me.job.stop) 47 | { 48 | me.pause(); 49 | } 50 | else 51 | { 52 | me.displace(me.playdelta); 53 | } 54 | }, 1. / this.fps * 1000); 55 | 56 | this._callback(this.onplay); 57 | } 58 | } 59 | 60 | /* 61 | * Pauses the video if playing. 62 | */ 63 | this.pause = function() 64 | { 65 | if (!this.paused) 66 | { 67 | console.log("Paused."); 68 | this.paused = true; 69 | window.clearInterval(this.interval); 70 | this.interval = null; 71 | 72 | this._callback(this.onpause); 73 | } 74 | } 75 | 76 | /* 77 | * Seeks to a specific video frame. 78 | */ 79 | this.seek = function(target) 80 | { 81 | this.frame = target; 82 | this.updateframe(); 83 | } 84 | 85 | /* 86 | * Displaces video frame by a delta. 87 | */ 88 | this.displace = function(delta) 89 | { 90 | this.frame += delta; 91 | this.updateframe(); 92 | } 93 | 94 | /* 95 | * Updates the current frame. Call whenever the frame changes. 96 | */ 97 | this.updateframe = function() 98 | { 99 | this.frame = Math.min(this.frame, this.job.stop); 100 | this.frame = Math.max(this.frame, this.job.start); 101 | 102 | var url = this.job.frameurl(this.frame); 103 | this.handle.css("background-image", "url('" + url + "')"); 104 | 105 | this._callback(this.onupdate); 106 | } 107 | 108 | /* 109 | * Calls callbacks 110 | */ 111 | this._callback = function(list) 112 | { 113 | for (var i = 0; i < list.length; i++) 114 | { 115 | list[i](); 116 | } 117 | } 118 | 119 | this.updateframe(); 120 | } 121 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import os.path, sys 2 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 3 | 4 | import config 5 | from turkic.server import handler, application 6 | from turkic.database import session 7 | import cStringIO 8 | from models import * 9 | 10 | import logging 11 | logger = logging.getLogger("vatic.server") 12 | 13 | @handler() 14 | def getjob(id, verified): 15 | job = session.query(Job).get(id) 16 | 17 | logger.debug("Found job {0}".format(job.id)) 18 | 19 | if int(verified) and job.segment.video.trainwith: 20 | # swap segment with the training segment 21 | training = True 22 | segment = job.segment.video.trainwith.segments[0] 23 | logger.debug("Swapping actual segment with training segment") 24 | else: 25 | training = False 26 | segment = job.segment 27 | 28 | video = segment.video 29 | labels = dict((l.id, l.text) for l in video.labels) 30 | 31 | attributes = {} 32 | for label in video.labels: 33 | attributes[label.id] = dict((a.id, a.text) for a in label.attributes) 34 | 35 | logger.debug("Giving user frames {0} to {1} of {2}".format(video.slug, 36 | segment.start, 37 | segment.stop)) 38 | 39 | return {"start": segment.start, 40 | "stop": segment.stop, 41 | "slug": video.slug, 42 | "width": video.width, 43 | "height": video.height, 44 | "skip": video.skip, 45 | "perobject": video.perobjectbonus, 46 | "completion": video.completionbonus, 47 | "blowradius": video.blowradius, 48 | "jobid": job.id, 49 | "training": int(training), 50 | "labels": labels, 51 | "attributes": attributes} 52 | 53 | @handler() 54 | def getboxesforjob(id): 55 | job = session.query(Job).get(id) 56 | result = [] 57 | for path in job.paths: 58 | attrs = [(x.attributeid, x.frame, x.value) for x in path.attributes] 59 | result.append({"label": path.labelid, 60 | "boxes": [tuple(x) for x in path.getboxes()], 61 | "attributes": attrs}) 62 | return result 63 | 64 | def readpaths(tracks): 65 | paths = [] 66 | logger.debug("Reading {0} total tracks".format(len(tracks))) 67 | 68 | for label, track, attributes in tracks: 69 | path = Path() 70 | path.label = session.query(Label).get(label) 71 | 72 | logger.debug("Received a {0} track".format(path.label.text)) 73 | 74 | visible = False 75 | for frame, userbox in track.items(): 76 | box = Box(path = path) 77 | box.xtl = max(int(userbox[0]), 0) 78 | box.ytl = max(int(userbox[1]), 0) 79 | box.xbr = max(int(userbox[2]), 0) 80 | box.ybr = max(int(userbox[3]), 0) 81 | box.occluded = int(userbox[4]) 82 | box.outside = int(userbox[5]) 83 | box.frame = int(frame) 84 | if not box.outside: 85 | visible = True 86 | 87 | logger.debug("Received box {0}".format(str(box.getbox()))) 88 | 89 | if not visible: 90 | logger.warning("Received empty path! Skipping") 91 | continue 92 | 93 | for attributeid, timeline in attributes.items(): 94 | attribute = session.query(Attribute).get(attributeid) 95 | for frame, value in timeline.items(): 96 | aa = AttributeAnnotation() 97 | aa.attribute = attribute 98 | aa.frame = frame 99 | aa.value = value 100 | path.attributes.append(aa) 101 | 102 | paths.append(path) 103 | return paths 104 | 105 | @handler(post = "json") 106 | def savejob(id, tracks): 107 | job = session.query(Job).get(id) 108 | 109 | for path in job.paths: 110 | session.delete(path) 111 | session.commit() 112 | for path in readpaths(tracks): 113 | job.paths.append(path) 114 | 115 | session.add(job) 116 | session.commit() 117 | 118 | @handler(post = "json") 119 | def validatejob(id, tracks): 120 | job = session.query(Job).get(id) 121 | paths = readpaths(tracks) 122 | 123 | return job.trainingjob.validator(paths, job.trainingjob.paths) 124 | 125 | @handler() 126 | def respawnjob(id): 127 | job = session.query(Job).get(id) 128 | 129 | replacement = job.markastraining() 130 | job.worker.verified = True 131 | session.add(job) 132 | session.add(replacement) 133 | session.commit() 134 | 135 | replacement.publish() 136 | session.add(replacement) 137 | session.commit() 138 | -------------------------------------------------------------------------------- /public/bootstrap.js: -------------------------------------------------------------------------------- 1 | var development = false; 2 | 3 | var container; 4 | 5 | $(document).ready(function() { 6 | mturk_ready(function() { 7 | mturk_blockbadworkers(boot); 8 | }); 9 | }); 10 | 11 | function isIE () { 12 | var myNav = navigator.userAgent.toLowerCase(); 13 | return (myNav.indexOf('msie') != -1) ? parseInt(myNav.split('msie')[1]) : false; 14 | } 15 | 16 | function boot() 17 | { 18 | console.log("Booting..."); 19 | 20 | container = $("#container"); 21 | 22 | if (isIE()) 23 | { 24 | container.html("

Sorry! This application does not currently support Internet Explorer. Please upgrade to a more modern browser to complete this HIT. We recommend Google Chrome or Mozilla Firefox.

"); 25 | return; 26 | } 27 | 28 | var parameters = mturk_parameters(); 29 | if (!parameters["id"]) 30 | { 31 | brandingscreen(); 32 | return; 33 | } 34 | 35 | if (!mturk_isassigned()) 36 | { 37 | mturk_acceptfirst(); 38 | } 39 | else 40 | { 41 | mturk_showstatistics(); 42 | } 43 | 44 | mturk_disabletimer(); 45 | 46 | function dispatch(training) 47 | { 48 | training = training ? 1 : 0; 49 | server_request("getjob", [parameters["id"], training], function(data) { 50 | loadingscreen(job_import(data)); 51 | }); 52 | } 53 | 54 | worker_isverified(function() { 55 | console.log("Worker is verified"); 56 | dispatch(false); 57 | }, function() { 58 | console.log("Worker is NOT verified"); 59 | dispatch(true); 60 | }); 61 | } 62 | 63 | function loadingscreen(job) 64 | { 65 | var ls = $("
"); 66 | ls.append("
Show " + 67 | "Instructions
"); 68 | ls.append("
Downloading the video...
"); 69 | ls.append("
"); 70 | 71 | if (!mturk_isassigned()) 72 | { 73 | ls.append("
Tip: You " + 74 | "are strongly recommended to accept the task before the " + 75 | "download completes. When you accept, you will have to restart " + 76 | "the download.
"); 77 | } 78 | 79 | ls.append("
You are welcome to work on " + 80 | "other HITs while you wait for the download to complete. When the " + 81 | "download finishes, we'll play a gentle musical tune to notify " + 82 | "you.
"); 83 | 84 | container.html(ls); 85 | 86 | if (!development && !mturk_isoffline()) 87 | { 88 | ui_showinstructions(job); 89 | } 90 | 91 | $("#loadingscreeninstructions").button({ 92 | icons: { 93 | primary: "ui-icon-newwin" 94 | } 95 | }).click(function() { 96 | ui_showinstructions(job); 97 | }); 98 | 99 | eventlog("preload", "Start preloading"); 100 | 101 | preloadvideo(job.start, job.stop, job.frameurl, 102 | preloadslider($("#loadingscreenslider"), function(progress) { 103 | if (progress == 1) 104 | { 105 | if (!development && !mturk_isoffline()) 106 | { 107 | /*$("body").append('
' + 108 | '<bgsound src="magic.mp3">
');*/ 109 | 110 | window.setTimeout(function() { 111 | $("#music").remove(); 112 | }, 2000); 113 | } 114 | 115 | ls.remove() 116 | ui_build(job); 117 | 118 | mturk_enabletimer(); 119 | 120 | eventlog("preload", "Done preloading"); 121 | } 122 | }) 123 | ); 124 | } 125 | 126 | function brandingscreen() 127 | { 128 | console.log("Loading screen"); 129 | var d = $("
"); 130 | d.append("

Welcome to vatic

"); 131 | d.append("

vatic is an online, interactive video annotation tool for computer vision research that crowdsources work to Amazon's Mechanical Turk. Our tool makes it easy to build massive, affordable video data sets and can be deployed on a cloud. Written in Python + C + Javascript, vatic is free and open-source software.

"); 132 | d.append("

More information »

"); 133 | d.hide(); 134 | d.appendTo(container); 135 | d.show("explode", 1000); 136 | 137 | $("body").animate({"background-color": "#666"}, 1000); 138 | } 139 | -------------------------------------------------------------------------------- /merge.py: -------------------------------------------------------------------------------- 1 | """ 2 | Merges paths across segments. Typical usage: 3 | 4 | >>> for boxes, paths in merge(segments): 5 | ... pass 6 | """ 7 | 8 | from match import match 9 | from vision.track.interpolation import Linear 10 | 11 | import logging 12 | 13 | logger = logging.getLogger("vatic.merge") 14 | 15 | def percentoverlap(first, second): 16 | """ 17 | Scores two paths, first and second, to see if they are the same path. 18 | 19 | A lower score is better. 0 is a perfect match. This method will assign a 20 | an extremely high score to paths that disagree on labels (a car cannot 21 | suddenly transform into a person). If labels match, then scores based 22 | off percent overlap in the intersecting timeline. 23 | """ 24 | firstboxes = first.getboxes(interpolate = True) 25 | secondboxes = second.getboxes(interpolate = True) 26 | secondboxes = dict((x.frame, x) for x in secondboxes) 27 | 28 | if first.label != second.label: 29 | return max(len(firstboxes), len(secondboxes)) + 1 30 | 31 | cost = 0 32 | for firstbox in firstboxes: 33 | if firstbox.frame in secondboxes: 34 | secondbox = secondboxes[firstbox.frame] 35 | if firstbox.lost != secondbox.lost: 36 | cost += 1 37 | else: 38 | cost += 1 - firstbox.percentoverlap(secondbox) 39 | return cost 40 | 41 | def overlapsize(first, second): 42 | """ 43 | Counts the number of frames in first that temporally overlap with second. 44 | """ 45 | return len(set(f.frame for f in first.getboxes(interpolate = True)) & 46 | set(s.frame for s in second.getboxes(interpolate = True))) 47 | 48 | def merge(segments, method = percentoverlap, threshold = 0.5): 49 | """ 50 | Takes a list of segments and attempts to find a correspondance between 51 | them by returning a list of merged paths. 52 | 53 | Uses 'method' to score two candidate paths. If the score returned by 54 | 'method' is greater than the number of overlaping frames times the 55 | threshold, then the correspondance is considered bunk and a new path 56 | is created instead. 57 | 58 | In general, if 'method' returns 0 for a perfect match and 1 for a 59 | horrible match, then 'threshold' = 0.5 is pretty good. 60 | """ 61 | logger.debug("Starting to merge!") 62 | paths = {} 63 | segments.sort(key = lambda x: x.start) 64 | for path in segments[0].paths: 65 | paths[path.id] = path.getboxes(), [path] 66 | for x, y in zip(segments, segments[1:]): 67 | logger.debug("Merging segments {0} and {1}".format(x.id, y.id)) 68 | if x.stop < y.start: 69 | logger.debug("Segments {0} and {1} do not overlap" 70 | .format(x.id, y.id)) 71 | for path in y.paths: 72 | paths[path.id] = path.getboxes(), [path] 73 | else: 74 | for first, second, score in match(x.paths, y.paths, method): 75 | logger.debug("{0} associated to {1} with score {2}" 76 | .format(first, second, score)) 77 | if second is None: 78 | continue 79 | 80 | isbirth = first is None 81 | if not isbirth: 82 | scorerequirement = threshold * overlapsize(first, second) 83 | if score > scorerequirement: 84 | logger.debug("Score {0} exceeds merge threshold of {1}" 85 | .format(score, scorerequirement)) 86 | isbirth = True 87 | else: 88 | logger.debug("Score {0} satisfies merge threshold of " 89 | "{1}" .format(score, scorerequirement)) 90 | 91 | if isbirth: 92 | paths[second.id] = second.getboxes(), [second] 93 | else: 94 | path = mergepath(paths[first.id][0], second.getboxes()) 95 | paths[first.id][1].append(second) 96 | paths[second.id] = (path, paths[first.id][1]) 97 | del paths[first.id] 98 | logger.debug("Done merging!") 99 | return paths.values() 100 | 101 | def mergepath(left, right): 102 | """ 103 | Takes two paths, left and right, and combines them into a single path by 104 | removing the duplicate annotations in the overlap region. 105 | """ 106 | 107 | rightmin = min(x.frame for x in right) 108 | 109 | boundary = (max((x.frame, x) for x in left if x.frame < rightmin), 110 | min((x.frame, x) for x in left if x.frame >= rightmin)) 111 | 112 | leftfill = Linear(boundary[0][1], boundary[1][1]) 113 | pivot = [x for x in leftfill if x.frame == rightmin][0] 114 | 115 | response = [x for x in left if x.frame < rightmin] 116 | response.append(pivot) 117 | response.extend(right[1:]) 118 | return response 119 | -------------------------------------------------------------------------------- /public/jquery-migrate-1.2.1.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery Migrate v1.2.1 | (c) 2005, 2013 jQuery Foundation, Inc. and other contributors | jquery.org/license */ 2 | jQuery.migrateMute===void 0&&(jQuery.migrateMute=!0),function(e,t,n){function r(n){var r=t.console;i[n]||(i[n]=!0,e.migrateWarnings.push(n),r&&r.warn&&!e.migrateMute&&(r.warn("JQMIGRATE: "+n),e.migrateTrace&&r.trace&&r.trace()))}function a(t,a,i,o){if(Object.defineProperty)try{return Object.defineProperty(t,a,{configurable:!0,enumerable:!0,get:function(){return r(o),i},set:function(e){r(o),i=e}}),n}catch(s){}e._definePropertyBroken=!0,t[a]=i}var i={};e.migrateWarnings=[],!e.migrateMute&&t.console&&t.console.log&&t.console.log("JQMIGRATE: Logging is active"),e.migrateTrace===n&&(e.migrateTrace=!0),e.migrateReset=function(){i={},e.migrateWarnings.length=0},"BackCompat"===document.compatMode&&r("jQuery is not compatible with Quirks Mode");var o=e("",{size:1}).attr("size")&&e.attrFn,s=e.attr,u=e.attrHooks.value&&e.attrHooks.value.get||function(){return null},c=e.attrHooks.value&&e.attrHooks.value.set||function(){return n},l=/^(?:input|button)$/i,d=/^[238]$/,p=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,f=/^(?:checked|selected)$/i;a(e,"attrFn",o||{},"jQuery.attrFn is deprecated"),e.attr=function(t,a,i,u){var c=a.toLowerCase(),g=t&&t.nodeType;return u&&(4>s.length&&r("jQuery.fn.attr( props, pass ) is deprecated"),t&&!d.test(g)&&(o?a in o:e.isFunction(e.fn[a])))?e(t)[a](i):("type"===a&&i!==n&&l.test(t.nodeName)&&t.parentNode&&r("Can't change the 'type' of an input or button in IE 6/7/8"),!e.attrHooks[c]&&p.test(c)&&(e.attrHooks[c]={get:function(t,r){var a,i=e.prop(t,r);return i===!0||"boolean"!=typeof i&&(a=t.getAttributeNode(r))&&a.nodeValue!==!1?r.toLowerCase():n},set:function(t,n,r){var a;return n===!1?e.removeAttr(t,r):(a=e.propFix[r]||r,a in t&&(t[a]=!0),t.setAttribute(r,r.toLowerCase())),r}},f.test(c)&&r("jQuery.fn.attr('"+c+"') may use property instead of attribute")),s.call(e,t,a,i))},e.attrHooks.value={get:function(e,t){var n=(e.nodeName||"").toLowerCase();return"button"===n?u.apply(this,arguments):("input"!==n&&"option"!==n&&r("jQuery.fn.attr('value') no longer gets properties"),t in e?e.value:null)},set:function(e,t){var a=(e.nodeName||"").toLowerCase();return"button"===a?c.apply(this,arguments):("input"!==a&&"option"!==a&&r("jQuery.fn.attr('value', val) no longer sets properties"),e.value=t,n)}};var g,h,v=e.fn.init,m=e.parseJSON,y=/^([^<]*)(<[\w\W]+>)([^>]*)$/;e.fn.init=function(t,n,a){var i;return t&&"string"==typeof t&&!e.isPlainObject(n)&&(i=y.exec(e.trim(t)))&&i[0]&&("<"!==t.charAt(0)&&r("$(html) HTML strings must start with '<' character"),i[3]&&r("$(html) HTML text after last tag is ignored"),"#"===i[0].charAt(0)&&(r("HTML string cannot start with a '#' character"),e.error("JQMIGRATE: Invalid selector string (XSS)")),n&&n.context&&(n=n.context),e.parseHTML)?v.call(this,e.parseHTML(i[2],n,!0),n,a):v.apply(this,arguments)},e.fn.init.prototype=e.fn,e.parseJSON=function(e){return e||null===e?m.apply(this,arguments):(r("jQuery.parseJSON requires a valid JSON string"),null)},e.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||0>e.indexOf("compatible")&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e.browser||(g=e.uaMatch(navigator.userAgent),h={},g.browser&&(h[g.browser]=!0,h.version=g.version),h.chrome?h.webkit=!0:h.webkit&&(h.safari=!0),e.browser=h),a(e,"browser",e.browser,"jQuery.browser is deprecated"),e.sub=function(){function t(e,n){return new t.fn.init(e,n)}e.extend(!0,t,this),t.superclass=this,t.fn=t.prototype=this(),t.fn.constructor=t,t.sub=this.sub,t.fn.init=function(r,a){return a&&a instanceof e&&!(a instanceof t)&&(a=t(a)),e.fn.init.call(this,r,a,n)},t.fn.init.prototype=t.fn;var n=t(document);return r("jQuery.sub() is deprecated"),t},e.ajaxSetup({converters:{"text json":e.parseJSON}});var b=e.fn.data;e.fn.data=function(t){var a,i,o=this[0];return!o||"events"!==t||1!==arguments.length||(a=e.data(o,t),i=e._data(o,t),a!==n&&a!==i||i===n)?b.apply(this,arguments):(r("Use of jQuery.fn.data('events') is deprecated"),i)};var j=/\/(java|ecma)script/i,w=e.fn.andSelf||e.fn.addBack;e.fn.andSelf=function(){return r("jQuery.fn.andSelf() replaced by jQuery.fn.addBack()"),w.apply(this,arguments)},e.clean||(e.clean=function(t,a,i,o){a=a||document,a=!a.nodeType&&a[0]||a,a=a.ownerDocument||a,r("jQuery.clean() is deprecated");var s,u,c,l,d=[];if(e.merge(d,e.buildFragment(t,a).childNodes),i)for(c=function(e){return!e.type||j.test(e.type)?o?o.push(e.parentNode?e.parentNode.removeChild(e):e):i.appendChild(e):n},s=0;null!=(u=d[s]);s++)e.nodeName(u,"script")&&c(u)||(i.appendChild(u),u.getElementsByTagName!==n&&(l=e.grep(e.merge([],u.getElementsByTagName("script")),c),d.splice.apply(d,[s+1,0].concat(l)),s+=l.length));return d});var Q=e.event.add,x=e.event.remove,k=e.event.trigger,N=e.fn.toggle,T=e.fn.live,M=e.fn.die,S="ajaxStart|ajaxStop|ajaxSend|ajaxComplete|ajaxError|ajaxSuccess",C=RegExp("\\b(?:"+S+")\\b"),H=/(?:^|\s)hover(\.\S+|)\b/,A=function(t){return"string"!=typeof t||e.event.special.hover?t:(H.test(t)&&r("'hover' pseudo-event is deprecated, use 'mouseenter mouseleave'"),t&&t.replace(H,"mouseenter$1 mouseleave$1"))};e.event.props&&"attrChange"!==e.event.props[0]&&e.event.props.unshift("attrChange","attrName","relatedNode","srcElement"),e.event.dispatch&&a(e.event,"handle",e.event.dispatch,"jQuery.event.handle is undocumented and deprecated"),e.event.add=function(e,t,n,a,i){e!==document&&C.test(t)&&r("AJAX events should be attached to document: "+t),Q.call(this,e,A(t||""),n,a,i)},e.event.remove=function(e,t,n,r,a){x.call(this,e,A(t)||"",n,r,a)},e.fn.error=function(){var e=Array.prototype.slice.call(arguments,0);return r("jQuery.fn.error() is deprecated"),e.splice(0,0,"error"),arguments.length?this.bind.apply(this,e):(this.triggerHandler.apply(this,e),this)},e.fn.toggle=function(t,n){if(!e.isFunction(t)||!e.isFunction(n))return N.apply(this,arguments);r("jQuery.fn.toggle(handler, handler...) is deprecated");var a=arguments,i=t.guid||e.guid++,o=0,s=function(n){var r=(e._data(this,"lastToggle"+t.guid)||0)%o;return e._data(this,"lastToggle"+t.guid,r+1),n.preventDefault(),a[r].apply(this,arguments)||!1};for(s.guid=i;a.length>o;)a[o++].guid=i;return this.click(s)},e.fn.live=function(t,n,a){return r("jQuery.fn.live() is deprecated"),T?T.apply(this,arguments):(e(this.context).on(t,this.selector,n,a),this)},e.fn.die=function(t,n){return r("jQuery.fn.die() is deprecated"),M?M.apply(this,arguments):(e(this.context).off(t,this.selector||"**",n),this)},e.event.trigger=function(e,t,n,a){return n||C.test(e)||r("Global events are undocumented and deprecated"),k.call(this,e,t,n||document,a)},e.each(S.split("|"),function(t,n){e.event.special[n]={setup:function(){var t=this;return t!==document&&(e.event.add(document,n+"."+e.guid,function(){e.event.trigger(n,null,t,!0)}),e._data(this,n,e.guid++)),!1},teardown:function(){return this!==document&&e.event.remove(document,n+"."+e._data(this,n)),!1}}})}(jQuery,window); -------------------------------------------------------------------------------- /public/stylesheet.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | -webkit-user-select: none; 4 | -khtml-user-select: none; 5 | -moz-user-select: none; 6 | -o-user-select: none; 7 | user-select: none; 8 | } 9 | 10 | label, .button 11 | { 12 | font-size : 12px !important; 13 | } 14 | 15 | table td 16 | { 17 | vertical-align : top; 18 | } 19 | 20 | h1 21 | { 22 | font-family : Arial; 23 | margin : 0; 24 | padding : 0; 25 | font-size : 30px; 26 | font-weight : bold; 27 | } 28 | 29 | h2 30 | { 31 | font-family : Arial; 32 | margin : 0; 33 | padding : 0; 34 | font-size : 26px; 35 | font-weight : bold; 36 | } 37 | 38 | h3 39 | { 40 | font-family : Arial; 41 | font-size : 20px; 42 | font-weight : bold; 43 | padding : 5px 0; 44 | margin : 0; 45 | } 46 | 47 | p, li 48 | { 49 | font-family : Arial; 50 | font-size : 16px; 51 | } 52 | 53 | ul 54 | { 55 | margin-top : 10px; 56 | margin-bottom : 10px; 57 | } 58 | 59 | code 60 | { 61 | padding : 0 5px; 62 | } 63 | 64 | #turkic_acceptfirst 65 | { 66 | background-color : #cd0a0a; 67 | color : #fff; 68 | } 69 | 70 | #turkic_workerstats 71 | { 72 | background-color : #E4E4E4; 73 | } 74 | 75 | #loadingscreen 76 | { 77 | width : 700px; 78 | margin : 0 auto; 79 | margin-top : 100px; 80 | } 81 | 82 | #loadingscreentext 83 | { 84 | font-weight : bold; 85 | font-size : 20px; 86 | font-family : Arial; 87 | } 88 | 89 | #loadingscreenslider 90 | { 91 | background-color : #D5E8FF; 92 | width : 100%; 93 | height : 50px; 94 | } 95 | 96 | #loadingscreenslider .progressbar 97 | { 98 | background-color : #0F7BFF; 99 | } 100 | 101 | #loadingscreenslider .text 102 | { 103 | color : #fff; 104 | font-weight : bold; 105 | margin-top : 15px; 106 | margin-left :10px; 107 | font-family : Arial; 108 | } 109 | 110 | .loadingscreentip 111 | { 112 | font-family : Arial; 113 | margin-top : 25px; 114 | color : #333; 115 | } 116 | 117 | #loadingscreeninstructions 118 | { 119 | float : right; 120 | margin-top : -10px; 121 | font-weight : bold; 122 | } 123 | 124 | #annotatescreen 125 | { 126 | width : 925px; 127 | margin : 0 auto; 128 | } 129 | 130 | #videoframe 131 | { 132 | background-color : #000; 133 | display : block; 134 | } 135 | 136 | #playbutton 137 | { 138 | font-weight : bold; 139 | width : 80px; 140 | } 141 | 142 | #newobjectcontainer 143 | { 144 | text-align : center; 145 | } 146 | 147 | #newobjectbutton 148 | { 149 | font-weight : bold; 150 | } 151 | 152 | #speedcontrol 153 | { 154 | display : inline; 155 | } 156 | 157 | #annotateoptions 158 | { 159 | display : inline; 160 | } 161 | 162 | #openadvancedoptions 163 | { 164 | float : right; 165 | } 166 | 167 | #advancedoptions 168 | { 169 | float : right; 170 | } 171 | 172 | #music 173 | { 174 | position : absolute; 175 | top : -100000px; 176 | left : -100000px; 177 | width : 1px; 178 | height : 1px; 179 | } 180 | 181 | 182 | #objectcontainer 183 | { 184 | overflow : auto; 185 | height : 100%; 186 | } 187 | 188 | #failedverificationdialog 189 | { 190 | position : fixed; 191 | top : 50px; 192 | left : 50%; 193 | width : 550px; 194 | margin-left : -275px; 195 | z-index : 10000000; 196 | padding : 10px; 197 | border : 5px solid #EB1414; 198 | background-color : #FFC2C2; 199 | overflow : auto; 200 | } 201 | 202 | #failedverificationbutton 203 | { 204 | font-weight : bold; 205 | } 206 | 207 | #instructions 208 | { 209 | font-family : Arial; 210 | font-size : 16px; 211 | padding : 5px 0; 212 | } 213 | 214 | #instructionsbutton 215 | { 216 | float : right; 217 | } 218 | 219 | #instructionsdialog 220 | { 221 | position : fixed; 222 | top : 50px; 223 | left : 50%; 224 | height : 480px; 225 | width : 720px; 226 | margin-left : -360px; 227 | background-color : #fff; 228 | z-index : 10000000000000; 229 | padding : 10px; 230 | border : 2px solid #000; 231 | overflow : auto; 232 | } 233 | 234 | #instructionsclosetop 235 | { 236 | float : right; 237 | font-weight : bold; 238 | } 239 | 240 | .keyboardshortcuts 241 | { 242 | list-style-type : none; 243 | margin : 0; 244 | margin-left : 10px; 245 | padding : 0; 246 | } 247 | 248 | .keyboardshortcuts code 249 | { 250 | display : block; 251 | width : 100px; 252 | float : left; 253 | } 254 | 255 | .trackobject 256 | { 257 | border : 2px solid black; 258 | font-family : Arial; 259 | font-size : 14px; 260 | } 261 | 262 | .trackobjectfoldedup 263 | { 264 | } 265 | 266 | .trackobjectfoldeddown 267 | { 268 | } 269 | 270 | .trackobject p, .trackobject li, .trackobject label 271 | { 272 | font-family : Arial; 273 | font-size : 14px !important; 274 | } 275 | 276 | .trackobject .label:hover 277 | { 278 | font-weight : bold; 279 | } 280 | 281 | .trackobject p 282 | { 283 | padding : 2px; 284 | margin : 0; 285 | } 286 | 287 | .trackobject ul, ol 288 | { 289 | margin : 0; 290 | padding : 0; 291 | margin-left : 30px; 292 | } 293 | 294 | .trackobjectheader 295 | { 296 | margin : 0; 297 | padding : 3px !important; 298 | /*cursor : pointer;*/ 299 | } 300 | 301 | .trackobjectheader:hover 302 | { 303 | /*text-decoration : underline;*/ 304 | } 305 | 306 | .trackobjectheader .ui-icon 307 | { 308 | float : left; 309 | } 310 | 311 | .boundingbox 312 | { 313 | position : absolute !important; 314 | border-style : solid; 315 | border-width : 2px; 316 | z-index : 5; 317 | } 318 | 319 | .boundingbox .boundingboxtext 320 | { 321 | font-size : 14px; 322 | /*margin-top : -18px;*/ 323 | color : #fff; 324 | 325 | position : absolute; 326 | margin-left : -80px; 327 | margin-top : -2px; 328 | width : 75px; 329 | text-align : right; 330 | 331 | padding : 1px; 332 | padding-right : 3px; 333 | cursor : default; 334 | z-index : 0; 335 | 336 | text-shadow: -1px 0 2px #000, 0px 1px 2px #000, 1px 0 2px #000, 0 -1px 2px #000; 337 | -moz-text-shadow: -1px 0 2px #000, 0px 1px 2px #000, 1px 0 2px #000, 0 -1px 2px #000; 338 | -webkit-text-shadow: -1px 0 2px #000, 0px 1px 2px #000, 1px 0 2px #000, 0 -1px 2px #000; 339 | } 340 | 341 | .boundingboxhighlight .boundingboxtext 342 | { 343 | text-shadow: -1px 0 5px #000, 0px 1px 5px #000, 1px 0 5px #000, 0 -1px 5px #000; 344 | -moz-text-shadow: -1px 0 5px #000, 0px 1px 5px #000, 1px 0 5px #000, 0 -1px 5px #000; 345 | -webkit-text-shadow: -1px 0 5px #000, 0px 1px 5px #000, 1px 0 5px #000, 0 -1px 5px #000; 346 | z-index : 1; 347 | } 348 | 349 | .boundingbox .fill 350 | { 351 | width : 100%; 352 | height : 100%; 353 | display : block; 354 | display : none; 355 | } 356 | 357 | .boundingboxhighlight 358 | { 359 | z-index : 10; 360 | } 361 | 362 | .boundingboxhighlight .fill 363 | { 364 | opacity : 0.25; 365 | display : block; 366 | } 367 | 368 | .boundingboxoccluded 369 | { 370 | border-style : dashed; 371 | } 372 | 373 | .ui-resizable-disabled 374 | { 375 | opacity : 1.0 !important; 376 | } 377 | 378 | .boundingboxdim 379 | { 380 | opacity : 0.3 !important; 381 | } 382 | 383 | .boundingboxlocked 384 | { 385 | z-index : 0; 386 | opacity : 0.3 !important; 387 | } 388 | 389 | .boundingboxhighlight 390 | { 391 | opacity : 1 !important; 392 | } 393 | 394 | #submitbar 395 | { 396 | text-align : center; 397 | vertical-align : bottom; 398 | } 399 | 400 | #submitdialog 401 | { 402 | position : absolute; 403 | top : 200px; 404 | left : 0; 405 | width : 100%; 406 | text-align : center; 407 | font-size : 100px; 408 | font-weight : bold; 409 | font-family : Arial; 410 | z-index : 100000; 411 | } 412 | 413 | .boxtooltip 414 | { 415 | position : absolute; 416 | width : 200px; 417 | height : 200px; 418 | z-index : 100; 419 | border : 5px solid #000; 420 | background-color : #000; 421 | background-repeat : no-repeat; 422 | -moz-box-shadow: 2px 2px 10px #000, -2px 2px 10px #000, 2px -2px 10px #000, -2px -2px 10px #000; 423 | -webkit-box-shadow: 2px 2px 10px #000, -2px 2px 10px #000, 2px -2px 10px #000, -2px -2px 10px #000; 424 | box-shadow: 2px 2px 10px #000, -2px 2px 10px #000, 2px -2px 10px #000, -2px -2px 10px #000; 425 | } 426 | 427 | .boxtooltip .boundingbox 428 | { 429 | position : relative; 430 | z-index : 101; 431 | } 432 | -------------------------------------------------------------------------------- /public/instructions.js: -------------------------------------------------------------------------------- 1 | function instructions(job, h) 2 | { 3 | h.append("

Important Instructions

"); 4 | h.append("

In this task, we ask you to annotate a video. You are to draw a box around every object of interest and track each object for the entire video. These instructions will give you tips on how to best use our tool.

"); 5 | 6 | h.append("

Crash Course

"); 7 | var str = ""; 27 | h.append(str); 28 | 29 | h.append("

Getting Started

"); 30 | h.append(""); 31 | h.append("

Click the New Object button to start annotating your first object. Position your cursor over the view screen to click on the corner of an object of interest. Use the cross hairs to line up your click. Click on another corner to finish drawing the box. The rectangle should tightly and completely enclose the object you are annotating. Resize the box, if necessary, by dragging the edges of the box.

"); 32 | 33 | h.append(""); 34 | h.append("

On the right, directly below the New Object button, you will find a colorful box. The box is prompting you to input which type of object you have labeled. Click the correst response.

"); 35 | 36 | if (job.skip > 0) 37 | { 38 | h.append("

Press the Play button. The video will play. When the video automatically pauses, adjust the boxes. Using your mouse, drag-and-drop the box to the correct position and resize if necessary. Continue in this fashion until you have reached the end of the video.

"); 39 | } 40 | else 41 | { 42 | h.append("

Press the Play button. The video will begin to play forward. After the object you are tracking has moved a bit, click Pause. Using your mouse, drag-and-drop the box to the correct position and resize if necessary. Continue in this fashion until you have reached the end of the video.

"); 43 | } 44 | 45 | if (job.perobject > 0) 46 | { 47 | h.append("

Once you have reached the end, you should rewind by pressing the rewind button (next to Play) and repeat this process for every object of interest. You are welcome to annotate multiple objects each playthrough. We will pay you a bonus for every object that you annotate.

"); 48 | } 49 | else 50 | { 51 | h.append("

Once you have reached the end, you should rewind by pressing the rewind button (next to Play) and repeat this process for every object of interest. You are welcome to annotate multiple objects each playthrough.

"); 52 | } 53 | 54 | h.append(""); 55 | h.append("

If an object leaves the screen, mark the Outside of view frame checkbox for the corresponding sidebar rectangle. Make sure you click the right button. When you mouse over the controls, the corresponding rectangle will light up in the view screen. Likewise, if the object you are tracking is still in the view frame but the view is obstructed (e.g., inside a car), mark the Occluded or obstructed checkbox. When the object becomes visible again, remember to uncheck these boxes. If there are additional checkboxes describing attributes, mark those boxes for the duration that it applies. For example, only mark \"Walking\" when the person is walking.

"); 56 | 57 | h.append("

If there are many objects on the screen, it can become difficult to select the right bounding box. By pressing the lock button on an object's sidebar rectangle, you can prevent changes to that track. Press the lock button again to renable modifications.

"); 58 | 59 | h.append("

Remembering which box correspond to which box can be confusing. If you click on a box in the view screen, a tooltip will pop that will attempt to remind you of the box's identity.

"); 60 | 61 | h.append("

When you are ready to submit your work, rewind the video and watch it through one more time. Does each rectangle follow the object it is tracking for the entire sequence? If you find a spot where it misses, press Pause and adjust the box. After you have checked your work, press the Submit HIT button. We will pay you as soon as possible.

"); 62 | 63 | h.append("

How We Accept Your Work

"); 64 | h.append("

We will hand review your work and we will only accept high quality work. Your annotations are not compared against other workers. Follow these guidelines to ensure your work is accepted:

"); 65 | 66 | h.append("

Label Every Object

") 67 | h.append(''); 68 | //h.append(""); 69 | //h.append(""); 70 | 71 | if (job.perobject > 0) 72 | { 73 | h.append("

Every object of interest should be labeled for the entire video. The above work was accepted because every object has a box around it. An object is not labeled more than once. Even if the object does not move, you must label it. We will pay you a bonus for every object you annotate.

"); 74 | } 75 | else 76 | { 77 | h.append("

Every object of interest should be labeled for the entire video. The above work was accepted because every object has a box around it. An object is not labeled more than once. Even if the object does not move, you must label it.

"); 78 | } 79 | 80 | h.append("

Boxes Are Tight

"); 81 | h.append("
GoodBad
"); 82 | h.append("

The boxes you draw must be tight. They boxes must fit around the object as close as possible. The loose annotation on the right would be rejected while the tight annotation on the left will be accepted.

"); 83 | 84 | h.append("

Entire Video is Labeled

") 85 | h.append(" "); 86 | h.append(" "); 87 | h.append("
"); 88 | h.append("

The entire video sequence must be labeled. When an object moves, you must update its position. A box must describe only one object. You should never change which object identity a particular box tracks.

"); 89 | 90 | h.append("

Disappearing and Reappearing Objects

"); 91 | h.append("

In order for your work to be accepted, you must correctly label objects as they enter and leave the scene. We want you to annotate the moment each object enters and leaves the scene. As it is often difficult to pinpoint the exact moment an object enters or leaves the scene, we allow some mistakes here, but only slightly!

"); 92 | 93 | h.append("
"); 94 | 95 | h.append("

If one object enters another object (such as a person getting inside a car), you should mark the disappearing object as outside of the view frame. Likewise, you should start annotating an object the moment it steps out of the enclosing object.

"); 96 | 97 | h.append("
"); 98 | 99 | h.append("

If an object disappears from the scene and the exact same object reappears later in the scene, you must mark that object as back inside the view frame. Do not draw a new object for its second appearance. Simply find the corresponding right-column rectangle and uncheck the Outside of view frame checkbox and position the box.

"); 100 | 101 | h.append("

Advanced Features

"); 102 | h.append("

We have provided some advanced tools for videos that are especially difficult. Clicking the Options button will enable the advanced options.

"); 103 | h.append(""); 108 | 109 | h.append("

Keyboard Shortcuts

"); 110 | h.append("

These keyboard shortcuts are available for your convenience:

"); 111 | h.append(''); 121 | } 122 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | import turkic.database 2 | import turkic.models 3 | from sqlalchemy import Column, Integer, Float, String, Boolean, Text 4 | from sqlalchemy import ForeignKey, Table, PickleType 5 | from sqlalchemy.orm import relationship, backref 6 | from PIL import Image 7 | import vision 8 | from vision.track.interpolation import LinearFill 9 | import random 10 | import logging 11 | import config 12 | 13 | logger = logging.getLogger("vatic.models") 14 | 15 | boxes_attributes = Table("boxes2attributes", turkic.database.Base.metadata, 16 | Column("box_id", Integer, ForeignKey("boxes.id")), 17 | Column("attribute_id", Integer, ForeignKey("attributes.id"))) 18 | 19 | class Video(turkic.database.Base): 20 | __tablename__ = "videos" 21 | 22 | id = Column(Integer, primary_key = True) 23 | slug = Column(String(250), index = True) 24 | width = Column(Integer) 25 | height = Column(Integer) 26 | totalframes = Column(Integer) 27 | location = Column(String(250)) 28 | skip = Column(Integer, default = 0, nullable = False) 29 | perobjectbonus = Column(Float, default = 0) 30 | completionbonus = Column(Float, default = 0) 31 | trainwithid = Column(Integer, ForeignKey(id)) 32 | trainwith = relationship("Video", remote_side = id) 33 | isfortraining = Column(Boolean, default = False) 34 | trainvalidator = Column(PickleType, nullable = True, default = None) 35 | blowradius = Column(Integer, default = 5) 36 | 37 | def __getitem__(self, frame): 38 | path = Video.getframepath(frame, self.location) 39 | return Image.open(path) 40 | 41 | @classmethod 42 | def getframepath(cls, frame, base = None): 43 | l1 = frame / 10000 44 | l2 = frame / 100 45 | path = "{0}/{1}/{2}.jpg".format(l1, l2, frame) 46 | if base is not None: 47 | path = "{0}/{1}".format(base, path) 48 | return path 49 | 50 | @property 51 | def cost(self): 52 | cost = 0 53 | for segment in self.segments: 54 | cost += segment.cost 55 | return cost 56 | 57 | @property 58 | def numjobs(self): 59 | count = 0 60 | for segment in self.segments: 61 | for job in segment.jobs: 62 | count += 1 63 | return count 64 | 65 | @property 66 | def numcompleted(self): 67 | count = 0 68 | for segment in self.segments: 69 | for job in segment.jobs: 70 | if job.completed: 71 | count += 1 72 | return count 73 | 74 | class Label(turkic.database.Base): 75 | __tablename__ = "labels" 76 | 77 | id = Column(Integer, primary_key = True) 78 | text = Column(String(250)) 79 | videoid = Column(Integer, ForeignKey(Video.id)) 80 | video = relationship(Video, backref = backref("labels", 81 | cascade = "all,delete")) 82 | 83 | class Attribute(turkic.database.Base): 84 | __tablename__ = "attributes" 85 | 86 | id = Column(Integer, primary_key = True) 87 | text = Column(String(250)) 88 | labelid = Column(Integer, ForeignKey(Label.id)) 89 | label = relationship(Label, backref = backref("attributes", 90 | cascade = "all,delete")) 91 | 92 | def __str__(self): 93 | return self.text 94 | 95 | class Segment(turkic.database.Base): 96 | __tablename__ = "segments" 97 | 98 | id = Column(Integer, primary_key = True) 99 | videoid = Column(Integer, ForeignKey(Video.id)) 100 | video = relationship(Video, backref = backref("segments", 101 | cascade = "all,delete")) 102 | start = Column(Integer) 103 | stop = Column(Integer) 104 | 105 | @property 106 | def paths(self): 107 | paths = [] 108 | for job in self.jobs: 109 | if job.useful: 110 | paths.extend(job.paths) 111 | return paths 112 | 113 | @property 114 | def cost(self): 115 | cost = 0 116 | for job in self.jobs: 117 | cost += job.cost 118 | return cost 119 | 120 | class Job(turkic.models.HIT): 121 | __tablename__ = "jobs" 122 | __mapper_args__ = {"polymorphic_identity": "jobs"} 123 | 124 | id = Column(Integer, ForeignKey(turkic.models.HIT.id), 125 | primary_key = True) 126 | segmentid = Column(Integer, ForeignKey(Segment.id)) 127 | segment = relationship(Segment, 128 | backref = backref("jobs", 129 | cascade = "all,delete")) 130 | istraining = Column(Boolean, default = False) 131 | 132 | def getpage(self): 133 | return "?id={0}".format(self.id) 134 | 135 | def markastraining(self): 136 | """ 137 | Marks this job as the result of a training run. This will automatically 138 | swap this job over to the training video and produce a replacement. 139 | """ 140 | replacement = Job(segment = self.segment, group = self.group) 141 | self.segment = self.segment.video.trainwith.segments[0] 142 | self.group = self.segment.jobs[0].group 143 | self.istraining = True 144 | 145 | logger.debug("Job is now training and replacement built") 146 | 147 | return replacement 148 | 149 | def invalidate(self): 150 | """ 151 | Invalidates this path because it is poor work. The new job will be 152 | respawned automatically for different workers to complete. 153 | """ 154 | self.useful = False 155 | # is this a training task? if yes, we don't want to respawn 156 | if not self.istraining: 157 | return Job(segment = self.segment, group = self.group) 158 | 159 | def check(self): 160 | if len(self.paths) > config.maxobjects: 161 | raise RuntimeError("Job {0} has too many objects to process " 162 | "payment. Please verify this is not an " 163 | "attempt to hack us and increase the " 164 | "limit in config.py".format(self.id)) 165 | return True 166 | 167 | @property 168 | def trainingjob(self): 169 | return self.segment.video.trainwith.segments[0].jobs[0] 170 | 171 | @property 172 | def validator(self): 173 | return self.segment.video.trainvalidator 174 | 175 | @property 176 | def cost(self): 177 | if not self.completed: 178 | return 0 179 | return self.bonusamount + self.group.cost + self.donatedamount 180 | 181 | def __iter__(self): 182 | return self.paths 183 | 184 | class Path(turkic.database.Base): 185 | __tablename__ = "paths" 186 | 187 | id = Column(Integer, primary_key = True) 188 | jobid = Column(Integer, ForeignKey(Job.id)) 189 | job = relationship(Job, backref = backref("paths", cascade="all,delete")) 190 | labelid = Column(Integer, ForeignKey(Label.id)) 191 | label = relationship(Label, cascade = "none", backref = "paths") 192 | 193 | interpolatecache = None 194 | 195 | def getboxes(self, interpolate = False, bind = False, label = False): 196 | result = [x.getbox() for x in self.boxes] 197 | result.sort(key = lambda x: x.frame) 198 | if interpolate: 199 | if not self.interpolatecache: 200 | self.interpolatecache = LinearFill(result) 201 | result = self.interpolatecache 202 | 203 | if bind: 204 | result = Path.bindattributes(self.attributes, result) 205 | 206 | if label: 207 | for box in result: 208 | box.attributes.insert(0, self.label.text) 209 | 210 | return result 211 | 212 | @classmethod 213 | def bindattributes(cls, attributes, boxes): 214 | attributes = sorted(attributes, key = lambda x: x.frame) 215 | 216 | byid = {} 217 | for attribute in attributes: 218 | if attribute.attributeid not in byid: 219 | byid[attribute.attributeid] = [] 220 | byid[attribute.attributeid].append(attribute) 221 | 222 | for attributes in byid.values(): 223 | for prev, cur in zip(attributes, attributes[1:]): 224 | if prev.value: 225 | for box in boxes: 226 | if prev.frame <= box.frame < cur.frame: 227 | if prev.attribute not in box.attributes: 228 | box.attributes.append(prev.attribute) 229 | last = attributes[-1] 230 | if last.value: 231 | for box in boxes: 232 | if last.frame <= box.frame: 233 | if last.attribute not in box.attributes: 234 | box.attributes.append(last.attribute) 235 | 236 | return boxes 237 | 238 | def __repr__(self): 239 | return "".format(self.id) 240 | 241 | class AttributeAnnotation(turkic.database.Base): 242 | __tablename__ = "attribute_annotations" 243 | 244 | id = Column(Integer, primary_key = True) 245 | pathid = Column(Integer, ForeignKey(Path.id)) 246 | path = relationship(Path, 247 | backref = backref("attributes", 248 | cascade = "all,delete")) 249 | attributeid = Column(Integer, ForeignKey(Attribute.id)) 250 | attribute = relationship(Attribute) 251 | frame = Column(Integer) 252 | value = Column(Boolean, default = False) 253 | 254 | def __repr__(self): 255 | return ("AttributeAnnotation(pathid = {0}, " 256 | "attributeid = {1}, " 257 | "frame = {2}, " 258 | "value = {3})").format(self.pathid, 259 | self.attributeid, 260 | self.frame, 261 | self.value) 262 | 263 | class Box(turkic.database.Base): 264 | __tablename__ = "boxes" 265 | 266 | id = Column(Integer, primary_key = True) 267 | pathid = Column(Integer, ForeignKey(Path.id)) 268 | path = relationship(Path, 269 | backref = backref("boxes", cascade = "all,delete")) 270 | xtl = Column(Integer) 271 | ytl = Column(Integer) 272 | xbr = Column(Integer) 273 | ybr = Column(Integer) 274 | frame = Column(Integer) 275 | occluded = Column(Boolean, default = False) 276 | outside = Column(Boolean, default = False) 277 | 278 | def getbox(self): 279 | return vision.Box(self.xtl, self.ytl, self.xbr, self.ybr, 280 | self.frame, self.outside, self.occluded, 0) 281 | 282 | class PerObjectBonus(turkic.models.BonusSchedule): 283 | __tablename__ = "per_object_bonuses" 284 | __mapper_args__ = {"polymorphic_identity": "per_object_bonuses"} 285 | 286 | id = Column(Integer, ForeignKey(turkic.models.BonusSchedule.id), 287 | primary_key = True) 288 | amount = Column(Float, default = 0.0, nullable = False) 289 | 290 | def description(self): 291 | return (self.amount, "per object") 292 | 293 | def award(self, hit): 294 | paths = len(hit.paths) 295 | amount = paths * self.amount 296 | if amount > 0: 297 | hit.awardbonus(amount, "For {0} objects".format(paths)) 298 | logger.debug("Awarded per-object bonus of ${0:.2f} for {1} paths" 299 | .format(amount, paths)) 300 | else: 301 | logger.debug("No award for per-object bonus because 0 paths") 302 | 303 | class CompletionBonus(turkic.models.BonusSchedule): 304 | __tablename__ = "completion_bonuses" 305 | __mapper_args__ = {"polymorphic_identity": "completion_bonuses"} 306 | 307 | id = Column(Integer, ForeignKey(turkic.models.BonusSchedule.id), 308 | primary_key = True) 309 | amount = Column(Float, default = 0.0, nullable = False) 310 | 311 | def description(self): 312 | return (self.amount, "if complete") 313 | 314 | def award(self, hit): 315 | hit.awardbonus(self.amount, "For complete annotation.") 316 | logger.debug("Awarded completion bonus of ${0:.2f}" 317 | .format(self.amount)) 318 | -------------------------------------------------------------------------------- /public/ui.js: -------------------------------------------------------------------------------- 1 | var ui_disabled = 0; 2 | 3 | function ui_build(job) 4 | { 5 | var screen = ui_setup(job); 6 | var videoframe = $("#videoframe"); 7 | var player = new VideoPlayer(videoframe, job); 8 | var tracks = new TrackCollection(player, job); 9 | var objectui = new TrackObjectUI($("#newobjectbutton"), $("#objectcontainer"), videoframe, job, player, tracks); 10 | 11 | ui_setupbuttons(job, player, tracks); 12 | ui_setupslider(player); 13 | ui_setupsubmit(job, tracks); 14 | ui_setupclickskip(job, player, tracks, objectui); 15 | ui_setupkeyboardshortcuts(job, player); 16 | ui_loadprevious(job, objectui); 17 | 18 | $("#newobjectbutton").click(function() { 19 | if (!mturk_submitallowed()) 20 | { 21 | $("#turkic_acceptfirst").effect("pulsate"); 22 | } 23 | }); 24 | } 25 | 26 | function ui_setup(job) 27 | { 28 | var screen = $("
").appendTo(container); 29 | 30 | $("" + 31 | "" + 32 | "" + 33 | "" + 34 | "" + 35 | "" + 36 | "" + 37 | "" + 38 | "" + 39 | "" + 40 | "" + 41 | "" + 42 | "" + 43 | "" + 44 | "" + 45 | "" + 46 | "
Instructions
Annotate every object, even stationary and obstructed objects, for the entire video.
").appendTo(screen).css("width", "100%"); 47 | 48 | 49 | var playerwidth = Math.max(720, job.width); 50 | 51 | 52 | $("#videoframe").css({"width": job.width + "px", 53 | "height": job.height + "px", 54 | "margin": "0 auto"}) 55 | .parent().css("width", playerwidth + "px"); 56 | 57 | $("#sidebar").css({"height": job.height + "px", 58 | "width": "205px"}); 59 | 60 | $("#annotatescreen").css("width", (playerwidth + 205) + "px"); 61 | 62 | $("#bottombar").append("
"); 63 | $("#bottombar").append("
Rewind
"); 64 | $("#bottombar").append("
Play
"); 65 | 66 | $("#topbar").append("
" + 67 | "
New Object
"); 68 | 69 | $("
").appendTo("#sidebar"); 70 | 71 | $("
Options
") 72 | .button({ 73 | icons: { 74 | primary: "ui-icon-wrench" 75 | } 76 | }).appendTo($("#advancedoptions").parent()).click(function() { 77 | eventlog("options", "Show advanced options"); 78 | $(this).remove(); 79 | $("#advancedoptions").show(); 80 | }); 81 | 82 | $("#advancedoptions").hide(); 83 | 84 | $("#advancedoptions").append( 85 | "" + 86 | " " + 87 | "" + 88 | " " + 89 | "" + 90 | " "); 91 | 92 | $("#advancedoptions").append( 93 | "
" + 94 | "" + 96 | "" + 97 | "" + 99 | "" + 100 | "" + 102 | "" + 103 | "" + 105 | "" + 106 | "
"); 107 | 108 | $("#submitbar").append("
Submit HIT
"); 109 | 110 | if (mturk_isoffline()) 111 | { 112 | $("#submitbutton").html("Save Work"); 113 | } 114 | 115 | return screen; 116 | } 117 | 118 | function ui_setupbuttons(job, player, tracks) 119 | { 120 | $("#instructionsbutton").click(function() { 121 | player.pause(); 122 | ui_showinstructions(job); 123 | }).button({ 124 | icons: { 125 | primary: "ui-icon-newwin" 126 | } 127 | }); 128 | 129 | $("#playbutton").click(function() { 130 | if (!$(this).button("option", "disabled")) 131 | { 132 | player.toggle(); 133 | 134 | if (player.paused) 135 | { 136 | eventlog("playpause", "Paused video"); 137 | } 138 | else 139 | { 140 | eventlog("playpause", "Play video"); 141 | } 142 | } 143 | }).button({ 144 | disabled: false, 145 | icons: { 146 | primary: "ui-icon-play" 147 | } 148 | }); 149 | 150 | $("#rewindbutton").click(function() { 151 | if (ui_disabled) return; 152 | player.pause(); 153 | player.seek(player.job.start); 154 | eventlog("rewind", "Rewind to start"); 155 | }).button({ 156 | disabled: true, 157 | icons: { 158 | primary: "ui-icon-seek-first" 159 | } 160 | }); 161 | 162 | player.onplay.push(function() { 163 | $("#playbutton").button("option", { 164 | label: "Pause", 165 | icons: { 166 | primary: "ui-icon-pause" 167 | } 168 | }); 169 | }); 170 | 171 | player.onpause.push(function() { 172 | $("#playbutton").button("option", { 173 | label: "Play", 174 | icons: { 175 | primary: "ui-icon-play" 176 | } 177 | }); 178 | }); 179 | 180 | player.onupdate.push(function() { 181 | if (player.frame == player.job.stop) 182 | { 183 | $("#playbutton").button("option", "disabled", true); 184 | } 185 | else if ($("#playbutton").button("option", "disabled")) 186 | { 187 | $("#playbutton").button("option", "disabled", false); 188 | } 189 | 190 | if (player.frame == player.job.start) 191 | { 192 | $("#rewindbutton").button("option", "disabled", true); 193 | } 194 | else if ($("#rewindbutton").button("option", "disabled")) 195 | { 196 | $("#rewindbutton").button("option", "disabled", false); 197 | } 198 | }); 199 | 200 | $("#speedcontrol").buttonset(); 201 | $("input[name='speedcontrol']").click(function() { 202 | player.fps = parseInt($(this).val().split(",")[0]); 203 | player.playdelta = parseInt($(this).val().split(",")[1]); 204 | console.log("Change FPS to " + player.fps); 205 | console.log("Change play delta to " + player.playdelta); 206 | if (!player.paused) 207 | { 208 | player.pause(); 209 | player.play(); 210 | } 211 | eventlog("speedcontrol", "FPS = " + player.fps + " and delta = " + player.playdelta); 212 | }); 213 | 214 | $("#annotateoptionsresize").button().click(function() { 215 | var resizable = $(this).attr("checked") ? false : true; 216 | tracks.resizable(resizable); 217 | 218 | if (resizable) 219 | { 220 | eventlog("disableresize", "Objects can be resized"); 221 | } 222 | else 223 | { 224 | eventlog("disableresize", "Objects can not be resized"); 225 | } 226 | }); 227 | 228 | $("#annotateoptionshideboxes").button().click(function() { 229 | var visible = !$(this).attr("checked"); 230 | tracks.visible(visible); 231 | 232 | if (visible) 233 | { 234 | eventlog("hideboxes", "Boxes are visible"); 235 | } 236 | else 237 | { 238 | eventlog("hideboxes", "Boxes are invisible"); 239 | } 240 | }); 241 | 242 | $("#annotateoptionshideboxtext").button().click(function() { 243 | var visible = !$(this).attr("checked"); 244 | 245 | if (visible) 246 | { 247 | $(".boundingboxtext").show(); 248 | } 249 | else 250 | { 251 | $(".boundingboxtext").hide(); 252 | } 253 | }); 254 | } 255 | 256 | function ui_setupkeyboardshortcuts(job, player) 257 | { 258 | $(window).keypress(function(e) { 259 | console.log("Key press: " + e.keyCode); 260 | 261 | if (ui_disabled) 262 | { 263 | console.log("Key press ignored because UI is disabled."); 264 | return; 265 | } 266 | 267 | var keycode = e.keyCode ? e.keyCode : e.which; 268 | eventlog("keyboard", "Key press: " + keycode); 269 | 270 | if (keycode == 32 || keycode == 112 || keycode == 116 || keycode == 98) 271 | { 272 | $("#playbutton").click(); 273 | } 274 | if (keycode == 114) 275 | { 276 | $("#rewindbutton").click(); 277 | } 278 | else if (keycode == 110) 279 | { 280 | $("#newobjectbutton").click(); 281 | } 282 | else if (keycode == 104) 283 | { 284 | $("#annotateoptionshideboxes").click(); 285 | } 286 | else 287 | { 288 | var skip = 0; 289 | if (keycode == 44 || keycode == 100) 290 | { 291 | skip = job.skip > 0 ? -job.skip : -10; 292 | } 293 | else if (keycode == 46 || keycode == 102) 294 | { 295 | skip = job.skip > 0 ? job.skip : 10; 296 | } 297 | else if (keycode == 62 || keycode == 118) 298 | { 299 | skip = job.skip > 0 ? job.skip : 1; 300 | } 301 | else if (keycode == 60 || keycode == 99) 302 | { 303 | skip = job.skip > 0 ? -job.skip : -1; 304 | } 305 | 306 | if (skip != 0) 307 | { 308 | player.pause(); 309 | player.displace(skip); 310 | 311 | ui_snaptokeyframe(job, player); 312 | } 313 | } 314 | }); 315 | 316 | } 317 | 318 | function ui_canresize() 319 | { 320 | return !$("#annotateoptionsresize").attr("checked"); 321 | } 322 | 323 | function ui_areboxeshidden() 324 | { 325 | return $("#annotateoptionshideboxes").attr("checked"); 326 | } 327 | 328 | function ui_setupslider(player) 329 | { 330 | var slider = $("#playerslider"); 331 | slider.slider({ 332 | range: "min", 333 | value: player.job.start, 334 | min: player.job.start, 335 | max: player.job.stop, 336 | slide: function(event, ui) { 337 | player.pause(); 338 | player.seek(ui.value); 339 | // probably too much bandwidth 340 | //eventlog("slider", "Seek to " + ui.value); 341 | } 342 | }); 343 | 344 | /*slider.children(".ui-slider-handle").hide();*/ 345 | slider.children(".ui-slider-range").css({ 346 | "background-color": "#868686", 347 | "background-image": "none"}); 348 | 349 | slider.css({ 350 | marginTop: "6px", 351 | width: parseInt(slider.parent().css("width")) - 200 + "px", 352 | float: "right" 353 | }); 354 | 355 | player.onupdate.push(function() { 356 | slider.slider({value: player.frame}); 357 | }); 358 | } 359 | 360 | function ui_iskeyframe(frame, job) 361 | { 362 | return frame == job.stop || (frame - job.start) % job.skip == 0; 363 | } 364 | 365 | function ui_snaptokeyframe(job, player) 366 | { 367 | if (job.skip > 0 && !ui_iskeyframe(player.frame, job)) 368 | { 369 | console.log("Fixing slider to key frame"); 370 | var remainder = (player.frame - job.start) % job.skip; 371 | if (remainder > job.skip / 2) 372 | { 373 | player.seek(player.frame + (job.skip - remainder)); 374 | } 375 | else 376 | { 377 | player.seek(player.frame - remainder); 378 | } 379 | } 380 | } 381 | 382 | function ui_setupclickskip(job, player, tracks, objectui) 383 | { 384 | if (job.skip <= 0) 385 | { 386 | return; 387 | } 388 | 389 | player.onupdate.push(function() { 390 | if (ui_iskeyframe(player.frame, job)) 391 | { 392 | console.log("Key frame hit"); 393 | player.pause(); 394 | $("#newobjectbutton").button("option", "disabled", false); 395 | $("#playbutton").button("option", "disabled", false); 396 | tracks.draggable(true); 397 | tracks.resizable(ui_canresize()); 398 | tracks.recordposition(); 399 | objectui.enable(); 400 | } 401 | else 402 | { 403 | $("#newobjectbutton").button("option", "disabled", true); 404 | $("#playbutton").button("option", "disabled", true); 405 | tracks.draggable(false); 406 | tracks.resizable(false); 407 | objectui.disable(); 408 | } 409 | }); 410 | 411 | $("#playerslider").bind("slidestop", function() { 412 | ui_snaptokeyframe(job, player); 413 | }); 414 | } 415 | 416 | function ui_loadprevious(job, objectui) 417 | { 418 | var overlay = $('
').appendTo("#container"); 419 | var note = $("
One moment...
").appendTo("#container"); 420 | 421 | server_request("getboxesforjob", [job.jobid], function(data) { 422 | overlay.remove(); 423 | note.remove(); 424 | 425 | for (var i in data) 426 | { 427 | objectui.injectnewobject(data[i]["label"], 428 | data[i]["boxes"], 429 | data[i]["attributes"]); 430 | } 431 | }); 432 | } 433 | 434 | function ui_setupsubmit(job, tracks) 435 | { 436 | $("#submitbutton").button({ 437 | icons: { 438 | primary: 'ui-icon-check' 439 | } 440 | }).click(function() { 441 | if (ui_disabled) return; 442 | ui_submit(job, tracks); 443 | }); 444 | } 445 | 446 | function ui_submit(job, tracks) 447 | { 448 | console.dir(tracks); 449 | console.log("Start submit - status: " + tracks.serialize()); 450 | 451 | if (!mturk_submitallowed()) 452 | { 453 | alert("Please accept the task before you submit."); 454 | return; 455 | } 456 | 457 | /*if (mturk_isassigned() && !mturk_isoffline()) 458 | { 459 | if (!window.confirm("Are you sure you are ready to submit? Please " + 460 | "make sure that the entire video is labeled and " + 461 | "your annotations are tight.\n\nTo submit, " + 462 | "press OK. Otherwise, press Cancel to keep " + 463 | "working.")) 464 | { 465 | return; 466 | } 467 | }*/ 468 | 469 | var overlay = $('
').appendTo("#container"); 470 | ui_disable(); 471 | 472 | var note = $("
").appendTo("#container"); 473 | 474 | function validatejob(callback) 475 | { 476 | server_post("validatejob", [job.jobid], tracks.serialize(), 477 | function(valid) { 478 | if (valid) 479 | { 480 | console.log("Validation was successful"); 481 | callback(); 482 | } 483 | else 484 | { 485 | note.remove(); 486 | overlay.remove(); 487 | ui_enable(); 488 | console.log("Validation failed!"); 489 | ui_submit_failedvalidation(); 490 | } 491 | }); 492 | } 493 | 494 | function respawnjob(callback) 495 | { 496 | server_request("respawnjob", [job.jobid], function() { 497 | callback(); 498 | }); 499 | } 500 | 501 | function savejob(callback) 502 | { 503 | server_post("savejob", [job.jobid], 504 | tracks.serialize(), function(data) { 505 | callback() 506 | }); 507 | } 508 | 509 | function finishsubmit(redirect) 510 | { 511 | if (mturk_isoffline()) 512 | { 513 | window.setTimeout(function() { 514 | note.remove(); 515 | overlay.remove(); 516 | ui_enable(); 517 | }, 1000); 518 | } 519 | else 520 | { 521 | window.setTimeout(function() { 522 | redirect(); 523 | }, 1000); 524 | } 525 | } 526 | 527 | if (job.training) 528 | { 529 | console.log("Submit redirect to train validate"); 530 | 531 | note.html("Checking..."); 532 | validatejob(function() { 533 | savejob(function() { 534 | mturk_submit(function(redirect) { 535 | respawnjob(function() { 536 | note.html("Good work!"); 537 | finishsubmit(redirect); 538 | }); 539 | }); 540 | }); 541 | }); 542 | } 543 | else 544 | { 545 | note.html("Saving..."); 546 | savejob(function() { 547 | mturk_submit(function(redirect) { 548 | note.html("Saved!"); 549 | finishsubmit(redirect); 550 | }); 551 | }); 552 | } 553 | } 554 | 555 | function ui_submit_failedvalidation() 556 | { 557 | $('
').appendTo("#container"); 558 | var h = $('
') 559 | h.appendTo("#container"); 560 | 561 | h.append("

Low Quality Work

"); 562 | h.append("

Sorry, but your work is low quality. We would normally reject this assignment, but we are giving you the opportunity to correct your mistakes since you are a new user.

"); 563 | 564 | h.append("

Please review the instructions, double check your annotations, and submit again. Remember:

"); 565 | 566 | var str = ""; 570 | 571 | h.append(str); 572 | 573 | h.append("

When you are ready to continue, press the button below.

"); 574 | 575 | $('
Try Again
').appendTo(h).button({ 576 | icons: { 577 | primary: "ui-icon-refresh" 578 | } 579 | }).click(function() { 580 | $("#turkic_overlay").remove(); 581 | h.remove(); 582 | }).wrap("
"); 583 | } 584 | 585 | function ui_showinstructions(job) 586 | { 587 | console.log("Popup instructions"); 588 | 589 | if ($("#instructionsdialog").size() > 0) 590 | { 591 | return; 592 | } 593 | 594 | eventlog("instructions", "Popup instructions"); 595 | 596 | $('
').appendTo("#container"); 597 | var h = $('
').appendTo("#container"); 598 | 599 | $('
Dismiss Instructions
').appendTo(h).button({ 600 | icons: { 601 | primary: "ui-icon-circle-close" 602 | } 603 | }).click(ui_closeinstructions); 604 | 605 | instructions(job, h) 606 | 607 | ui_disable(); 608 | } 609 | 610 | function ui_closeinstructions() 611 | { 612 | console.log("Popdown instructions"); 613 | $("#turkic_overlay").remove(); 614 | $("#instructionsdialog").remove(); 615 | eventlog("instructions", "Popdown instructions"); 616 | 617 | ui_enable(); 618 | } 619 | 620 | function ui_disable() 621 | { 622 | if (ui_disabled++ == 0) 623 | { 624 | $("#newobjectbutton").button("option", "disabled", true); 625 | $("#playbutton").button("option", "disabled", true); 626 | $("#rewindbutton").button("option", "disabled", true); 627 | $("#submitbutton").button("option", "disabled", true); 628 | $("#playerslider").slider("option", "disabled", true); 629 | 630 | console.log("Disengaged UI"); 631 | } 632 | 633 | console.log("UI disabled with count = " + ui_disabled); 634 | } 635 | 636 | function ui_enable() 637 | { 638 | if (--ui_disabled == 0) 639 | { 640 | $("#newobjectbutton").button("option", "disabled", false); 641 | $("#playbutton").button("option", "disabled", false); 642 | $("#rewindbutton").button("option", "disabled", false); 643 | $("#submitbutton").button("option", "disabled", false); 644 | $("#playerslider").slider("option", "disabled", false); 645 | 646 | console.log("Engaged UI"); 647 | } 648 | 649 | ui_disabled = Math.max(0, ui_disabled); 650 | 651 | console.log("UI disabled with count = " + ui_disabled); 652 | } 653 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Please note: the community is working on a modernized version in the [contrib](https://github.com/cvondrick/vatic/tree/contrib) branch. Please check it out and commit any pull requests there. Thank you! - December 3, 2016 2 | 3 | # VATIC - Video Annotation Tool from Irvine, California 4 | 5 | 6 | 7 | VATIC is an online video annotation tool for computer vision research that 8 | crowdsources work to Amazon's Mechanical Turk. Our tool makes it easy to build 9 | massive, affordable video data sets. 10 | 11 | 12 | 13 | # INSTALLATION 14 | 15 | Note: VATIC has only been tested on Ubuntu with Apache 2.2 HTTP server and a 16 | MySQL server. This document will describe installation on this platform, 17 | however it should work any operating system and with any server. 18 | 19 | ## Download 20 | 21 | You can download and extract VATIC from our website. Note: do NOT run the 22 | installer as root. 23 | 24 | $ wget http://mit.edu/vondrick/vatic/vatic-install.sh 25 | $ chmod +x vatic-install.sh 26 | $ ./vatic-install.sh 27 | $ cd vatic 28 | 29 | ## HTTP Server Configuration 30 | 31 | Open the Apache configuration file. On Ubuntu, this file is located at: 32 | 33 | /etc/apache2/sites-enabled/000-default 34 | 35 | If you do not use Apache on this computer for any other purpose, replace the 36 | contents of the file with: 37 | 38 | WSGIDaemonProcess www-data 39 | WSGIProcessGroup www-data 40 | 41 | 42 | ServerName vatic.domain.edu 43 | DocumentRoot /path/to/vatic/public 44 | 45 | WSGIScriptAlias /server /path/to/vatic/server.py 46 | CustomLog /var/log/apache2/access.log combined 47 | 48 | 49 | updating ServerName with your domain name, DocumentRoot with the path to 50 | the public directory in VATIC, and WSGIScriptAlias to VATIC's server.py file. 51 | 52 | If you do use Apache for other purposes, you will have to setup a new virtual 53 | host with the correct document root and script alias, as shown above. 54 | 55 | Make sure you have the mod_headers module enabled: 56 | 57 | $ sudo cp /etc/apache2/mods-available/headers.load /etc/apache2/mods-enabled 58 | 59 | After making these changes, restart Apache: 60 | 61 | $ sudo apache2ctl graceful 62 | 63 | ## SQL Server Configuration 64 | 65 | We recommend creating a separate database specifically for VATIC: 66 | 67 | $ mysql -u root 68 | mysql> create database vatic; 69 | 70 | The next section will automatically create the necessary tables. 71 | 72 | ## Setup 73 | 74 | Inside the vatic directory, copy config.py-example to config.py: 75 | 76 | $ cp config.py-example config.py 77 | 78 | Then open config.py and make changes to the following variables in order to 79 | configure VATIC: 80 | 81 | signature Amazon Mechanical Turk AWS signature (secret access key) 82 | accesskey Amazon Mechanical Turk AWS access key (access key ID) 83 | sandbox If true, put into Mturk sandbox mode. For debugging. 84 | localhost The local HTTP address: http://vatic.domain.edu/ so it 85 | matches the ServerName in Apache. 86 | database Database connection string: for example, 87 | mysql://user:pass@localhost/vatic 88 | geolocation API key from ipinfodb.com for geolocation services 89 | 90 | If you do not plan on using VATIC on Mechcanical Turk (offlien mode only), you 91 | can leave the signature and accesskey empty. 92 | 93 | After saving results, you can then initialize the database: 94 | 95 | $ turkic setup --database 96 | 97 | Note: if you want to reset the database, you can do this with: 98 | 99 | $ turkic setup --database --reset 100 | 101 | which will require confirmation to reset in order to prevent data loss. 102 | 103 | Finally, you must also allow VATIC to access turkic, a major dependency: 104 | 105 | $ turkic setup --public-symlink 106 | 107 | # ANNOTATION 108 | 109 | Before you continue, you should verify that the installation was correct. You 110 | can verify this with: 111 | 112 | $ turkic status --verify 113 | 114 | If you receive any error messages, it means the installation was not complete 115 | and you should review the previous section. Note: If you do not plan on 116 | using Mechanical Turk, you can safely ignore any errors caused by Mechanical 117 | Turk. 118 | 119 | ## Frame Extraction 120 | 121 | Our system requires that videos are extracted into JPEG frames. Our tool can 122 | do this automatically for you: 123 | 124 | $ mkdir /path/to/output/directory 125 | $ turkic extract /path/to/video.mp4 /path/to/output/directory 126 | 127 | By default, our tool will resize the frames to fit within a 720x480 rectangle. 128 | We believe this resolution is ideal for online video viewing. You can change 129 | resolution with options: 130 | 131 | $ turkic extract /path/to/video.mp4 /path/to/output/directory 132 | --width 1000 --height 1000 133 | 134 | or 135 | 136 | $ turkic extract /path/to/video.mp4 /path/to/output/directory 137 | --no-resize 138 | 139 | The tool will maintain aspect ratio in all cases. 140 | 141 | Alternatively, if you have already extracted frames, you can use the 142 | formatframes command to format the video into a format that VATIC understands: 143 | 144 | $ turkic formatframes /path/to/frames/ /path/to/output/directory 145 | 146 | The above command will read all the images in /path/to/frames and create 147 | hard links (soft copy) in /path/to/output/directory. 148 | 149 | ## Importing a Video 150 | 151 | After extracting frames, the video can be imported into our tool for 152 | annotation. The general syntax for this operation is: 153 | 154 | $ turkic load identifier /path/to/output/directory Label1 Label2 LabelN 155 | 156 | where identifier is a unique string that you will use to refer to this video, 157 | /path/to/output/directory is the directory of frames, and LabelX are class 158 | labels that you want annotated (e.g., Person, Car, Bicycle). You can have as 159 | many class labels as you wish, but you must have at least one. 160 | 161 | When a video is imported, it is broken into small segments typically of only a 162 | few seconds. When all the segments are annotated, the annotations are merged 163 | across segments because each segment overlaps another by a small margin. 164 | 165 | The above command specifies all of the required options, but there are many 166 | options available as well. We recommend using these options. 167 | 168 | MTurk Options 169 | --title The title that MTurk workers see 170 | --description The description that MTurk workers see 171 | --duration Time in seconds that a worker has to complete the task 172 | --lifetime Time in seconds that the task is online 173 | --keywords Keywords that MTurk workers can search on 174 | --offline Disable MTurk and use for self annotation only 175 | 176 | Compensation Options 177 | --cost The price advertised to MTurk workers 178 | --per-object-bonus A bonus in dollars paid for each object 179 | --completion-bonus A bonus in dollars paid for completing the task 180 | 181 | Qualification Options 182 | --min-approved-percent Minimum percent of tasks the worker must have 183 | approved before they can work for you 184 | --min-approved-amount Minimum number of tasks that the worker must 185 | have completed before they can work for you 186 | 187 | Video Options 188 | --length The length of each segment for this video in frames 189 | --overlap The overlap between segments in frames 190 | --use-frames When splitting into segments, only the frame intervals 191 | specified in this file. Each line should contain a 192 | start frame, followed by a space, then the stop frame. 193 | Frames outside the intervals in this file will be 194 | ignored. 195 | --skip If specified, request annotations only every N frames. 196 | --blow-radius When a user marks an annotation, blow away all other 197 | annotations within this many frames. If you want to 198 | allow the user to make fine-grained annotations, set 199 | this number to a small integer, or 0 to disable. By 200 | default, this is 5, which we recommend. 201 | 202 | You can also specify temporal attributes that each object label can take on. 203 | For example, you may have a person object with attributes "walking", "running", 204 | or "sitting". You can specify attributes the same way as labels, except you 205 | prepend an ~ before the text, which bind the attribute to the previous label: 206 | 207 | $ turkic load identifier /path/to/output/directory Label1 ~Attr1A ~Attr1B 208 | Label2 ~Attr2A ~Attr2B ~Attr2C Label3 209 | 210 | In the above example, Label1 will have attributes Attr1A and Attr1B, Label2 211 | will have attributes Attr2B, Attr2B, and Attr2C and Label3 will have no 212 | attributes. Specifying attributes is optional. 213 | 214 | ## Gold Standard Training 215 | 216 | It turns out that video annotation is extremely challenging and most MTurk 217 | workers lack the necessary patience. For this reason, we recommend requiring 218 | workers to pass a "gold standard" video. When a new worker visits the task, 219 | they will be redirected to a video for which the annotations are already known. 220 | In order to move on to the true annotations, the worker must correctly annotate 221 | the gold standard video first. We have found that this approach significantly 222 | improves the quality of the annotations. 223 | 224 | To use this feature, import a video to be used as the gold standard: 225 | 226 | $ turkic load identifier-train /path/to/frames Label1 Label2 LabelN 227 | --for-training --for-training-start 0 --for-training-stop 500 228 | --for-training-overlap 0.5 --for-training-tolerance 0.1 229 | --for-training-mistakes 1 230 | 231 | You can also use any of the options described above. Explanations for the new 232 | options are as follows: 233 | 234 | --for-training Specifies that this video is gold standard 235 | --for-training-start Specifies the first frame to use 236 | --for-training-stop Specifies the last frame to use 237 | --for-training-overlap Percent overlap that worker's boxes must match 238 | --for-training-tolerance Percent that annotations must agree temporally 239 | --for-training-mistakes The number of completely wrong annotations 240 | allowed. We recommend setting this to a small, 241 | nonzero integer. 242 | 243 | After running the above command, it will provide you with an URL for you to 244 | input the ground truth annotation. You must make this ground truth annotation 245 | as careful as possible, as it will be used to evaluate future workers. 246 | 247 | You can now specify that a video should use a gold standard video: 248 | 249 | $ turkic load identifier /path/to/output/directory Label1 Label2 LabelN 250 | --train-with identifier-train 251 | 252 | When a not-yet-seen worker visits this video, they will now be redirected to 253 | to the training video and be required to pass the evaluation test first. 254 | 255 | ## Publishing Tasks 256 | 257 | When you are ready for the MTurk workers to annotate, you must publish the 258 | tasks, which will allow workers to start annotating: 259 | 260 | $ turkic publish 261 | 262 | You can limit the number of tasks that are published: 263 | 264 | $ turkic publish --limit 100 265 | 266 | Running above command repeatedly will launch tasks in batches of 100. You can 267 | also disable all pending tasks: 268 | 269 | $ turkic publish --disable 270 | 271 | which will "unpublish" tasks that have not yet been completed. 272 | 273 | If you have videos that are offline only, you can see their access URLs with 274 | the command: 275 | 276 | $ turkic publish --offline 277 | 278 | Note: for the above command to work, you must have loaded the video with the 279 | --offline parameter as well: 280 | 281 | $ turkic load identifier /path/to/frames Person --offline 282 | 283 | ## Checking the Status 284 | 285 | You can check the status of the video annotation server with the command: 286 | 287 | $ turkic status 288 | 289 | This will list various statistics about the server, such as number of jobs 290 | published and how many are completed. You can get even more statistics by 291 | requesting additional information from Amazon: 292 | 293 | $ turkic status --turk 294 | 295 | which will output how much money is left in your account, among other 296 | statistics. 297 | 298 | When all the videos are annotated, the last line will read: 299 | 300 | Server is offline. 301 | 302 | ## Retrieving Annotations 303 | 304 | You can get all the annotations for a video with the command: 305 | 306 | $ turkic dump identifier -o output.txt 307 | 308 | which will write the file "output.txt" where each line contains one 309 | annotation. Each line contains 10+ columns, separated by spaces. The 310 | definition of these columns are: 311 | 312 | 1 Track ID. All rows with the same ID belong to the same path. 313 | 2 xmin. The top left x-coordinate of the bounding box. 314 | 3 ymin. The top left y-coordinate of the bounding box. 315 | 4 xmax. The bottom right x-coordinate of the bounding box. 316 | 5 ymax. The bottom right y-coordinate of the bounding box. 317 | 6 frame. The frame that this annotation represents. 318 | 7 lost. If 1, the annotation is outside of the view screen. 319 | 8 occluded. If 1, the annotation is occluded. 320 | 9 generated. If 1, the annotation was automatically interpolated. 321 | 10 label. The label for this annotation, enclosed in quotation marks. 322 | 11+ attributes. Each column after this is an attribute. 323 | 324 | By default, the above command will not attempt to merge annotations across 325 | shot segments. You can request merging with the command: 326 | 327 | $ turkic dump identifier -o output.txt --merge --merge-threshold 0.5 328 | 329 | The --merge-threshold option is optional, but it is a number between 0 and 1 330 | that represents much the paths must agree in order to merge. 1 specifies a 331 | perfect match and 0 specifies no match. In practice, 0.5 is sufficient. Merging 332 | is done using the Hungarian algorithm. 333 | 334 | You can also scale annotations by a factor, which is useful for when the 335 | videos have been downsampled: 336 | 337 | $ turkic dump identifier -o output.txt -s 2.8 338 | 339 | or force it to fit within a max dimension: 340 | 341 | $ turkic dump identifier -o output.txt --dimensions 400x200 342 | 343 | or force it to fit within the dimensions of the original video: 344 | 345 | $ turkic dump identifier -o output.txt --original-video /path/to/video.mp4 346 | 347 | The command can also output to many different formats. Available formats are: 348 | 349 | --xml Use XML 350 | --json Use JSON 351 | --matlab Use MATLAB 352 | --pickle Use Python's Pickle 353 | --labelme Use LabelMe video's XML format 354 | --pascal Use PASCAL VOC format, treating each frame as an image 355 | 356 | The specifications for these formats should be self explanatory. 357 | 358 | ## Visualizing Videos 359 | 360 | You can preview the annotations by visualizing the results: 361 | 362 | $ turkic visualize identifier /tmp --merge 363 | 364 | which will output frames to /tmp with the bounding boxes with the file name 365 | as the frame number. The visualization will contain some meta information 366 | that can help you identify bad workers. You can remove this meta information 367 | with the option: 368 | 369 | $ turkic visualize identifer /tmp --merge --no-augment 370 | 371 | If you want to make a video of the visualization (e.g., with ffmpeg), it is 372 | useful to renumber the frames so that they start counting at 0 and do not 373 | have any gaps: 374 | 375 | $ turkic visualize identifier /tmp --merge --renumber 376 | 377 | If you wish to display the class label and their attributes next to the box, 378 | specify the --labels option: 379 | 380 | $ turkic visualize identifier /tmp --labels 381 | 382 | ## Compensating Workers 383 | 384 | When you are ready, you can compensate workers: 385 | 386 | $ turkic compensate --default accept 387 | 388 | which will pay all workers for all outstanding tasks. We strongly recommend 389 | paying all workers regardless of their quality. You should attempt to pay 390 | workers at least once per day. 391 | 392 | ## Finding Jobs 393 | 394 | If you have found a small mistake in a video and want to make 395 | the correction yourself, you can start an annotation session initialized with 396 | the MTurk workers annotations: 397 | 398 | $ turkic find --id identifier 399 | $ turkic find --id identifier --frame frame 400 | 401 | where identifier is the identifier for the video and frame is the frame number 402 | that the error occurs. In most cases, this command will return one URL for you 403 | to make the corrections. If it outputs two URLs, it means the frame number 404 | occurs in two overlapping segments, and so you may have to make changes to both 405 | of the segments. You can also omit the frame argument, in which case it will 406 | output all URLs for that video. 407 | 408 | If you want to find the HIT id, assignment ID, or worker ID for a particular 409 | video, specify the --ids parameter to the vet command: 410 | 411 | $ turkic find --id identifer --ids 412 | $ turkic find --id identifer --frame frame --ids 413 | 414 | will print a list of all the IDs for the video. If the corresponding segment 415 | has been published and completed, it will list three strings: the HIT ID, 416 | assignment ID, and the worker ID. If the job has been published but not 417 | finished, it will just list the HIT ID. If the job has not yet been published, 418 | it prints "(not published)". 419 | 420 | Additionally, if you want to find the job that corresponds to a particular 421 | HIT ID, you can use the find command: 422 | 423 | $ turkic find --hitid HITID 424 | 425 | ## Quality Control 426 | 427 | The gold standard does a "pretty good" job of weeding out bad workers. 428 | Nonetheless, there will always be bad workers that we must identify and 429 | invalidate. Our tool provides a method to sample the annotations provided by 430 | workers, which you can then manually verify for correctness: 431 | 432 | $ turkic sample /tmp 433 | 434 | which by default will pick 3 random videos that the worker has completed, and 435 | pick 4 random frames from each of those videos, and write visualiations to a 436 | file in /tmp. You can tweak the number of videos and the number of frames with 437 | the options: 438 | 439 | $ turkic sample /tmp --number 3 --frames 4 440 | 441 | Moreover, you can only look at work from a certain date: 442 | 443 | $ turkic sample /tmp --since "yesterday" 444 | 445 | The filename will follow the format of WORKERID-JOBID.jpg. Once you have 446 | identified a mallicious worker, you can block them, invalidate ALL of their 447 | work, and respawn their jobs with the command: 448 | 449 | $ turkic invalidate workerid 450 | 451 | The options are also available: 452 | 453 | --no-block invalidate and respawn, but don't block 454 | --no-publish block and invalidate, but don't respawn 455 | 456 | You can also invalidate and respawn individual jobs with the command: 457 | 458 | $ turkic invalidate --hit hitid 459 | 460 | ## Listing all Videos 461 | 462 | You can retrieve a list of all videos in the system with: 463 | 464 | $ turkic list 465 | 466 | If you want just the videos that have been published: 467 | 468 | $ turkic list --published 469 | 470 | If you want just the videos that have been worked on: 471 | 472 | $ turkic list --completed 473 | 474 | If you instead want the videos that are used for gold standard: 475 | 476 | $ turkic list --training 477 | 478 | Finally, if you just want to count how many videos are in the system, use the 479 | --count option, in combination with any of the above: 480 | 481 | $ turkic list --count 482 | $ turkic list --published --count 483 | 484 | If you want statistics about each video, then give the --stats option: 485 | 486 | $ turkic list --stats 487 | 488 | ## Managing Workers 489 | 490 | You can list all known workers with the command: 491 | 492 | $ turkic workers 493 | 494 | which will dump every worker with the number of jobs they have completed. You 495 | can also use this command to block and unblock workers: 496 | 497 | $ turkic workers --block workerid 498 | $ turkic workers --unblock workerid 499 | 500 | You can also search for workers by the first few letters of their ID: 501 | 502 | $ turkic workers --search A3M 503 | 504 | ## Deleting a Video 505 | 506 | You can delete a video at any time with: 507 | 508 | $ turkic delete identifier 509 | 510 | If the video has already been annotated (even partially), this command will 511 | warn you and abort. You can force deletion with: 512 | 513 | $ turkic delete identifier --force 514 | 515 | which will REMOVE ALL DATA AND CANNOT BE UNDONE. 516 | 517 | # REFERENCES 518 | 519 | When using our system, please cite: 520 | 521 | Carl Vondrick, Donald Patterson, Deva Ramanan. "Efficiently Scaling Up 522 | Crowdsourced Video Annotation" International Journal of Computer Vision 523 | (IJCV). June 2012. 524 | 525 | # FEEDBACK AND BUGS 526 | 527 | Please direct all comments and report all bugs to: 528 | 529 | Carl Vondrick 530 | vondrick@mit.edu 531 | 532 | Thanks for using our system! 533 | -------------------------------------------------------------------------------- /public/jquery-ui/css/smoothness/jquery-ui-1.10.4.custom.min.css: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.10.4 - 2014-02-10 2 | * http://jqueryui.com 3 | * Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css 4 | * To view and modify this theme, visit http://jqueryui.com/themeroller/ 5 | * Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ 6 | 7 | .ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;min-height:0}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{overflow:hidden;position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;width:100%;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;min-height:0;font-weight:normal}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px}.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("images/animated-overlay.gif");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #aaa;background:#ccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x;color:#222;font-weight:bold}.ui-widget-header a{color:#222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#555}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999;background:#dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#212121;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_222222_256x240.png)}.ui-state-default .ui-icon{background-image:url(images/ui-icons_888888_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-active .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_2e83ff_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_cd0a0a_256x240.png)}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);border-radius:8px} -------------------------------------------------------------------------------- /public/objectui.js: -------------------------------------------------------------------------------- 1 | function TrackObjectUI(button, container, videoframe, job, player, tracks) 2 | { 3 | var me = this; 4 | 5 | this.button = button; 6 | this.container = container; 7 | this.videoframe = videoframe; 8 | this.job = job; 9 | this.player = player; 10 | this.tracks = tracks; 11 | 12 | this.drawer = new BoxDrawer(videoframe); 13 | 14 | this.counter = 0; 15 | 16 | this.currentobject = null; 17 | this.currentcolor = null; 18 | 19 | this.objects = []; 20 | 21 | this.startnewobject = function() 22 | { 23 | if (this.button.button("option", "disabled")) 24 | { 25 | return; 26 | } 27 | 28 | tracks.drawingnew(true); 29 | 30 | console.log("Starting new track object"); 31 | 32 | eventlog("newobject", "Start drawing new object"); 33 | 34 | //this.instructions.fadeOut(); 35 | 36 | this.currentcolor = this.pickcolor(); 37 | this.drawer.color = this.currentcolor[0]; 38 | this.drawer.enable(); 39 | 40 | this.button.button("option", "disabled", true); 41 | 42 | this.currentobject = new TrackObject(this.job, this.player, 43 | this.container, 44 | this.currentcolor); 45 | this.currentobject.statedraw(); 46 | 47 | this.tracks.resizable(false); 48 | this.tracks.draggable(false); 49 | } 50 | 51 | this.stopdrawing = function(position) 52 | { 53 | console.log("Received new track object drawing"); 54 | 55 | var track = tracks.add(player.frame, position, this.currentcolor[0]); 56 | 57 | this.drawer.disable(); 58 | ui_disable(); 59 | 60 | this.currentobject.onready.push(function() { 61 | me.stopnewobject(); 62 | }); 63 | 64 | this.currentobject.initialize(this.counter, track, this.tracks); 65 | this.currentobject.stateclassify(); 66 | } 67 | 68 | this.stopnewobject = function() 69 | { 70 | console.log("Finished new track object"); 71 | 72 | ui_enable(); 73 | tracks.drawingnew(false); 74 | 75 | this.objects.push(this.currentobject); 76 | 77 | this.tracks.draggable(true); 78 | if ($("#annotateoptionsresize:checked").size() == 0) 79 | { 80 | this.tracks.resizable(true); 81 | } 82 | else 83 | { 84 | this.tracks.resizable(false); 85 | } 86 | 87 | this.tracks.dim(false); 88 | this.currentobject.track.highlight(false); 89 | 90 | this.button.button("option", "disabled", false); 91 | 92 | this.counter++; 93 | } 94 | 95 | this.injectnewobject = function(label, path, attributes) 96 | { 97 | console.log("Injecting existing object"); 98 | 99 | //this.instructions.fadeOut(); 100 | 101 | this.currentcolor = this.pickcolor(); 102 | var obj = new TrackObject(this.job, this.player, 103 | container, this.currentcolor); 104 | 105 | function convert(box) 106 | { 107 | return new Position(box[0], box[1], box[2], box[3], 108 | box[6], box[5]); 109 | } 110 | 111 | var track = tracks.add(path[0][4], convert(path[0]), 112 | this.currentcolor[0]); 113 | for (var i = 1; i < path.length; i++) 114 | { 115 | track.journal.mark(path[i][4], convert(path[i])); 116 | } 117 | 118 | obj.initialize(this.counter, track, this.tracks); 119 | obj.finalize(label); 120 | 121 | for (var i = 0; i < attributes.length; i++) 122 | { 123 | track.attributejournals[attributes[i][0]].mark(attributes[i][1], attributes[i][2]); 124 | console.log("Injecting attribute " + attributes[i][0] + " at frame " + attributes[i][1] + " to " + attributes[i][2]); 125 | } 126 | 127 | obj.statefolddown(); 128 | obj.updatecheckboxes(); 129 | obj.updateboxtext(); 130 | this.counter++; 131 | 132 | return obj; 133 | } 134 | 135 | this.setup = function() 136 | { 137 | this.button.button({ 138 | icons: { 139 | primary: "ui-icon-plusthick", 140 | }, 141 | disabled: false 142 | }).click(function() { 143 | me.startnewobject(); 144 | }); 145 | 146 | this.drawer.onstopdraw.push(function(position) { 147 | me.stopdrawing(position); 148 | }); 149 | 150 | var html = "

In this video, please track all of these objects:

"; 151 | html += ""; 157 | 158 | this.instructions = $(html).appendTo(this.container); 159 | } 160 | 161 | this.disable = function() 162 | { 163 | for (var i in this.objects) 164 | { 165 | this.objects[i].disable(); 166 | } 167 | } 168 | 169 | this.enable = function() 170 | { 171 | for (var i in this.objects) 172 | { 173 | this.objects[i].enable(); 174 | } 175 | } 176 | 177 | this.setup(); 178 | 179 | this.availcolors = [["#FF00FF", "#FFBFFF", "#FFA6FF"], 180 | ["#FF0000", "#FFBFBF", "#FFA6A6"], 181 | ["#FF8000", "#FFDCBF", "#FFCEA6"], 182 | ["#FFD100", "#FFEEA2", "#FFEA8A"], 183 | ["#008000", "#8FBF8F", "#7CBF7C"], 184 | ["#0080FF", "#BFDFFF", "#A6D2FF"], 185 | ["#0000FF", "#BFBFFF", "#A6A6FF"], 186 | ["#000080", "#8F8FBF", "#7C7CBF"], 187 | ["#800080", "#BF8FBF", "#BF7CBF"]]; 188 | 189 | this.pickcolor = function() 190 | { 191 | return this.availcolors[this.availcolors.push(this.availcolors.shift()) - 1]; 192 | } 193 | } 194 | 195 | function TrackObject(job, player, container, color) 196 | { 197 | var me = this; 198 | 199 | this.job = job; 200 | this.player = player; 201 | this.container = container; 202 | this.color = color; 203 | 204 | this.id = null; 205 | this.track = null; 206 | this.tracks = null; 207 | this.label = null; 208 | 209 | this.onready = []; 210 | this.onfolddown = []; 211 | this.onfoldup = []; 212 | 213 | this.handle = $("
"); 214 | this.handle.prependTo(container); 215 | this.handle.css({ 216 | 'background-color': color[2], 217 | 'border-color': color[2]}); 218 | this.handle.mouseover(function() { 219 | me.mouseover(); 220 | }); 221 | this.handle.mouseout(function() { 222 | me.mouseout(); 223 | }); 224 | 225 | this.header = null; 226 | this.headerdetails = null; 227 | this.details = null; 228 | this.drawinst = null; 229 | this.classifyinst = null; 230 | this.opencloseicon = null; 231 | 232 | this.ready = false; 233 | this.foldedup = false; 234 | 235 | this.tooltip = null; 236 | this.tooltiptimer = null; 237 | 238 | this.initialize = function(id, track, tracks) 239 | { 240 | this.id = id; 241 | this.track = track; 242 | this.tracks = tracks; 243 | 244 | this.track.onmouseover.push(function() { 245 | me.mouseover(); 246 | }); 247 | 248 | this.track.onmouseout.push(function() { 249 | me.mouseout(); 250 | me.hidetooltip(); 251 | }); 252 | 253 | this.track.onstartupdate.push(function() { 254 | me.hidetooltip(); 255 | }); 256 | 257 | this.player.onupdate.push(function() { 258 | me.hidetooltip(); 259 | }); 260 | 261 | this.track.oninteract.push(function() { 262 | var pos = me.handle.position().top + me.container.scrollTop() - 30; 263 | pos = pos - me.handle.height(); 264 | me.container.stop().animate({scrollTop: pos}, 750); 265 | 266 | me.toggletooltip(); 267 | }); 268 | 269 | this.track.onupdate.push(function() { 270 | me.hidetooltip(); 271 | eventlog("interact", "Interact with box " + me.id); 272 | }); 273 | 274 | this.track.notifyupdate(); 275 | eventlog("newobject", "Finished drawing new object"); 276 | } 277 | 278 | this.remove = function() 279 | { 280 | this.handle.slideUp(null, function() { 281 | me.handle.remove(); 282 | }); 283 | this.track.remove(); 284 | } 285 | 286 | this.statedraw = function() 287 | { 288 | var html = "

Draw a box around one of these objects:

"; 289 | 290 | html += "
    "; 291 | for (var i in this.job.labels) 292 | { 293 | html += "
  • " + this.job.labels[i] + "
  • "; 294 | } 295 | html += "
"; 296 | html += "

Do not annotate the same object twice.

"; 297 | 298 | this.drawinst = $("
" + html + "
").appendTo(this.handle); 299 | this.drawinst.hide().slideDown(); 300 | 301 | this.container.stop().animate({scrollTop: 0}, 750); 302 | 303 | } 304 | 305 | this.stateclassify = function() 306 | { 307 | this.drawinst.slideUp(null, function() { 308 | me.drawinst.remove(); 309 | }); 310 | 311 | var length = 0; 312 | var firsti = 0; 313 | for (var i in this.job.labels) 314 | { 315 | length++; 316 | firsti = i; 317 | } 318 | 319 | if (length == 1) 320 | { 321 | this.finalize(firsti); 322 | this.statefolddown(); 323 | } 324 | else 325 | { 326 | var html = "

What type of object did you just annotate?

"; 327 | for (var i in job.labels) 328 | { 329 | var id = "classification" + this.id + "_" + i; 330 | html += "
"; 331 | } 332 | 333 | this.classifyinst = $("
" + html + "
").appendTo(this.handle); 334 | this.classifyinst.hide().slideDown(); 335 | 336 | $("input[name='classification" + this.id + "']").click(function() { 337 | me.classifyinst.slideUp(null, function() { 338 | me.classifyinst.remove(); 339 | }); 340 | 341 | for (var i in me.job.labels) 342 | { 343 | var id = "classification" + me.id + "_" + i; 344 | if ($("#" + id + ":checked").size() > 0) 345 | { 346 | me.finalize(i); 347 | me.statefolddown(); 348 | break; 349 | } 350 | } 351 | 352 | }); 353 | } 354 | } 355 | 356 | this.finalize = function(labelid) 357 | { 358 | this.label = labelid; 359 | this.track.label = labelid; 360 | 361 | this.headerdetails = $("
").appendTo(this.handle); 362 | this.header = $("

" + this.job.labels[this.label] + " " + (this.id + 1) + "

").appendTo(this.handle).hide().slideDown(); 363 | //this.opencloseicon = $('
').prependTo(this.header); 364 | this.details = $("
").appendTo(this.handle).hide(); 365 | 366 | this.setupdetails(); 367 | 368 | this.updateboxtext(); 369 | 370 | this.track.initattributes(this.job.attributes[this.track.label]); 371 | 372 | this.header.mouseup(function() { 373 | me.click(); 374 | }); 375 | 376 | this.ready = true; 377 | this._callback(this.onready); 378 | 379 | this.player.onupdate.push(function() { 380 | me.updateboxtext(); 381 | }); 382 | } 383 | 384 | this.updateboxtext = function() 385 | { 386 | var str = "" + this.job.labels[this.label] + " " + (this.id + 1) + ""; 387 | 388 | var count = 0; 389 | for (var i in this.job.attributes[this.track.label]) 390 | { 391 | if (this.track.estimateattribute(i, this.player.frame)) 392 | { 393 | str += "
"; 394 | str += this.job.attributes[this.track.label][i]; 395 | count++; 396 | } 397 | } 398 | 399 | this.track.settext(str); 400 | 401 | if ($("#annotateoptionshideboxtext").attr("checked")) 402 | { 403 | $(".boundingboxtext").hide(); 404 | } 405 | } 406 | 407 | this.setupdetails = function() 408 | { 409 | this.details.append("
"); 410 | this.details.append("
"); 411 | 412 | for (var i in this.job.attributes[this.track.label]) 413 | { 414 | this.details.append("
"); 415 | 416 | // create a closure on attributeid 417 | (function(attributeid) { 418 | 419 | $("#trackobject" + me.id + "attribute" + i).click(function() { 420 | me.player.pause(); 421 | 422 | var checked = $(this).attr("checked"); 423 | me.track.setattribute(attributeid, checked ? true : false); 424 | me.track.notifyupdate(); 425 | 426 | me.updateboxtext(); 427 | 428 | if (checked) 429 | { 430 | eventlog("markattribute", "Mark object as " + me.job.attributes[me.track.label][attributeid]); 431 | } 432 | else 433 | { 434 | eventlog("markattribute", "Mark object as not " + me.job.attributes[me.track.label][attributeid]); 435 | } 436 | }); 437 | 438 | })(i); 439 | } 440 | 441 | 442 | $("#trackobject" + this.id + "lost").click(function() { 443 | me.player.pause(); 444 | 445 | var outside = $(this).is(":checked"); 446 | me.track.setoutside(outside); 447 | me.track.notifyupdate(); 448 | 449 | if (outside) 450 | { 451 | eventlog("markoutside", "Mark object outside"); 452 | } 453 | else 454 | { 455 | eventlog("markoutside", "Mark object inside"); 456 | } 457 | }); 458 | $("#trackobject" + this.id + "occluded").click(function() { 459 | me.player.pause(); 460 | 461 | var occlusion = $(this).is(":checked"); 462 | me.track.setocclusion(occlusion); 463 | me.track.notifyupdate(); 464 | 465 | if (occlusion) 466 | { 467 | eventlog("markocclusion", "Mark object as occluded"); 468 | } 469 | else 470 | { 471 | eventlog("markocclusion", "Mark object as not occluded"); 472 | } 473 | }); 474 | 475 | this.player.onupdate.push(function() { 476 | me.updatecheckboxes(); 477 | }); 478 | 479 | //this.details.append("
"); 480 | this.headerdetails.append("
"); 481 | this.headerdetails.append("
"); 482 | this.headerdetails.append("
"); 483 | 484 | $("#trackobject" + this.id + "delete").click(function() { 485 | if (window.confirm("Delete the " + me.job.labels[me.label] + " " + (me.id + 1) + " track? If the object just left the view screen, click the \"Outside of view frame\" check box instead.")) 486 | { 487 | me.remove(); 488 | eventlog("removeobject", "Deleted an object"); 489 | } 490 | }); 491 | 492 | $("#trackobject" + this.id + "lock").click(function() { 493 | if (me.track.locked) 494 | { 495 | me.track.setlock(false); 496 | $(this).addClass("ui-icon-unlocked").removeClass("ui-icon-locked"); 497 | } 498 | else 499 | { 500 | me.track.setlock(true); 501 | $(this).removeClass("ui-icon-unlocked").addClass("ui-icon-locked"); 502 | } 503 | }); 504 | 505 | $("#trackobject" + this.id + "tooltip").click(function() { 506 | me.toggletooltip(false); 507 | }).mouseout(function() { 508 | me.hidetooltip(); 509 | }); 510 | } 511 | 512 | this.updatecheckboxes = function() 513 | { 514 | var e = this.track.estimate(this.player.frame); 515 | $("#trackobject" + this.id + "lost").attr("checked", e.outside); 516 | $("#trackobject" + this.id + "occluded").attr("checked", e.occluded); 517 | 518 | for (var i in this.job.attributes[this.track.label]) 519 | { 520 | if (!this.track.estimateattribute(i, this.player.frame)) 521 | { 522 | $("#trackobject" + this.id + "attribute" + i).attr("checked", false); 523 | } 524 | else 525 | { 526 | $("#trackobject" + this.id + "attribute" + i).attr("checked", true); 527 | } 528 | } 529 | } 530 | 531 | this.toggletooltip = function(onscreen) 532 | { 533 | if (this.tooltip == null) 534 | { 535 | this.showtooltip(onscreen); 536 | } 537 | else 538 | { 539 | this.hidetooltip(); 540 | } 541 | } 542 | 543 | this.showtooltip = function(onscreen) 544 | { 545 | if (this.tooltip != null) 546 | { 547 | return; 548 | } 549 | 550 | var x; 551 | var y; 552 | 553 | if (onscreen || onscreen == null) 554 | { 555 | var pos = this.track.handle.position(); 556 | var width = this.track.handle.width(); 557 | var height = this.track.handle.height(); 558 | 559 | var cpos = this.player.handle.position(); 560 | var cwidth = this.player.handle.width(); 561 | var cheight = this.player.handle.height(); 562 | 563 | var displacement = 15; 564 | 565 | x = pos.left + width + displacement; 566 | if (x + 200 > cpos.left + cwidth) 567 | { 568 | x = pos.left - 200 - displacement; 569 | } 570 | 571 | y = pos.top; 572 | if (y + 200 > cpos.top + cheight) 573 | { 574 | y = cpos.top + cheight - 200 - displacement; 575 | } 576 | } 577 | else 578 | { 579 | var pos = this.handle.position(); 580 | x = pos.left - 210; 581 | 582 | var cpos = this.player.handle.position(); 583 | var cheight = this.player.handle.height(); 584 | 585 | y = pos.top; 586 | if (y + 200 > cpos.top + cheight) 587 | { 588 | y = cpos.top + cheight - 215; 589 | } 590 | } 591 | 592 | var numannotations = 0; 593 | var frames = []; 594 | for (var i in this.track.journal.annotations) 595 | { 596 | if (!me.track.journal.annotations[i].outside) 597 | { 598 | numannotations++; 599 | frames.push(i); 600 | } 601 | } 602 | 603 | if (numannotations == 0) 604 | { 605 | return; 606 | } 607 | 608 | frames.sort(); 609 | 610 | this.tooltip = $("
").appendTo("body"); 611 | this.tooltip.css({ 612 | top: y + "px", 613 | left: x + "px" 614 | }); 615 | this.tooltip.hide(); 616 | var boundingbox = $("
").appendTo(this.tooltip); 617 | 618 | var annotation = 0; 619 | var update = function() { 620 | if (annotation >= numannotations) 621 | { 622 | annotation = 0; 623 | } 624 | 625 | var frame = frames[annotation]; 626 | var anno = me.track.journal.annotations[frame]; 627 | var bw = anno.xbr - anno.xtl; 628 | var bh = anno.ybr - anno.ytl; 629 | 630 | var scale = 1; 631 | if (bw > 200) 632 | { 633 | scale = 200 / bw; 634 | } 635 | if (bh > 200) 636 | { 637 | scale = Math.min(scale, 200 / bh); 638 | } 639 | 640 | var x = (anno.xtl + (anno.xbr - anno.xtl) / 2) * scale - 100; 641 | var y = (anno.ytl + (anno.ybr - anno.ytl) / 2) * scale - 100; 642 | 643 | var bx = 100 - (anno.xbr - anno.xtl) / 2 * scale; 644 | var by = 100 - (anno.ybr - anno.ytl) / 2 * scale; 645 | bw = bw * scale; 646 | bh = bh * scale; 647 | 648 | if (x < 0) 649 | { 650 | bx += x; 651 | x = 0; 652 | } 653 | if (x > me.job.width * scale - 200) 654 | { 655 | bx = 200 - (me.job.width - anno.xtl) * scale; 656 | x = me.job.width * scale - 200; 657 | } 658 | if (y < 0) 659 | { 660 | by += y; 661 | y = 0; 662 | } 663 | if (y > me.job.height * scale - 200) 664 | { 665 | by = 200 - (me.job.height - anno.ytl) * scale; 666 | y = (me.job.height) * scale - 200; 667 | } 668 | 669 | x = -x; 670 | y = -y; 671 | 672 | console.log("Show tooltip for " + frame); 673 | me.tooltip.css("background-image", "url('" + me.job.frameurl(frame) + "')"); 674 | me.tooltip.css("background-position", x + "px " + y + "px"); 675 | var bgsize = (me.job.width * scale) + "px " + (me.job.height * scale) + "px"; 676 | me.tooltip.css("background-size", bgsize); 677 | me.tooltip.css("-o-background-size", bgsize); 678 | me.tooltip.css("-webkit-background-size", bgsize); 679 | me.tooltip.css("-khtml-background-size", bgsize); 680 | me.tooltip.css("-moz-background-size", bgsize); 681 | annotation++; 682 | 683 | boundingbox.css({ 684 | top: by + "px", 685 | left: bx + "px", 686 | width: (bw-4) + "px", 687 | height: (bh-4) + "px", 688 | borderColor: me.color[0] 689 | }); 690 | } 691 | 692 | 693 | this.tooltiptimer = window.setInterval(function() { 694 | update(); 695 | }, 500); 696 | 697 | this.tooltip.hide().slideDown(250); 698 | update(); 699 | } 700 | 701 | this.hidetooltip = function() 702 | { 703 | if (this.tooltip != null) 704 | { 705 | this.tooltip.slideUp(250, function() { 706 | $(this).remove(); 707 | }); 708 | this.tooltip = null; 709 | window.clearInterval(this.tooltiptimer); 710 | this.tooltiptimer = null; 711 | } 712 | } 713 | 714 | this.disable = function() 715 | { 716 | if (this.ready) 717 | { 718 | $("#trackobject" + this.id + "lost").attr("disabled", true); 719 | $("#trackobject" + this.id + "occluded").attr("disabled", true); 720 | } 721 | } 722 | 723 | this.enable = function() 724 | { 725 | if (this.ready) 726 | { 727 | $("#trackobject" + this.id + "lost").attr("disabled", false); 728 | $("#trackobject" + this.id + "occluded").attr("disabled", false); 729 | } 730 | } 731 | 732 | this.statefoldup = function() 733 | { 734 | this.handle.addClass("trackobjectfoldedup"); 735 | this.handle.removeClass("trackobjectfoldeddown"); 736 | this.details.slideUp(); 737 | this.headerdetails.fadeOut(); 738 | this.foldedup = true; 739 | this._callback(this.onfoldup); 740 | 741 | //this.opencloseicon.removeClass("ui-icon-triangle-1-s"); 742 | //this.opencloseicon.addClass("ui-icon-triangle-1-e"); 743 | } 744 | 745 | this.statefolddown = function() 746 | { 747 | this.handle.removeClass("trackobjectfoldedup"); 748 | this.handle.addClass("trackobjectfoldeddown"); 749 | this.details.slideDown(); 750 | this.headerdetails.fadeIn(); 751 | this.foldedup = false; 752 | this._callback(this.onfolddown); 753 | 754 | //this.opencloseicon.removeClass("ui-icon-triangle-1-e"); 755 | //this.opencloseicon.addClass("ui-icon-triangle-1-s"); 756 | } 757 | 758 | this.mouseover = function() 759 | { 760 | this.highlight(); 761 | 762 | if (this.track) 763 | { 764 | this.tracks.dim(true); 765 | this.track.dim(false); 766 | this.track.highlight(true); 767 | } 768 | 769 | if (this.opencloseicon) 770 | { 771 | this.opencloseicon.addClass("ui-icon-triangle-1-se"); 772 | } 773 | } 774 | 775 | this.highlight = function() 776 | { 777 | this.handle.css({ 778 | 'border-color': me.color[0], 779 | 'background-color': me.color[1], 780 | }); 781 | } 782 | 783 | this.mouseout = function() 784 | { 785 | this.unhighlight(); 786 | 787 | if (this.track) 788 | { 789 | this.tracks.dim(false); 790 | this.track.highlight(false); 791 | } 792 | 793 | if (this.opencloseicon) 794 | { 795 | this.opencloseicon.removeClass("ui-icon-triangle-1-se"); 796 | } 797 | } 798 | 799 | this.unhighlight = function() 800 | { 801 | this.handle.css({ 802 | 'border-color': me.color[2], 803 | 'background-color': me.color[2], 804 | }); 805 | } 806 | 807 | this.click = function() 808 | { 809 | return; // disable fold down 810 | if (this.ready) 811 | { 812 | if (this.foldedup) 813 | { 814 | this.statefolddown(); 815 | } 816 | else 817 | { 818 | this.statefoldup(); 819 | } 820 | } 821 | } 822 | 823 | this._callback = function(list) 824 | { 825 | for (var i = 0; i < list.length; i++) 826 | { 827 | list[i](me); 828 | } 829 | } 830 | } 831 | -------------------------------------------------------------------------------- /public/jquery-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jQuery UI Example Page 6 | 7 | 8 | 9 | 111 | 151 | 152 | 153 | 154 |

Welcome to jQuery UI!

155 | 156 |
157 |

This page demonstrates the widgets you downloaded using the theme you selected in the download builder. We've included and linked to minified versions of jQuery, your personalized copy of jQuery UI (js/jquery-ui-.custom.min.js), and css//jquery-ui-.custom.min.css which imports the entire jQuery UI CSS Framework. You can choose to link a subset of the CSS Framework depending on your needs.

158 |

You've downloaded components and a theme that are compatible with jQuery 1.6+. Please make sure you are using jQuery 1.6+ in your production environment.

159 |
160 | 161 |

YOUR COMPONENTS:

162 | 163 | 164 | 165 |

Accordion

166 |
167 |

First

168 |
Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet.
169 |

Second

170 |
Phasellus mattis tincidunt nibh.
171 |

Third

172 |
Nam dui erat, auctor a, dignissim quis.
173 |
174 | 175 | 176 | 177 | 178 |

Autocomplete

179 |
180 | 181 |
182 | 183 | 184 | 185 | 186 |

Button

187 | 188 |
189 |
190 | 191 | 192 | 193 |
194 |
195 | 196 | 197 | 198 | 199 |

Tabs

200 |
201 | 206 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
207 |
Phasellus mattis tincidunt nibh. Cras orci urna, blandit id, pretium vel, aliquet ornare, felis. Maecenas scelerisque sem non nisl. Fusce sed lorem in enim dictum bibendum.
208 |
Nam dui erat, auctor a, dignissim quis, sollicitudin eu, felis. Pellentesque nisi urna, interdum eget, sagittis et, consequat vestibulum, lacus. Mauris porttitor ullamcorper augue.
209 |
210 | 211 | 212 | 213 | 214 |

Dialog

215 |

Open Dialog

216 | 217 |

Overlay and Shadow Classes (not currently used in UI widgets)

218 |
219 |

Lorem ipsum dolor sit amet, Nulla nec tortor. Donec id elit quis purus consectetur consequat.

Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante. Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci.

Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam. Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat.

Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante. Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci. Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam.

Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat. Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi. Aliquam ante.

Suspendisse scelerisque dui nec velit. Duis augue augue, gravida euismod, vulputate ac, facilisis id, sem. Morbi in orci. Nulla purus lacus, pulvinar vel, malesuada ac, mattis nec, quam. Nam molestie scelerisque quam. Nullam feugiat cursus lacus.orem ipsum dolor sit amet, consectetur adipiscing elit. Donec libero risus, commodo vitae, pharetra mollis, posuere eu, pede. Nulla nec tortor. Donec id elit quis purus consectetur consequat. Nam congue semper tellus. Sed erat dolor, dapibus sit amet, venenatis ornare, ultrices ut, nisi.

220 | 221 | 222 |
223 |
224 |
225 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

226 |
227 |
228 | 229 |
230 | 231 | 232 |
233 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

234 |
235 | 236 | 237 | 238 |

Framework Icons (content color preview)

239 |
    240 |
  • 241 |
  • 242 |
  • 243 |
  • 244 |
  • 245 |
  • 246 |
  • 247 |
  • 248 |
  • 249 |
  • 250 |
  • 251 |
  • 252 |
  • 253 |
  • 254 |
  • 255 |
  • 256 |
  • 257 |
  • 258 |
  • 259 |
  • 260 |
  • 261 |
  • 262 |
  • 263 |
  • 264 |
  • 265 |
  • 266 |
  • 267 |
  • 268 |
  • 269 |
  • 270 |
  • 271 |
  • 272 |
  • 273 |
  • 274 |
  • 275 |
  • 276 |
  • 277 |
  • 278 |
  • 279 |
  • 280 |
  • 281 |
  • 282 |
  • 283 |
  • 284 |
  • 285 |
  • 286 |
  • 287 |
  • 288 |
  • 289 |
  • 290 |
  • 291 |
  • 292 |
  • 293 |
  • 294 |
  • 295 |
  • 296 |
  • 297 |
  • 298 |
  • 299 |
  • 300 |
  • 301 |
  • 302 |
  • 303 |
  • 304 |
  • 305 |
  • 306 |
  • 307 |
  • 308 |
  • 309 |
  • 310 |
  • 311 |
  • 312 |
  • 313 |
  • 314 |
  • 315 |
  • 316 |
  • 317 |
  • 318 |
  • 319 |
  • 320 |
  • 321 |
  • 322 |
  • 323 |
  • 324 |
  • 325 |
  • 326 |
  • 327 |
  • 328 |
  • 329 |
  • 330 |
  • 331 |
  • 332 |
  • 333 |
  • 334 |
  • 335 |
  • 336 |
  • 337 |
  • 338 |
  • 339 |
  • 340 |
  • 341 |
  • 342 |
  • 343 |
  • 344 |
  • 345 |
  • 346 |
  • 347 |
  • 348 |
  • 349 |
  • 350 |
  • 351 |
  • 352 |
  • 353 |
  • 354 |
  • 355 |
  • 356 |
  • 357 |
  • 358 |
  • 359 |
  • 360 |
  • 361 |
  • 362 |
  • 363 |
  • 364 |
  • 365 |
  • 366 |
  • 367 |
  • 368 |
  • 369 |
  • 370 |
  • 371 |
  • 372 |
  • 373 |
  • 374 |
  • 375 |
  • 376 |
  • 377 |
  • 378 |
  • 379 |
  • 380 |
  • 381 |
  • 382 |
  • 383 |
  • 384 |
  • 385 |
  • 386 |
  • 387 |
  • 388 |
  • 389 |
  • 390 |
  • 391 |
  • 392 |
  • 393 |
  • 394 |
  • 395 |
  • 396 |
  • 397 |
  • 398 |
  • 399 |
  • 400 |
  • 401 |
  • 402 |
  • 403 |
  • 404 |
  • 405 |
  • 406 |
  • 407 |
  • 408 |
  • 409 |
  • 410 |
  • 411 |
  • 412 |
  • 413 |
414 | 415 | 416 | 417 |

Slider

418 |
419 | 420 | 421 | 422 | 423 |

Datepicker

424 |
425 | 426 | 427 | 428 | 429 |

Progressbar

430 |
431 | 432 | 433 | 434 |

Highlight / Error

435 |
436 |
437 |

438 | Hey! Sample ui-state-highlight style.

439 |
440 |
441 |
442 |
443 |
444 |

445 | Alert: Sample ui-state-error style.

446 |
447 |
448 | 449 | 450 | 451 | --------------------------------------------------------------------------------