├── .gitignore ├── LICENSE ├── README.md ├── django-run-demo.sh ├── django-setup-demo.sh ├── example_project ├── example_project │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py └── segmentation │ ├── __init__.py │ ├── models.py │ ├── static │ ├── css │ │ ├── bootstrap-2.2.1.min.css │ │ ├── bootstrap-responsive.min.css │ │ ├── jquery-ui-1.9.1.custom.min.css │ │ └── mturk.css │ ├── img │ │ ├── adjust0.jpg │ │ ├── adjust1.jpg │ │ ├── adjust2.jpg │ │ ├── bad-close.jpg │ │ ├── bad-vertex.jpg │ │ ├── bad1.jpg │ │ ├── bad2.jpg │ │ ├── bad3.jpg │ │ ├── bad4.jpg │ │ ├── bad5.jpg │ │ ├── bad6.jpg │ │ ├── controls.jpg │ │ ├── draw1.jpg │ │ ├── draw5.jpg │ │ ├── draw6.jpg │ │ ├── glyphicons-halflings.png │ │ ├── good-close.jpg │ │ ├── good11.jpg │ │ ├── good12.jpg │ │ ├── good4.jpg │ │ ├── good5.jpg │ │ ├── good9.jpg │ │ ├── segment_material_bad1.jpg │ │ ├── segment_material_bad2.jpg │ │ ├── segment_material_bad3.jpg │ │ ├── segment_material_bad4.jpg │ │ ├── segment_material_good1.jpg │ │ └── segment_material_good2.jpg │ └── js │ │ ├── common │ │ ├── actions.coffee │ │ ├── active_timer.coffee │ │ ├── geom.coffee │ │ ├── get_url_params.coffee │ │ ├── hacks.coffee │ │ ├── modals.coffee │ │ ├── scroll.coffee │ │ ├── ui_events.coffee │ │ └── util.coffee │ │ ├── lib │ │ ├── bootstrap-2.2.1.min.js │ │ ├── csrf.js │ │ ├── delaunay.js │ │ ├── jquery-1.8.2.min.js │ │ ├── jquery-ui-1.9.1.custom.min.js │ │ ├── jquery.hotkeys.js │ │ ├── json2.js │ │ └── kinetic-v4.3.3.min.js │ │ ├── mturk │ │ ├── mt_segment_material.coffee │ │ └── mt_submit.coffee │ │ └── poly │ │ ├── actions.coffee │ │ ├── controller_state.coffee │ │ ├── controller_ui.coffee │ │ ├── polygon_ui.coffee │ │ └── stage_ui.coffee │ ├── templates │ ├── base.html │ ├── base_scripts.html │ ├── error.html │ ├── modal │ │ ├── modal_areyousure.html │ │ ├── modal_error.html │ │ ├── modal_form.html │ │ ├── modal_instructions.html │ │ ├── modal_loading.html │ │ ├── mt_modal_feedback.html │ │ └── poly_modal_intersect.html │ ├── mturk │ │ ├── mt_base_fixed.html │ │ ├── mt_base_responsive.html │ │ ├── mt_segment_material.html │ │ ├── mt_segment_material_inst.html │ │ └── mt_segment_material_inst_content.html │ └── poly │ │ ├── poly_controls.html │ │ └── poly_scripts.html │ ├── tests.py │ └── views.py ├── index.html ├── python-run-demo.sh └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | *.py[cod] 3 | /static/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Sean Bell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenSurfaces Segmentation UI 2 | This repository contains the segmentation user interface from the 3 | [OpenSurfaces](http://opensurfaces.cs.cornell.edu) project, extracted as a 4 | lightweight tool. A dummy server backend is included to run the demo. 5 | 6 | You can also view the demo 7 | [online](http://seanbell.ca/opensurfaces-segmentation-ui/). 8 | 9 |  10 | 11 | To run the demo, there are two versions: one with django, and one with no 12 | framework. The django version uses a dummy django server and compiles the 13 | website live as necessary. The non-django version is a flat html file 14 | extracted from the django version. 15 | 16 | If you find this tool helpful, please cite our 17 | [project](http://opensurfaces.cs.cornell.edu/publications): 18 |
19 | @inproceedings{bell13opensurfaces, 20 | author = "Sean Bell and Paul Upchurch and Noah Snavely and Kavita Bala", 21 | title = "OpenSurfaces: A Richly Annotated Catalog of Surface Appearance", 22 | booktitle = "SIGGRAPH Conf. Proc.", 23 | volume = "32", 24 | number = "4", 25 | year = "2013", 26 | } 27 |28 | and report any bugs using the [GitHub issue 29 | tracker](https://github.com/seanbell/opensurfaces-segmentation-ui/issues). 30 | Also, please "star" this project on GitHub; it's nice to see how many people 31 | are using our code. 32 | 33 | ## Version 1: Run with Django (Ubuntu Linux) 34 | 35 | 1. Install dependencies (`coffee-script`, `django`, `django-compressor`, 36 | `ua-parser`, `BeautifulSoup`): 37 | 38 | Note: this will change your django current installation if you are 39 | not somewhere between `1.4.*` and `1.6.*`. I suggest looking into the 40 | `virtualenv` package if this is a problem for you. 41 |
42 | ./django-setup-demo.sh 43 |44 | 45 | 2. Start the local webserver: 46 |
47 | ./django-run-demo.sh 48 |49 | 50 | 3. Visit `localhost:8000` in a web browser 51 | 52 | To get the demo to work on Mac and Windows, you will have to look at the above 53 | scripts and run the equivalent commands for your system. 54 | 55 | After drawing 6 polygons, the submit button will show you the POST data 56 | that would have been sent to the server. 57 | 58 | ## Version 2: Run without Django (Linux or Mac) 59 | 60 | 1. Install `npm` and `node.js`. On Ubuntu, this is: 61 |
62 | sudo apt-get install npm nodejs 63 |64 | 65 | 2. Install `coffee-script`: 66 |
67 | sudo npm install -g coffee-script 68 |69 | 70 | 3. Build static files (js, css, img) and then start a local python-based 71 | webserver: 72 |
73 | ./python-run-demo.sh 74 |75 | 76 | 4. Visit `localhost:8000` in a web browser 77 | 78 | To get the demo to work on Windows, you will have to look at the above scripts 79 | and run the equivalent commands for your system. 80 | 81 | ## Project Notes 82 | 83 | #### POST data 84 | 85 | When a user submits, the client will POST the data to the same URL. On 86 | success, the client expects the JSON response `{"message": "success", "result": 87 | "success"}`. The client will then notify the MTurk server that the task is 88 | completed. For more details, see 89 | `example_project/segmentation/views.py`. 90 | 91 | When a user submits, the POST will contain these fields: 92 |
93 | results: a dictionary mapping from the photo ID (which is just "1" in 94 | this example) to a list of polygons. Example: 95 | {"1": [[x1,y1,x2,y2,x3,y3,...], [x1,y1,x2,y2,...]]}. 96 | Coordinates are scaled with respect to the source photo dimensions, so both 97 | x and y are in the range 0 to 1. 98 | 99 | time_ms: amount of time the user spent (whether or not they were active) 100 | 101 | time_active_ms: amount of time that the user was active in the current window 102 | 103 | action_log: a JSON-encoded log of user actions 104 | 105 | screen_width: user screen width 106 | 107 | screen_height: user screen height 108 | 109 | version: always "1.0" 110 | 111 | feedback: omitted if there is no feedback; JSON encoded dictionary of the form: 112 | { 113 | 'thoughts': user's response to "What did you think of this task?", 114 | 'understand': user's response to "What parts didn't you understand?", 115 | 'other': user's response to "Any other feedback, improvements, or suggestions?" 116 | } 117 |118 | 119 | #### Feedback survey 120 | 121 | When the user finishes the task, a popup will ask for feedback. In the django 122 | version, disable this by setting `ask_for_feedback` to `'false'` in the file 123 | `example_project/segmentation/vies.py`. In the non-django verfsion, update the 124 | `window.ask_for_feedback` variable in `index.html`. 125 | 126 | I recommend asking for feedback after the 2nd or 3rd time a user has submitted, 127 | not the first time, and then not asking again (otherwise it gets annoying). 128 | Users usually don't have feedback until they have been working for a little while. 129 | 130 | #### Compiling from coffeescript 131 | The javascript for the tool is automatically compiled from coffeescript files 132 | by `django-compressor` and accessed by the client at a url of the form 133 | `/static/cache/js/*.js`. This is set up already if using django. 134 | 135 | If not using django, the `python-run-demo.sh` does this for you by manually 136 | compiling coffeescript files and storing them in the `/static/` folder. 137 | 138 | #### Browser compatibility 139 | This UI works in Chrome and Firefox only. The Django version includes a 140 | browser check that shows an error page if the user is not on Chrome or Firefox 141 | or is on a mobile device. 142 | 143 | #### Local `/static/` folder 144 | After you run the demo setup, the directory `/static/` will contain compiled css 145 | and javascript files. 146 | 147 | If you are usikng django and change any part of the static files (js, css, 148 | images, coffeescript), you will need to repopulate the static folder with this 149 | command: 150 |
151 | example_project/manage.py collectstatic --noinput 152 |153 | 154 | #### If you are building on top of this repository: 155 | In `example_project/settings.py`: 156 | 1. Change `SECRET_KEY` to some random string. 157 | 2. Fill in the rest of the values (admin name, database, etc). 158 | 159 | #### If you want to add this demo to your own (separate) Django project: 160 | In your `settings.py` file, make the following changes: 161 | 162 | 1. Make sure `STATIC_ROOT` is set to an absolute writable path. 163 | 164 | 2. Add this to the `STATICFILES_FINDERS` tuple: 165 |
166 | 'compressor.finders.CompressorFinder', 167 |168 | 169 | 3. Add this to the `INSTALLED_APPS` tuple: 170 |
171 | 'django.contrib.humanize', 172 | 'compressor', 173 | 'segmentation', 174 |175 | 176 | 4. Add this to `settings.py` (e.g. at the end): 177 |
178 | # Django Compressor 179 | COMPRESS_ENABLED = True 180 | COMPRESS_OUTPUT_DIR = 'cache' 181 | COMPRESS_PRECOMPILERS = ( 182 | ('text/coffeescript', 'coffee --bare --compile --stdio'), 183 | ('text/less', 'lessc -x {infile} {outfile}'), 184 | ) 185 |186 | -------------------------------------------------------------------------------- /django-run-demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | example_project/manage.py runserver 3 | -------------------------------------------------------------------------------- /django-setup-demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # install coffee-script dependencies (npm, node.js) 4 | sudo apt-get install npm nodejs 5 | 6 | # symbolically link nodejs to node so django can find it 7 | sudo ln -s /usr/bin/nodejs /usr/bin/node 8 | 9 | # install coffee-script 10 | sudo npm install -g coffee-script 11 | 12 | # install python dependencies: django, django-compressor, ua-parser, 13 | # BeautifulSoup 14 | sudo pip install \ 15 | "django>=1.4,<1.7" \ 16 | "django-compressor>=1.3,<1.4" \ 17 | "ua-parser>=0.3.2,<0.4" \ 18 | "BeautifulSoup<4.0" 19 | 20 | # In the settings file (example_project/example_project/settings.py), 21 | # automatically update the STATIC_ROOT variable to point to a temporary 22 | # directory in the current repository. It doesn't matter what the 23 | # directory is, as long as it's writable and previously unused. 24 | STATIC_ROOT=$(pwd)/static/ 25 | mkdir -p $STATIC_ROOT 26 | sed -r -i -e "s|^\s*STATIC_ROOT\s*=.*$|STATIC_ROOT = '$STATIC_ROOT'|" \ 27 | example_project/example_project/settings.py 28 | 29 | # Copy static files to the new directory 30 | example_project/manage.py collectstatic --noinput 31 | -------------------------------------------------------------------------------- /example_project/example_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/example_project/__init__.py -------------------------------------------------------------------------------- /example_project/example_project/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for example_project project. 2 | 3 | DEBUG = True 4 | TEMPLATE_DEBUG = DEBUG 5 | 6 | ADMINS = ( 7 | # ('Your Name', 'your_email@example.com'), 8 | ) 9 | 10 | MANAGERS = ADMINS 11 | 12 | DATABASES = { 13 | 'default': { 14 | 'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 15 | 'NAME': '', # Or path to database file if using sqlite3. 16 | 'USER': '', # Not used with sqlite3. 17 | 'PASSWORD': '', # Not used with sqlite3. 18 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 19 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 20 | } 21 | } 22 | 23 | # Local time zone for this installation. Choices can be found here: 24 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 25 | # although not all choices may be available on all operating systems. 26 | # In a Windows environment this must be set to your system time zone. 27 | TIME_ZONE = 'America/Chicago' 28 | 29 | # Language code for this installation. All choices can be found here: 30 | # http://www.i18nguy.com/unicode/language-identifiers.html 31 | LANGUAGE_CODE = 'en-us' 32 | 33 | SITE_ID = 1 34 | 35 | # If you set this to False, Django will make some optimizations so as not 36 | # to load the internationalization machinery. 37 | USE_I18N = True 38 | 39 | # If you set this to False, Django will not format dates, numbers and 40 | # calendars according to the current locale. 41 | USE_L10N = True 42 | 43 | # If you set this to False, Django will not use timezone-aware datetimes. 44 | USE_TZ = True 45 | 46 | # Absolute filesystem path to the directory that will hold user-uploaded files. 47 | # Example: "/home/media/media.lawrence.com/media/" 48 | MEDIA_ROOT = '' 49 | 50 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 51 | # trailing slash. 52 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 53 | MEDIA_URL = '' 54 | 55 | # Absolute path to the directory static files should be collected to. 56 | # Don't put anything in this directory yourself; store your static files 57 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 58 | # Example: "/home/media/media.lawrence.com/static/" 59 | STATIC_ROOT = '/home/sbell/opensurfaces-segmentation-ui/static/' 60 | 61 | # URL prefix for static files. 62 | # Example: "http://media.lawrence.com/static/" 63 | STATIC_URL = '/static/' 64 | 65 | # Additional locations of static files 66 | STATICFILES_DIRS = ( 67 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 68 | # Always use forward slashes, even on Windows. 69 | # Don't forget to use absolute paths, not relative paths. 70 | ) 71 | 72 | # List of finder classes that know how to find static files in 73 | # various locations. 74 | STATICFILES_FINDERS = ( 75 | 'django.contrib.staticfiles.finders.FileSystemFinder', 76 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 77 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 78 | 'compressor.finders.CompressorFinder', 79 | ) 80 | 81 | # Make this unique, and don't share it with anybody. 82 | SECRET_KEY = 'a35f3&!fba)!azbml5#5j67%o6us97zo*_hwlo#k5=o8g^d8v^' 83 | 84 | # List of callables that know how to import templates from various sources. 85 | TEMPLATE_LOADERS = ( 86 | 'django.template.loaders.filesystem.Loader', 87 | 'django.template.loaders.app_directories.Loader', 88 | # 'django.template.loaders.eggs.Loader', 89 | ) 90 | 91 | MIDDLEWARE_CLASSES = ( 92 | 'django.middleware.common.CommonMiddleware', 93 | 'django.contrib.sessions.middleware.SessionMiddleware', 94 | 'django.middleware.csrf.CsrfViewMiddleware', 95 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 96 | 'django.contrib.messages.middleware.MessageMiddleware', 97 | # Uncomment the next line for simple clickjacking protection: 98 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 99 | ) 100 | 101 | ROOT_URLCONF = 'example_project.urls' 102 | 103 | # Python dotted path to the WSGI application used by Django's runserver. 104 | WSGI_APPLICATION = 'example_project.wsgi.application' 105 | 106 | TEMPLATE_DIRS = ( 107 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 108 | # Always use forward slashes, even on Windows. 109 | # Don't forget to use absolute paths, not relative paths. 110 | ) 111 | 112 | INSTALLED_APPS = ( 113 | 'django.contrib.auth', 114 | 'django.contrib.contenttypes', 115 | 'django.contrib.sessions', 116 | 'django.contrib.sites', 117 | 'django.contrib.messages', 118 | 'django.contrib.staticfiles', 119 | 'django.contrib.humanize', 120 | # Uncomment the next line to enable the admin: 121 | # 'django.contrib.admin', 122 | # Uncomment the next line to enable admin documentation: 123 | # 'django.contrib.admindocs', 124 | 'compressor', 125 | 'segmentation' 126 | ) 127 | 128 | # A sample logging configuration. The only tangible logging 129 | # performed by this configuration is to send an email to 130 | # the site admins on every HTTP 500 error when DEBUG=False. 131 | # See http://docs.djangoproject.com/en/dev/topics/logging for 132 | # more details on how to customize your logging configuration. 133 | LOGGING = { 134 | 'version': 1, 135 | 'disable_existing_loggers': False, 136 | 'filters': { 137 | 'require_debug_false': { 138 | '()': 'django.utils.log.RequireDebugFalse' 139 | } 140 | }, 141 | 'handlers': { 142 | 'mail_admins': { 143 | 'level': 'ERROR', 144 | 'filters': ['require_debug_false'], 145 | 'class': 'django.utils.log.AdminEmailHandler' 146 | } 147 | }, 148 | 'loggers': { 149 | 'django.request': { 150 | 'handlers': ['mail_admins'], 151 | 'level': 'ERROR', 152 | 'propagate': True, 153 | }, 154 | } 155 | } 156 | 157 | # Django Compressor 158 | COMPRESS_ENABLED = True 159 | COMPRESS_OUTPUT_DIR = 'cache' 160 | COMPRESS_PRECOMPILERS = ( 161 | ('text/coffeescript', 'coffee --bare --compile --stdio'), 162 | ('text/less', 'lessc -x {infile} {outfile}'), 163 | ) 164 | -------------------------------------------------------------------------------- /example_project/example_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | 3 | # Uncomment the next two lines to enable the admin: 4 | # from django.contrib import admin 5 | # admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | url(r'^$', 'segmentation.views.demo', name='demo'), 9 | 10 | # Examples: 11 | # url(r'^$', 'example_project.views.home', name='home'), 12 | # url(r'^example_project/', include('example_project.foo.urls')), 13 | 14 | # Uncomment the admin/doc line below to enable admin documentation: 15 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 16 | 17 | # Uncomment the next line to enable the admin: 18 | # url(r'^admin/', include(admin.site.urls)), 19 | ) 20 | -------------------------------------------------------------------------------- /example_project/example_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example_project project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_project.settings") 19 | 20 | # This application object is used by any WSGI server configured to use this 21 | # file. This includes Django's development server, if the WSGI_APPLICATION 22 | # setting points here. 23 | from django.core.wsgi import get_wsgi_application 24 | application = get_wsgi_application() 25 | 26 | # Apply WSGI middleware here. 27 | # from helloworld.wsgi import HelloWorldApplication 28 | # application = HelloWorldApplication(application) 29 | -------------------------------------------------------------------------------- /example_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_project.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /example_project/segmentation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/__init__.py -------------------------------------------------------------------------------- /example_project/segmentation/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /example_project/segmentation/static/css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.2.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */@-ms-viewport{width:device-width}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /example_project/segmentation/static/css/mturk.css: -------------------------------------------------------------------------------- 1 | /*body, html { padding: 0px; margin: 0px; [>overflow:hidden;<] }*/ 2 | iframe { border: 1px solid #969696; } 3 | #mt-top { 4 | position: fixed; 5 | top: 0px; 6 | left: 0px; 7 | z-index: 999; 8 | width: 100%; 9 | height: 20px; 10 | } 11 | #mt-top-padding { 12 | height: 60px; 13 | } 14 | #modal-instructions { 15 | width: 1000px; 16 | margin-left: -500px; 17 | } 18 | #mt-feedback { 19 | padding: 0px; 20 | margin: 0px; 21 | margin-left: 20px; 22 | } 23 | #mt-feedback > input { 24 | margin-bottom: 0px; 25 | margin-top: -2px; 26 | vertical-align: middle; 27 | } 28 | #mt-instructions { 29 | background-color: #EDA126; 30 | font-weight: bold; 31 | padding: 8px; 32 | margin: 0px; 33 | } 34 | #mt-instructions > .btn { 35 | margin: 0px; 36 | } 37 | #mt-instructions > span { 38 | padding: 0px; 39 | margin: 0px; 40 | margin-top: -5px; 41 | } 42 | #mt-num-shapes { 43 | text-decoration: underline; 44 | } 45 | .ui-controls-top { 46 | background-color:#EEE; 47 | padding: 5px; 48 | margin: 0px; 49 | } 50 | .mt-caption { 51 | text-align: center; 52 | } 53 | .item-selected { 54 | background-color: #fff1c5; border: 1px solid #ff7000; 55 | -webkit-box-shadow: 0px 0px 5px #ff7000; 56 | -moz-box-shadow: 0px 0px 5px #ff7000; 57 | box-shadow: 0px 0px 5px #ff7000; 58 | } 59 | -------------------------------------------------------------------------------- /example_project/segmentation/static/img/adjust0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/adjust0.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/adjust1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/adjust1.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/adjust2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/adjust2.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/bad-close.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/bad-close.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/bad-vertex.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/bad-vertex.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/bad1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/bad1.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/bad2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/bad2.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/bad3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/bad3.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/bad4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/bad4.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/bad5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/bad5.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/bad6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/bad6.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/controls.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/controls.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/draw1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/draw1.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/draw5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/draw5.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/draw6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/draw6.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /example_project/segmentation/static/img/good-close.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/good-close.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/good11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/good11.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/good12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/good12.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/good4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/good4.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/good5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/good5.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/good9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/good9.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/segment_material_bad1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/segment_material_bad1.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/segment_material_bad2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/segment_material_bad2.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/segment_material_bad3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/segment_material_bad3.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/segment_material_bad4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/segment_material_bad4.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/segment_material_good1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/segment_material_good1.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/img/segment_material_good2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanbell/opensurfaces-segmentation-ui/247abb6c9ee6014ec9669c06463c28ea0f3f2f9a/example_project/segmentation/static/img/segment_material_good2.jpg -------------------------------------------------------------------------------- /example_project/segmentation/static/js/common/actions.coffee: -------------------------------------------------------------------------------- 1 | class UndoableEvent 2 | undo: (ui) -> throw { name: "TODO", message: "Unimplemetned Code" } 3 | run: (ui) -> throw { name: "TODO", message: "Unimplemetned Code" } 4 | redo: (ui) -> @run(ui) 5 | entry: -> throw { name: "TODO", message: "Unimplemetned Code" } 6 | 7 | class UndoRedo 8 | constructor: (@ui, args) -> 9 | @btn_undo = if args.btn_undo? then args.btn_undo else '#btn-undo' 10 | @btn_redo = if args.btn_redo? then args.btn_redo else '#btn-redo' 11 | 12 | @undo_stack = [] 13 | @redo_stack = [] 14 | 15 | # map events 16 | if @btn_undo? then $(@btn_undo).on('click', => @undo()) 17 | if @btn_redo? then $(@btn_redo).on('click', => @redo()) 18 | $(document).bind('keydown.ctrl_z', => @undo()) 19 | $(document).bind('keydown.meta_z', => @undo()) 20 | $(document).bind('keydown.ctrl_y', => @redo()) 21 | $(document).bind('keydown.meta_y', => @redo()) 22 | 23 | run: (e) -> if e? 24 | console.log 'run:' 25 | console.log e 26 | e.run(@ui) 27 | @undo_stack.push(e) 28 | @redo_stack = [] 29 | @ui.s.log.action(e.entry()) 30 | @update_buttons() 31 | 32 | undo: => 33 | if @can_undo() 34 | e = @undo_stack.pop() 35 | console.log 'undo:' 36 | console.log e 37 | e.undo(@ui) 38 | @redo_stack.push(e) 39 | @ui.s.log.action(name: 'UndoRedo.undo', event: e) 40 | @update_buttons() 41 | else 42 | @ui.s.log.attempted(name: 'UndoRedo.undo') 43 | 44 | redo: => 45 | if @can_redo() 46 | e = @redo_stack.pop() 47 | console.log 'redo:' 48 | console.log e 49 | e.redo(@ui) 50 | @undo_stack.push(e) 51 | @ui.s.log.action(name: 'UndoRedo.redo', event: e) 52 | @update_buttons() 53 | else 54 | @ui.s.log.attempted(name: 'UndoRedo.redo') 55 | 56 | can_undo: -> @undo_stack.length > 0 57 | can_redo: -> @redo_stack.length > 0 58 | 59 | update_buttons: -> 60 | @ui.s.update_buttons() 61 | if @btn_undo? then set_btn_enabled(@btn_undo, @can_undo()) 62 | if @btn_redo? then set_btn_enabled(@btn_redo, @can_redo()) 63 | 64 | 65 | class ActionLog 66 | constructor: -> 67 | @entries = [] 68 | 69 | # successfully performed action 70 | action: (args) => 71 | entry = $.extend(true, {time: new Date(), done: true}, args) 72 | @entries.push(entry) 73 | console.log 'action:' 74 | console.log(entry) 75 | 76 | # invalid action 77 | attempted: (args) => 78 | entry = $.extend(true, {time: new Date(), done: false}, args) 79 | @entries.push(entry) 80 | console.log 'attempted:' 81 | console.log(entry) 82 | 83 | get_submit_data: => 84 | JSON.stringify(@entries) 85 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/common/active_timer.coffee: -------------------------------------------------------------------------------- 1 | # Timer that stops counting when the user leaves the window 2 | class ActiveTimer 3 | constructor: -> 4 | @started = false 5 | @total_start = null 6 | @active_start = null 7 | @partial_time_ms = 0 8 | 9 | $(window).on('focus', => 10 | if @started 11 | @active_start = Date.now() 12 | ) 13 | 14 | $(window).on('blur', => 15 | if @started and @active_start? 16 | @partial_time_ms += Date.now() - @active_start 17 | @active_start = null 18 | ) 19 | 20 | start: -> 21 | @total_start = Date.now() 22 | @active_start = Date.now() 23 | @partial_time_ms = 0 24 | @started = true 25 | 26 | ensure_started: -> 27 | if not @started then @start() 28 | 29 | time_ms: -> 30 | if @started 31 | Date.now() - @total_start 32 | else 33 | 0 34 | 35 | time_active_ms: -> 36 | if @started 37 | if @active_start? 38 | @partial_time_ms + (Date.now() - @active_start) 39 | else 40 | @partial_time_ms 41 | else 42 | 0 43 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/common/geom.coffee: -------------------------------------------------------------------------------- 1 | # handy constants 2 | INF = Number.POSITIVE_INFINITY 3 | NINF = Number.NEGATIVE_INFINITY 4 | 5 | # Axis-aligned bounding box (2D) 6 | class AABB 7 | constructor: (min={x:INF,y:INF}, max={x:NINF,y:NINF}) -> 8 | @min = clone_pt(min) 9 | @max = clone_pt(max) 10 | 11 | # sets this to the empty AABB 12 | reset: -> 13 | @min={x:INF,y:INF} 14 | @max={x:NINF,y:NINF} 15 | 16 | # clone this AABB 17 | clone: -> 18 | new AABB(@min, @max) 19 | 20 | midpoint: -> 21 | x: 0.5 * (@min.x + @max.x) 22 | y: 0.5 * (@min.y + @max.y) 23 | 24 | # returns true if this contains point p 25 | contains_pt: (p) -> 26 | p.x >= @min.x and p.x <= @max.x and p.y >= @min.y and p.y <= @max.y 27 | 28 | # returns true if this intersects b 29 | intersects_bbox: (b) -> 30 | (@max.x >= b.min.x and @min.x <= b.max.x and 31 | @max.y >= b.min.y and @min.y <= b.max.y) 32 | 33 | # returns true if this entirely contains b 34 | contains_bbox: (b) -> 35 | (@max.x >= b.max.x and @min.x <= b.min.x and 36 | @max.y >= b.max.y and @min.y <= b.min.y) 37 | 38 | # expand the AABB to include a new point 39 | extend_pt: (p) -> 40 | @min = {x: Math.min(@min.x, p.x), y: Math.min(@min.y, p.y)} 41 | @max = {x: Math.max(@max.x, p.x), y: Math.max(@max.y, p.y)} 42 | 43 | # compute a new AABB from the given points 44 | recompute_from_points: (points) -> 45 | @reset() 46 | for p in points 47 | @extend_pt(p) 48 | 49 | normalize_pt: (p) -> 50 | x: (p.x - @min.x) / (@max.x - @min.x) 51 | y: (p.y - @min.y) / (@max.y - @min.y) 52 | 53 | 54 | # Mathematical polygon (2D) 55 | class Polygon 56 | constructor: (pts, @open = true) -> 57 | @aabb = new AABB() 58 | @points = [] 59 | @push_points(pts) if pts? 60 | 61 | # add point (make sure to check can_push_point first) 62 | push_point: (p) -> if p? 63 | @points.push(clone_pt(p)) 64 | @aabb.extend_pt(p) 65 | 66 | push_points: (pts) -> 67 | for p in pts 68 | @push_point(p) 69 | 70 | # remove last point 71 | pop_point: (p) -> 72 | if @points.length > 0 73 | ret = @points.pop() 74 | @aabb.recompute_from_points(@points) 75 | ret 76 | 77 | # move point index i to position p 78 | set_point: (i, p) -> if p? 79 | @points[i].x = p.x 80 | @points[i].y = p.y 81 | @aabb.recompute_from_points(@points) 82 | p 83 | 84 | # change close state (make sure to check can_close first) 85 | close: -> @open = false 86 | unclose: -> @open = true 87 | empty: -> @points.length == 0 88 | 89 | # point accessors 90 | get_pt: (i) -> clone_pt(@points[i]) 91 | num_points: -> @points.length 92 | clone_points: -> (clone_pt(p) for p in @points) 93 | get_aabb: -> @aabb.clone() 94 | 95 | # returns true if p can be added 96 | can_push_point: (p) -> 97 | if not @open then return false 98 | if @points.length < 3 then return true 99 | return not @intersects_segment( 100 | @points[@points.length - 1], p, [@points.length - 2]) 101 | 102 | # returns true if this poly can close without self-intersecting 103 | can_close: () -> 104 | if not @open or @points.length < 3 then return false 105 | if @points.length == 3 then return true 106 | return not @intersects_segment(@points[0], 107 | @points[@points.length - 1], [0, @points.length - 2]) 108 | 109 | # return the average vertex position 110 | midpoint: -> 111 | if @points.length < 1 then return undefined 112 | x = y = 0 113 | for p in @points 114 | x += p.x 115 | y += p.y 116 | return {x: x/@points.length, y: y/@points.length} 117 | 118 | # returns the exact centroid 119 | centroid: -> 120 | if @points.length < 1 then return undefined 121 | A = Cx = Cy = 0 122 | for v0, i in @points 123 | v1 = @points[(i + 1) % @points.length] 124 | t = v0.x * v1.y - v1.x * v0.y 125 | A += t 126 | Cx += (v0.x + v1.x) * t 127 | Cy += (v0.y + v1.y) * t 128 | if Math.abs(A) < 0.001 then return @midpoint() 129 | return {x: Cx / (3 * A), y: Cy / (3 * A)} 130 | 131 | # return the midpoint of the axis-aligned bounding box 132 | aabb_midpoint: -> 133 | @aabb.midpoint() 134 | 135 | # returns exact area 136 | area: -> 137 | A = 0 138 | for v0, i in @points 139 | v1 = @points[(i + 1) % @points.length] 140 | A += v0.x * v1.y - v1.x * v0.y 141 | return Math.abs(A / 2) 142 | 143 | # returns a good position to place a label 144 | # requires: delaunay.js 145 | labelpos: -> 146 | # if the centroid is good enough, use it 147 | best_m = @centroid() 148 | if @contains_pt(best_m) 149 | best_d2 = @dist2_to_edge(best_m) 150 | if best_d2 > 2000 # good enough already 151 | return best_m 152 | else 153 | best_d2 = 0 154 | 155 | # delaunay triangulation (O(n^2) worst case) 156 | del_tri = delaunay_triangulate(@clone_points()) 157 | 158 | # find the triangle midpoint that is inside the poly 159 | # and furthest from the edges 160 | for t in del_tri 161 | m = t.midpoint() 162 | if @contains_pt(m) 163 | d2 = @dist2_to_edge(m) 164 | if d2 > best_d2 165 | best_d2 = d2 166 | best_m = m 167 | 168 | return best_m 169 | 170 | # returns true iff this polygon is complex 171 | # (assumes non-self-intersecting) 172 | is_convex: -> 173 | s = true 174 | for v0, i in @points 175 | v1 = @points[(i + 1) % @points.length] 176 | v2 = @points[(i + 2) % @points.length] 177 | if i == 0 178 | s = ccw(v1, v0, v2) 179 | else if s != ccw(v1, v0, v2) 180 | return false 181 | return true 182 | 183 | # returns the closest squared distance from p to an edge 184 | dist2_to_edge: (p) -> 185 | n = @points.length 186 | if n < 2 then return 0 187 | min_d2 = Number.POSITIVE_INFINITY 188 | for v0, i in @points 189 | v1 = @points[(i + 1) % @points.length] 190 | min_d2 = Math.min(min_d2, seg_pt_dist2(v0.x, v0.y, v1.x, v1.y, p.x, p.y)) 191 | return min_d2 192 | 193 | # returns true if this intersects the segment p1--p2 194 | intersects_segment: (p1, p2, excludes=[]) -> 195 | n = @points.length 196 | if n < 2 then return false 197 | max = if @open then (n-1) else n 198 | for i in [0...max] when i not in excludes 199 | v1 = @points[i]; v2 = @points[(i+1) % n] 200 | if segments_intersect(p1, p2, v1, v2) then return true 201 | return false 202 | 203 | # returns true if this polygon contains point p 204 | # adapted from http://www.ecse.rpi.edu/Homepages/wrf 205 | # /Research/Short_Notes/pnpoly.html 206 | contains_pt: (p) -> 207 | if @points.length < 3 or not @aabb.contains_pt(p) then return false 208 | n = @points.length; c = false; i = 0; j = n-1 209 | while i < n 210 | vi = @points[i] 211 | if vi.x == p.x and vi.y == p.y then return true 212 | vj = @points[j] 213 | if (((vi.y > p.y) != (vj.y > p.y)) and 214 | (p.x < (vj.x - vi.x) * (p.y - vi.y) / (vj.y - vi.y) + vi.x)) 215 | c = not c 216 | j = i++ 217 | return c 218 | 219 | # returns true if the polygon self-intersects at vertex i 220 | self_intersects_at_index: (i) -> 221 | if @points.length < 4 then return false 222 | m2 = mod(i - 2, @points.length) 223 | m1 = mod(i - 1, @points.length) 224 | p1 = mod(i + 1, @points.length) 225 | return @intersects_segment(@points[i], @points[p1], [m1, i, p1]) or 226 | @intersects_segment(@points[i], @points[m1], [m2, m1, i]) 227 | 228 | # returns true if some segment of this polygon intersects another segment 229 | self_intersects: -> 230 | max = if @open then (@points.length-1) else @points.length 231 | for i in [0...max] 232 | if @self_intersects_at_index(i) 233 | return true 234 | return false 235 | 236 | # returns true if some edge of this polygon intersects poly 237 | # entire containment of one poly with the other returns false 238 | partially_intersects_poly: (poly) -> 239 | if not @aabb.intersects_bbox(poly.aabb) then return false 240 | n = @points.length 241 | if n < 2 then return false 242 | max = if @open then (n-1) else n 243 | for i in [0...max] 244 | v1 = @points[i]; v2 = @points[(i+1) % n] 245 | if poly.intersects_segment(v1, v2) then return true 246 | return false 247 | 248 | # returns true if this polygon entirely contains poly 249 | contains_poly: (poly) -> 250 | if not @aabb.contains_bbox(poly.aabb) then return false 251 | for p in poly.points 252 | if not @contains_pt(p) then return false 253 | return true 254 | 255 | # returns true if this polygon intersects poly 256 | intersects_poly: (poly) -> 257 | @contains_poly(poly) or @partially_intersects_poly(poly) 258 | 259 | 260 | # Mathematical complex polygon (2D). 261 | # Representation: list of vertices, triangles, and unorganized segments. 262 | # Triangles and segments are indices into the list of vertices. 263 | # Triangles are length-3 arrays, vertices are length-2 integer arrays. 264 | class ComplexPolygon 265 | constructor: (vertices, triangles, segments) -> 266 | # clone the input arrays 267 | @vertices = vertices.splice(0) 268 | @triangles = triangles.splice(0) 269 | @segments = segments.splice(0) 270 | 271 | @aabb = new AABB() 272 | for p in @vertices 273 | @aabb.extend_pt(p) 274 | 275 | # computes the centroid of this polygon 276 | centroid: -> 277 | sum_x = 0 278 | sum_y = 0 279 | sum_a = 0 280 | for t in @triangles_points() 281 | m = mean_pt(t) 282 | a = tri_area(t[0], t[1], t[2]) 283 | sum_x += m.x * a 284 | sum_y += m.y * a 285 | sum_a += a 286 | x: sum_x / sum_a 287 | y: sum_y / sum_a 288 | 289 | get_aabb: -> @aabb.clone() 290 | 291 | aabb_midpoint: -> 292 | @aabb.midpoint() 293 | 294 | contains_pt: (p) -> 295 | if not @aabb.contains_pt(p) then return false 296 | for t in @triangles_points() 297 | if pt_in_tri(t[0], t[1], t[2], p) 298 | return true 299 | return false 300 | 301 | # returns a good position to place a label 302 | labelpos: -> 303 | centroid = @centroid() 304 | if @contains_pt(centroid) 305 | best_m = centroid 306 | best_d2 = @dist2_to_edge(centroid) 307 | else 308 | best_d2 = NINF 309 | for t in @triangles_points() 310 | m = mean_pt(t) 311 | d2 = @dist2_to_edge(m) 312 | if d2 > best_d2 313 | best_d2 = d2 314 | best_m = m 315 | return best_m 316 | 317 | # returns the smallest squared distance from p to an edge 318 | dist2_to_edge: (p) -> 319 | min_d2 = Number.POSITIVE_INFINITY 320 | for s in @segments 321 | v0 = @vertices[s[0]] 322 | v1 = @vertices[s[1]] 323 | min_d2 = Math.min(min_d2, seg_pt_dist2(v0.x, v0.y, v1.x, v1.y, p.x, p.y)) 324 | return min_d2 325 | 326 | # returns a list of line segments as nested point arrays 327 | segments_points: -> 328 | ([@vertices[s[0]], @vertices[s[1]]] for s in @segments) 329 | 330 | # returns a list of triangles as nested point arrays 331 | triangles_points: -> 332 | ([@vertices[t[0]], @vertices[t[1]], @vertices[t[2]]] for t in @triangles) 333 | 334 | 335 | # Represents a pinhole camera model but without a transform (located at the 336 | # origin) 337 | class Perspective 338 | constructor: (args) -> 339 | if args.focal? 340 | @f = args.focal 341 | else if args.width? and args.fov? 342 | @f = args.width / (2 * Math.tan(args.fov / 2)) 343 | else 344 | console.log args 345 | throw {name: "error: bad arguments", args: args} 346 | 347 | project: (p) -> 348 | {x: @f * p.x / p.z, y: @f * p.y / p.z} 349 | 350 | unproject: (p, z=p.z) -> 351 | {x: p.x * p.z / @f, y: p.y * p.z / @f, z: z} 352 | 353 | 354 | # returns true if AB intersects CD (2D) 355 | segments_intersect = (A, B, C, D) -> 356 | ccw(A, C, D) isnt ccw(B, C, D) and ccw(A, B, C) isnt ccw(A, B, D) 357 | ccw = (A, B, C) -> (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x) 358 | 359 | # squared point-segment distance 360 | seg_pt_dist2 = (x1, y1, x2, y2, px, py) -> 361 | pd2 = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) 362 | 363 | if (pd2 == 0) 364 | # Points are coincident. 365 | x = x1 366 | y = y2 367 | else 368 | # parameter of closest point on the line 369 | u = ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / pd2 370 | if (u < 0) # off the end 371 | x = x1 372 | y = y1 373 | else if (u > 1.0) # off the end 374 | x = x2 375 | y = y2 376 | else # interpolate 377 | x = x1 + u * (x2 - x1) 378 | y = y1 + u * (y2 - y1) 379 | 380 | return (x - px) * (x - px) + (y - py) * (y - py) 381 | 382 | # converts an object having an x and y attribute (possibly z) into a point 383 | clone_pt = (o) -> 384 | if o? 385 | if o.z? 386 | {x: o.x, y: o.y, z: o.z} 387 | else 388 | {x: o.x, y: o.y} 389 | else 390 | null 391 | 392 | 393 | # distance between p and q 394 | dist_pt = (p, q) -> 395 | dx = p.x - q.x 396 | dy = p.y - q.y 397 | Math.sqrt(dx * dx + dy * dy) 398 | 399 | 400 | # subtract two points 401 | sub_pt = (p, q) -> {x: p.x - q.x, y: p.y - q.y} 402 | 403 | 404 | # computes the mean position of a list of points 405 | mean_pt = (points) -> 406 | x = 0 407 | y = 0 408 | for p in points 409 | x += p.x 410 | y += p.y 411 | {x: x/points.length, y: y/points.length} 412 | 413 | 414 | # computes the area of the triangle a,b,c 415 | tri_area = (a, b, c) -> 416 | 0.5 * Math.abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) 417 | 418 | 419 | # return whether v is in the triangle a,b,c 420 | # (from http://mathworld.wolfram.com/TriangleInterior.html) 421 | pt_in_tri = (a, b, c, v) -> 422 | det = (u, v) -> u.x * v.y - u.y * v.x 423 | v0 = a 424 | v1 = sub_pt(b, a) 425 | v2 = sub_pt(c, a) 426 | det_v1v2 = det(v1, v2) 427 | a = (det(v, v2) - det(v0, v2)) / det_v1v2 428 | if a < 0 or a > 1 then return false 429 | b = -(det(v, v1) - det(v0, v1)) / det_v1v2 430 | return b >= 0 and a + b <= 1 431 | 432 | # computes mod, wrapping around negative numbers properly 433 | mod = (x,n) -> ((x % n) + n) % n 434 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/common/get_url_params.coffee: -------------------------------------------------------------------------------- 1 | # Returns get parameters. 2 | # 3 | # If the desired param does not exist, null will be returned 4 | # 5 | # To get the document params: 6 | # @example value = $(document).getUrlParam("paramName"); 7 | # 8 | # To get the params of a html-attribut (uses src attribute) 9 | # @example value = $('#imgLink').getUrlParam("paramName"); 10 | jQuery.fn.extend 11 | getUrlParam: (strParamName) -> 12 | strParamName = escape(unescape(strParamName)) 13 | 14 | if $(this).attr("nodeName") is "#document" 15 | if window.location.search.search(strParamName) > -1 16 | qString = window.location.search.substr(1,window.location.search.length).split("&") 17 | else if $(this).attr("src") isnt "undefined" 18 | strHref = $(this).attr("src") 19 | if strHref.indexOf("?") > -1 20 | strQueryString = strHref.substr(strHref.indexOf("?") + 1) 21 | qString = strQueryString.split("&") 22 | else if $(this).attr("href") isnt "undefined" 23 | strHref = $(this).attr("href") 24 | if strHref.indexOf("?") > -1 25 | strQueryString = strHref.substr(strHref.indexOf("?") + 1) 26 | qString = strQueryString.split("&") 27 | else 28 | return null 29 | 30 | return null unless qString 31 | 32 | returnVal = (query.split("=")[1] for query in qString when escape(unescape(query.split("=")[0])) is strParamName) 33 | 34 | if returnVal.lenght is 0 35 | null 36 | else if returnVal.lenght is 1 37 | returnVal[0] 38 | else 39 | returnVal 40 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/common/hacks.coffee: -------------------------------------------------------------------------------- 1 | ## BROWSER HACKS 2 | 3 | # Browser: IE 4 | # Problem: doesn't implement vector-effect: non-scaling-stroke 5 | # Hack solution: replace with stroke-width: 0.2% 6 | if $.browser.msie 7 | console.log 'detected: IE' 8 | do -> 9 | fix_ie = -> 10 | $('.nss').css('vector-effect', '') \ 11 | .css('stroke-width', '0.2%') \ 12 | .css('stroke-linecap', 'round') 13 | fix_ie() 14 | $(document).on('items-added', fix_ie) 15 | if Number($.browser.version) < 10 16 | $('#outdated-browser-alert').show() 17 | 18 | # Browser: Firefox and IE 19 | # Problem: inline SVG height doesn't scale properly 20 | # Hack solution: store aspect in tag and manually enforce aspect ratio 21 | if not ($.browser.webkit or $.browser.opera) 22 | fix_aspect = -> 23 | $('svg.fix-aspect').each( -> 24 | t = $(this) 25 | aspect = t.attr('data-aspect') 26 | #console.log aspect 27 | if aspect? 28 | t.height(t.width() / aspect) 29 | ) 30 | fix_aspect() 31 | $(document).on('items-added', fix_aspect) 32 | $(window).on('resize', fix_aspect) 33 | 34 | # to show if not webkit 35 | if not $.browser.webkit 36 | $('.if-not-webkit').show() 37 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/common/modals.coffee: -------------------------------------------------------------------------------- 1 | ## LOGIC FOR MODAL DIALOGS 2 | 3 | ## templates/modal_areyousure.html 4 | 5 | window.show_modal_areyousure = (args) -> 6 | $("#modal-areyousure-label").html(args.label) if args.label? 7 | $("#modal-areyousure-message").html(args.message) if args.message? 8 | $("#modal-areyousure-yes").text(args.yes_text) if args.yes_text? 9 | $("#modal-areyousure-no").text(args.no_text) if args.no_text? 10 | $("#modal-areyousure-yes").off("click").on("click", args.yes) if args.yes? 11 | $("#modal-areyousure-no").off("click").on("click", args.no) if args.no? 12 | args.before_show?() 13 | $("#modal-areyousure").modal("show") 14 | 15 | 16 | ## templates/modal_error.html 17 | 18 | window.show_modal_error = (message, header) -> 19 | $("#modal-error-label").html(if header? then header else "Error!") 20 | $("#modal-error-message").html(message) 21 | $("#modal-error").modal("show") 22 | 23 | window.hide_modal_error = -> 24 | $("#modal-error").modal("hide") 25 | 26 | 27 | ## templates/modal_form.html 28 | 29 | window.show_modal_form = (args) -> 30 | $("#modal-form-label").html(args.label) if args.label? 31 | $("#modal-form-body").html(args.body) if args.body? 32 | $("#modal-form-yes").text(args.yes_text) if args.yes_text? 33 | $("#modal-form-no").text(args.no_text) if args.no_text? 34 | $("#modal-form-yes").off("click").on "click", args.yes if args.yes? 35 | $("#modal-form-no").off("click").on "click", args.no if args.no? 36 | args.before_show?() 37 | $("#modal-form").off("shown").on("shown", -> 38 | $("#modal-form-body").find("input").filter(-> 39 | $(this).val() is "" 40 | ).first().focus() 41 | ) 42 | $("#modal-form").modal("show") 43 | 44 | window.hide_modal_form = -> 45 | $("#modal-form").modal("hide") 46 | 47 | 48 | ## templates/modal_give_up.html 49 | 50 | window.show_modal_give_up = (label_message, prompt_message, submit_message, suggested_reasons) -> 51 | $("#modal-give-up-text").val("") 52 | $("#modal-give-up-label").text(label_message) 53 | $("#modal-give-up-prompt").text(prompt_message) 54 | $("#modal-give-up-submit").text(submit_message) 55 | $("#modal-give-up-submit").attr("disabled", "disabled") 56 | reasons = $("#modal-give-up-suggested-reasons") 57 | reasons.empty() 58 | if suggested_reasons? 59 | for str in suggested_reasons 60 | r = $("") 61 | r.on("click", -> 62 | $("#modal-give-up-text").val(str) 63 | $("#modal-give-up-submit").removeAttr("disabled") 64 | ) 65 | reasons.append(r) 66 | reasons.append $("
Other problem:
") 67 | $("#modal-give-up").modal "show" 68 | 69 | window.hide_modal_give_up = -> 70 | $("#modal-give-up").modal "hide" 71 | 72 | $ -> 73 | $("#modal-give-up-text").on("input propertychange", -> 74 | text = $("#modal-give-up-text").val() 75 | if text and text.length > 10 76 | $("#modal-give-up-submit").removeAttr("disabled") 77 | else 78 | $("#modal-give-up-submit").attr("disabled", "disabled") 79 | ) 80 | 81 | 82 | ## templates/modal_loading.html 83 | 84 | window.show_modal_loading = (message, timeout) -> 85 | message = "Loading..." unless message? 86 | timeout = 1000 unless timeout? 87 | modal_loading_timeout = setTimeout(( -> 88 | $("#modal-loading-label").text(message) 89 | $("#modal-loading").modal( 90 | backdrop: "static" 91 | keyboard: false 92 | ).modal("show") 93 | ), timeout) 94 | window.hide_modal_loading = (on_hide) -> 95 | clearTimeout(modal_loading_timeout) if modal_loading_timeout? 96 | $modal = $("#modal-loading") 97 | $modal.modal("hide") 98 | $modal.off("hidden").on("hidden", on_hide) if on_hide? 99 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/common/scroll.coffee: -------------------------------------------------------------------------------- 1 | # 2 | # ENDLESS PAGINATION 3 | # 4 | 5 | $ -> 6 | $("a.endless_more").live("click", -> 7 | container = $(this).closest(".endless_container") 8 | get_url = $(this).attr("href") 9 | $(this).remove() 10 | 11 | container.find(".endless_loading").show() 12 | get_data = "querystring_key=" + $(this).attr("rel").split(" ")[0] 13 | $.get(get_url, get_data, (data) -> 14 | container.before(data) 15 | container.remove() 16 | $(document).trigger("items-added") 17 | ) 18 | false 19 | ) 20 | 21 | $("a.endless_page_link").live("click", -> 22 | page_template = $(this).closest(".endless_page_template") 23 | if not page_template.hasClass("endless_page_skip") 24 | data = "querystring_key=" + $(this).attr("rel").split(" ")[0] 25 | page_template.load $(this).attr("href"), data 26 | false 27 | ) 28 | 29 | on_scroll = -> 30 | delta = $(document).height() - $(window).height() - $(window).scrollTop() 31 | if delta <= 3200 then $("a.endless_more").click() 32 | $(window).on('scroll', on_scroll) 33 | on_scroll() 34 | setTimeout((-> $("a.endless_more").click()), 1000) 35 | 36 | 37 | # 38 | # SUBNAV 39 | # 40 | 41 | $ -> 42 | isFixed = false 43 | $nav = $("#subnav") 44 | if not $nav.length then return 45 | navTop = $nav.offset().top - $(".navbar").first().height() - 20 46 | on_scroll = -> 47 | $after = $("#subnav-after") 48 | scrollTop = $(window).scrollTop() 49 | if scrollTop >= navTop and not isFixed 50 | isFixed = true 51 | $nav.addClass("subnav-fixed") 52 | $after.addClass("subnav-after-fixed") 53 | else if scrollTop <= navTop and isFixed 54 | isFixed = false 55 | $nav.removeClass("subnav-fixed") 56 | $after.removeClass("subnav-after-fixed") 57 | $(window).on("scroll", on_scroll) 58 | on_scroll() 59 | 60 | 61 | # 62 | # TOOLTIPS, POPOVERS, HOVERS, ETC 63 | # 64 | 65 | $ -> 66 | on_items_added = -> 67 | $(".nav-tabs").button() 68 | $(".tool").removeClass("tool").tooltip( 69 | placement: "bottom" 70 | trigger: "hover" 71 | ) 72 | $(".pop").removeClass("pop").popover( 73 | placement: "bottom" 74 | trigger: "hover" 75 | ) 76 | $(document).on("items-added", on_items_added) 77 | on_items_added() 78 | 79 | $("body").delegate(".entry-thumb", "mouseover", -> 80 | $(this).addClass "entry-thumb-hover" 81 | ).delegate(".entry-thumb", "mouseout", -> 82 | $(this).removeClass "entry-thumb-hover" 83 | ) 84 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/common/ui_events.coffee: -------------------------------------------------------------------------------- 1 | ## Common UI helpers 2 | 3 | # Helper for hover items 4 | $(document).on('mouseover', '.hover-toggle', -> 5 | out = $(@).find('.show-on-mouseout') 6 | over = $(@).find('.show-on-mouseover') 7 | w = out.width() 8 | h = out.height() 9 | out.hide() 10 | over.width(w) 11 | over.height(h) 12 | over.show() 13 | ) 14 | $(document).on('mouseout', '.hover-toggle', -> 15 | $(@).find('.show-on-mouseout').show() 16 | $(@).find('.show-on-mouseover').hide() 17 | ) 18 | 19 | # Speedup for categories pages 20 | do -> 21 | handle_nav = (id) -> 22 | $("div##{id}").on('click', 'li > a', -> 23 | $('.loading-spinner').remove() 24 | $("div##{id} li.active").removeClass('active') 25 | $(@).closest('li').addClass('active') 26 | $(@).append(' ') 27 | timer = setTimeout(( => 28 | $('div#content').html('') 29 | ), 1000) 30 | window.on('beforeunload', -> 31 | clearTimeout(timer) 32 | null 33 | ) 34 | ) 35 | 36 | handle_nav('subnav') 37 | handle_nav('sidenav') 38 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/common/util.coffee: -------------------------------------------------------------------------------- 1 | # polygon fill colors 2 | POLYGON_COLORS = [ 3 | '#5D8AA8', '#3B7A57', '#915C83', '#A52A2A', '#FFE135', 4 | '#2E5894', '#3D2B1F', '#FE6F5E', '#ACE5EE', '#006A4E', 5 | '#873260', '#CD7F32', '#BD33A4', '#1E4D2B', '#DE6FA1', 6 | '#965A3E', '#002E63', '#FF3800', '#007BBB', '#6F4E37', 7 | '#0F4D92', '#9F1D35', '#B78727', '#8878C3', '#30D5C8', 8 | '#417DC1', '#FF6347' 9 | ] 10 | 11 | # sets the button state 12 | set_btn_enabled = (selector, enabled = true) -> 13 | #console.log "set_btn_enabled(#{selector}, #{enabled})" 14 | if enabled 15 | $(selector).removeAttr('disabled') 16 | else 17 | $(selector).attr('disabled', 'disabled') 18 | 19 | 20 | select_image_url = (urls, size) -> 21 | best_k = 65536 22 | best_v = urls.orig 23 | for k, v of urls when k != 'orig' 24 | k = Number(k) 25 | if k >= size and k < best_k 26 | best_k = k 27 | best_v = v 28 | #console.log "size: #{size}, best_k: #{best_k}" 29 | return best_v 30 | 31 | # add a GET parameter to the URL to ensure that we aren't served a cached 32 | # non-CORS version 33 | get_cors_url = (url) -> 34 | if url.indexOf('?') == -1 35 | url + '?origin=' + window.location.host 36 | else 37 | if url.indexOf("?origin=") == -1 and url.indexOf("&origin=") == -1 38 | url + '&origin=' + window.location.host 39 | else 40 | url 41 | 42 | # load a cross-origin image 43 | load_cors_image = (url, onload=null) -> 44 | img = new Image() 45 | img.crossOrigin = '' 46 | if onload 47 | img.onload = onload 48 | img.src = get_cors_url(url) 49 | return img 50 | 51 | # compute width, height so that obj fills a box. 52 | # the object is only blown up by at most max_scale. 53 | # there is no limit to how much it must be shrunk to fit. 54 | compute_dimensions = (obj, bbox, max_scale = 2) -> 55 | scale_x = bbox.width / obj.width 56 | scale_y = bbox.height / obj.height 57 | if scale_x < scale_y 58 | if scale_x < max_scale 59 | return { 60 | width: bbox.width 61 | height: obj.height * scale_x 62 | scale: scale_x 63 | } 64 | else 65 | if scale_y < max_scale 66 | return { 67 | width: obj.width * scale_y 68 | height: bbox.height 69 | scale: scale_y 70 | } 71 | 72 | return { 73 | width: obj.width * max_scale 74 | height: obj.height * max_scale 75 | scale: max_scale 76 | } 77 | 78 | # stops an event from propagating 79 | stop_event = (e) -> 80 | e ?= window.event 81 | e.stopPropagation?() 82 | e.preventDefault?() 83 | e.cancelBubble = true 84 | e.returnValue = false 85 | e.cancel = true 86 | false 87 | 88 | 89 | # from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript 90 | # assumes r, g, b are in [0, 255] and h, s, v are in [0, 1] 91 | rgb_to_hsv = (r, g, b) -> 92 | r = r / 255 93 | g = g / 255 94 | b = b / 255 95 | 96 | max = Math.max(r, g, b) 97 | min = Math.min(r, g, b) 98 | v = max 99 | d = max - min 100 | s = (if max is 0 then 0 else d / max) 101 | if max is min 102 | h = 0 # achromatic 103 | else 104 | switch max 105 | when r 106 | h = (g - b) / d + ((if g < b then 6 else 0)) 107 | when g 108 | h = (b - r) / d + 2 109 | when b 110 | h = (r - g) / d + 4 111 | h /= 6 112 | [h, s, v] 113 | 114 | # from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript 115 | # assumes r, g, b are in [0, 255] and h, s, v are in [0, 1] 116 | hsv_to_rgb = (h, s, v) -> 117 | i = Math.floor(h * 6) 118 | f = h * 6 - i 119 | p = v * (1 - s) 120 | q = v * (1 - f * s) 121 | t = v * (1 - (1 - f) * s) 122 | switch i % 6 123 | when 0 124 | r = v 125 | g = t 126 | b = p 127 | when 1 128 | r = q 129 | g = v 130 | b = p 131 | when 2 132 | r = p 133 | g = v 134 | b = t 135 | when 3 136 | r = p 137 | g = q 138 | b = v 139 | when 4 140 | r = t 141 | g = p 142 | b = v 143 | when 5 144 | r = v 145 | g = p 146 | b = q 147 | [r * 255, g * 255, b * 255] 148 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/lib/csrf.js: -------------------------------------------------------------------------------- 1 | // see https://docs.djangoproject.com/en/dev/ref/contrib/csrf/ 2 | function getCookie(name) { 3 | var cookieValue = null; 4 | if (document.cookie && document.cookie != '') { 5 | var cookies = document.cookie.split(';'); 6 | for (var i = 0; i < cookies.length; i++) { 7 | var cookie = jQuery.trim(cookies[i]); 8 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 9 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 10 | break; 11 | } 12 | } 13 | } 14 | return cookieValue; 15 | } 16 | var csrftoken = getCookie('csrftoken'); 17 | function csrfSafeMethod(method) { 18 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 19 | } 20 | $.ajaxSetup({ 21 | crossDomain: false, 22 | beforeSend: function(xhr, settings) { 23 | if (!csrfSafeMethod(settings.type)) { 24 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/lib/delaunay.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Delaunay triangulation 3 | * From: https://github.com/ironwallaby/delaunay 4 | */ 5 | function DelaunayTriangle(a, b, c) { 6 | this.a = a; 7 | this.b = b; 8 | this.c = c; 9 | 10 | var A = b.x - a.x, 11 | B = b.y - a.y, 12 | C = c.x - a.x, 13 | D = c.y - a.y, 14 | E = A * (a.x + b.x) + B * (a.y + b.y), 15 | F = C * (a.x + c.x) + D * (a.y + c.y), 16 | G = 2 * (A * (c.y - b.y) - B * (c.x - b.x)), 17 | minx, miny, dx, dy; 18 | 19 | /* If the points of the triangle are collinear, then just find the 20 | * extremes and use the midpoint as the center of the circumcircle. */ 21 | if(Math.abs(G) < 0.000001) { 22 | minx = Math.min(a.x, b.x, c.x); 23 | miny = Math.min(a.y, b.y, c.y); 24 | dx = (Math.max(a.x, b.x, c.x) - minx) * 0.5; 25 | dy = (Math.max(a.y, b.y, c.y) - miny) * 0.5; 26 | 27 | this.x = minx + dx; 28 | this.y = miny + dy; 29 | this.r = dx * dx + dy * dy; 30 | } 31 | 32 | else { 33 | this.x = (D*E - B*F) / G; 34 | this.y = (A*F - C*E) / G; 35 | dx = this.x - a.x; 36 | dy = this.y - a.y; 37 | this.r = dx * dx + dy * dy; 38 | } 39 | } 40 | 41 | DelaunayTriangle.prototype.midpoint = function() { 42 | return { 43 | x: (this.a.x + this.b.x + this.c.x) / 3, 44 | y: (this.a.y + this.b.y + this.c.y) / 3 45 | }; 46 | }; 47 | 48 | DelaunayTriangle.prototype.area = function() { 49 | return 0.5 * Math.abs( 50 | -this.b.x * this.a.y + this.c.x * this.a.y + this.a.x * this.b.y - 51 | this.c.x * this.b.y - this.a.x * this.c.y + this.b.x * this.c.y 52 | ); 53 | }; 54 | 55 | function delaunay_dedup(edges) { 56 | var j = edges.length, 57 | a, b, i, m, n; 58 | 59 | outer: while(j) { 60 | b = edges[--j]; 61 | a = edges[--j]; 62 | i = j; 63 | while(i) { 64 | n = edges[--i]; 65 | m = edges[--i]; 66 | if((a === m && b === n) || (a === n && b === m)) { 67 | edges.splice(j, 2); 68 | edges.splice(i, 2); 69 | j -= 2; 70 | continue outer; 71 | } 72 | } 73 | } 74 | } 75 | 76 | function delaunay_triangulate(vertices) { 77 | /* Bail if there aren't enough vertices to form any triangles. */ 78 | if(vertices.length < 3) 79 | return []; 80 | 81 | /* Ensure the vertex array is in order of descending X coordinate 82 | * (which is needed to ensure a subquadratic runtime), and then find 83 | * the bounding box around the points. */ 84 | vertices.sort(function(a, b) { return b.x - a.x; }); 85 | 86 | var i = vertices.length - 1, 87 | xmin = vertices[i].x, 88 | xmax = vertices[0].x, 89 | ymin = vertices[i].y, 90 | ymax = ymin; 91 | 92 | while(i--) { 93 | if(vertices[i].y < ymin) ymin = vertices[i].y; 94 | if(vertices[i].y > ymax) ymax = vertices[i].y; 95 | } 96 | 97 | /* Find a supertriangle, which is a triangle that surrounds all the 98 | * vertices. This is used like something of a sentinel value to remove 99 | * cases in the main algorithm, and is removed before we return any 100 | * results. 101 | * 102 | * Once found, put it in the "open" list. (The "open" list is for 103 | * triangles who may still need to be considered; the "closed" list is 104 | * for triangles which do not.) */ 105 | var dx = xmax - xmin, 106 | dy = ymax - ymin, 107 | dmax = (dx > dy) ? dx : dy, 108 | xmid = (xmax + xmin) * 0.5, 109 | ymid = (ymax + ymin) * 0.5, 110 | open = [ 111 | new DelaunayTriangle( 112 | {x: xmid - 20 * dmax, y: ymid - dmax, __sentinel: true}, 113 | {x: xmid , y: ymid + 20 * dmax, __sentinel: true}, 114 | {x: xmid + 20 * dmax, y: ymid - dmax, __sentinel: true} 115 | ) 116 | ], 117 | closed = [], 118 | edges = [], 119 | j, a, b; 120 | 121 | /* Incrementally add each vertex to the mesh. */ 122 | i = vertices.length; 123 | while(i--) { 124 | /* For each open triangle, check to see if the current point is 125 | * inside it's circumcircle. If it is, remove the triangle and add 126 | * it's edges to an edge list. */ 127 | edges.length = 0; 128 | j = open.length; 129 | while(j--) { 130 | /* If this point is to the right of this triangle's circumcircle, 131 | * then this triangle should never get checked again. Remove it 132 | * from the open list, add it to the closed list, and skip. */ 133 | dx = vertices[i].x - open[j].x; 134 | if(dx > 0 && dx * dx > open[j].r) { 135 | closed.push(open[j]); 136 | open.splice(j, 1); 137 | continue; 138 | } 139 | 140 | /* If not, skip this triangle. */ 141 | dy = vertices[i].y - open[j].y; 142 | if(dx * dx + dy * dy > open[j].r) 143 | continue; 144 | 145 | /* Remove the triangle and add it's edges to the edge list. */ 146 | edges.push( 147 | open[j].a, open[j].b, 148 | open[j].b, open[j].c, 149 | open[j].c, open[j].a 150 | ); 151 | open.splice(j, 1); 152 | } 153 | 154 | /* Remove any doubled edges. */ 155 | delaunay_dedup(edges); 156 | 157 | /* Add a new triangle for each edge. */ 158 | j = edges.length; 159 | while(j) { 160 | b = edges[--j]; 161 | a = edges[--j]; 162 | open.push(new DelaunayTriangle(a, b, vertices[i])); 163 | } 164 | } 165 | 166 | /* Copy any remaining open triangles to the closed list, and then 167 | * remove any triangles that share a vertex with the supertriangle. */ 168 | Array.prototype.push.apply(closed, open); 169 | 170 | i = closed.length; 171 | while(i--) 172 | if(closed[i].a.__sentinel || 173 | closed[i].b.__sentinel || 174 | closed[i].c.__sentinel) 175 | closed.splice(i, 1); 176 | 177 | /* Yay, we're done! */ 178 | return closed; 179 | } 180 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/lib/jquery.hotkeys.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Hotkeys Plugin 3 | * Copyright 2010, John Resig 4 | * Dual licensed under the MIT or GPL Version 2 licenses. 5 | * 6 | * Based upon the plugin by Tzury Bar Yochay: 7 | * http://github.com/tzuryby/hotkeys 8 | * 9 | * Original idea by: 10 | * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ 11 | */ 12 | 13 | (function(jQuery){ 14 | 15 | jQuery.hotkeys = { 16 | version: "0.8+", 17 | 18 | specialKeys: { 19 | 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 20 | 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 21 | 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 22 | 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 23 | 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 24 | 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 25 | 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 188: ",", 190: ".", 26 | 191: "/", 224: "meta" 27 | }, 28 | 29 | shiftNums: { 30 | "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", 31 | "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", 32 | ".": ">", "/": "?", "\\": "|" 33 | } 34 | }; 35 | 36 | function keyHandler( handleObj ) { 37 | 38 | var origHandler = handleObj.handler, 39 | //use namespace as keys so it works with event delegation as well 40 | //will also allow removing listeners of a specific key combination 41 | //and support data objects 42 | keys = (handleObj.namespace || "").toLowerCase().split(" "); 43 | keys = jQuery.map(keys, function(key) { return key.split("."); }); 44 | 45 | //no need to modify handler if no keys specified 46 | if (keys.length === 1 && (keys[0] === "" || keys[0] === "autocomplete")) { 47 | return; 48 | } 49 | 50 | handleObj.handler = function( event ) { 51 | // Don't fire in text-accepting inputs that we didn't directly bind to 52 | // important to note that $.fn.prop is only available on jquery 1.6+ 53 | if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || 54 | event.target.type === "text" || $(event.target).prop('contenteditable') == 'true' )) { 55 | return; 56 | } 57 | 58 | // Keypress represents characters, not special keys 59 | var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], 60 | character = String.fromCharCode( event.which ).toLowerCase(), 61 | key, modif = "", possible = {}; 62 | 63 | // check combinations (alt|ctrl|shift+anything) 64 | if ( event.altKey && special !== "alt" ) { 65 | modif += "alt_"; 66 | } 67 | 68 | if ( event.ctrlKey && special !== "ctrl" ) { 69 | modif += "ctrl_"; 70 | } 71 | 72 | // TODO: Need to make sure this works consistently across platforms 73 | if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { 74 | modif += "meta_"; 75 | } 76 | 77 | if ( event.shiftKey && special !== "shift" ) { 78 | modif += "shift_"; 79 | } 80 | 81 | if ( special ) { 82 | possible[ modif + special ] = true; 83 | 84 | } else { 85 | possible[ modif + character ] = true; 86 | possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; 87 | 88 | // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" 89 | if ( modif === "shift_" ) { 90 | possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; 91 | } 92 | } 93 | 94 | for ( var i = 0, l = keys.length; i < l; i++ ) { 95 | if ( possible[ keys[i] ] ) { 96 | return origHandler.apply( this, arguments ); 97 | } 98 | } 99 | }; 100 | } 101 | 102 | jQuery.each([ "keydown", "keyup", "keypress" ], function() { 103 | jQuery.event.special[ this ] = { add: keyHandler }; 104 | }); 105 | 106 | })( jQuery ); 107 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/lib/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | json2.js 3 | 2012-10-08 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, regexp: true */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | if (typeof JSON !== 'object') { 163 | JSON = {}; 164 | } 165 | 166 | (function () { 167 | 'use strict'; 168 | 169 | function f(n) { 170 | // Format integers to have at least two digits. 171 | return n < 10 ? '0' + n : n; 172 | } 173 | 174 | if (typeof Date.prototype.toJSON !== 'function') { 175 | 176 | Date.prototype.toJSON = function (key) { 177 | 178 | return isFinite(this.valueOf()) 179 | ? this.getUTCFullYear() + '-' + 180 | f(this.getUTCMonth() + 1) + '-' + 181 | f(this.getUTCDate()) + 'T' + 182 | f(this.getUTCHours()) + ':' + 183 | f(this.getUTCMinutes()) + ':' + 184 | f(this.getUTCSeconds()) + 'Z' 185 | : null; 186 | }; 187 | 188 | String.prototype.toJSON = 189 | Number.prototype.toJSON = 190 | Boolean.prototype.toJSON = function (key) { 191 | return this.valueOf(); 192 | }; 193 | } 194 | 195 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 196 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 197 | gap, 198 | indent, 199 | meta = { // table of character substitutions 200 | '\b': '\\b', 201 | '\t': '\\t', 202 | '\n': '\\n', 203 | '\f': '\\f', 204 | '\r': '\\r', 205 | '"' : '\\"', 206 | '\\': '\\\\' 207 | }, 208 | rep; 209 | 210 | 211 | function quote(string) { 212 | 213 | // If the string contains no control characters, no quote characters, and no 214 | // backslash characters, then we can safely slap some quotes around it. 215 | // Otherwise we must also replace the offending characters with safe escape 216 | // sequences. 217 | 218 | escapable.lastIndex = 0; 219 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 220 | var c = meta[a]; 221 | return typeof c === 'string' 222 | ? c 223 | : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 224 | }) + '"' : '"' + string + '"'; 225 | } 226 | 227 | 228 | function str(key, holder) { 229 | 230 | // Produce a string from holder[key]. 231 | 232 | var i, // The loop counter. 233 | k, // The member key. 234 | v, // The member value. 235 | length, 236 | mind = gap, 237 | partial, 238 | value = holder[key]; 239 | 240 | // If the value has a toJSON method, call it to obtain a replacement value. 241 | 242 | if (value && typeof value === 'object' && 243 | typeof value.toJSON === 'function') { 244 | value = value.toJSON(key); 245 | } 246 | 247 | // If we were called with a replacer function, then call the replacer to 248 | // obtain a replacement value. 249 | 250 | if (typeof rep === 'function') { 251 | value = rep.call(holder, key, value); 252 | } 253 | 254 | // What happens next depends on the value's type. 255 | 256 | switch (typeof value) { 257 | case 'string': 258 | return quote(value); 259 | 260 | case 'number': 261 | 262 | // JSON numbers must be finite. Encode non-finite numbers as null. 263 | 264 | return isFinite(value) ? String(value) : 'null'; 265 | 266 | case 'boolean': 267 | case 'null': 268 | 269 | // If the value is a boolean or null, convert it to a string. Note: 270 | // typeof null does not produce 'null'. The case is included here in 271 | // the remote chance that this gets fixed someday. 272 | 273 | return String(value); 274 | 275 | // If the type is 'object', we might be dealing with an object or an array or 276 | // null. 277 | 278 | case 'object': 279 | 280 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 281 | // so watch out for that case. 282 | 283 | if (!value) { 284 | return 'null'; 285 | } 286 | 287 | // Make an array to hold the partial results of stringifying this object value. 288 | 289 | gap += indent; 290 | partial = []; 291 | 292 | // Is the value an array? 293 | 294 | if (Object.prototype.toString.apply(value) === '[object Array]') { 295 | 296 | // The value is an array. Stringify every element. Use null as a placeholder 297 | // for non-JSON values. 298 | 299 | length = value.length; 300 | for (i = 0; i < length; i += 1) { 301 | partial[i] = str(i, value) || 'null'; 302 | } 303 | 304 | // Join all of the elements together, separated with commas, and wrap them in 305 | // brackets. 306 | 307 | v = partial.length === 0 308 | ? '[]' 309 | : gap 310 | ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' 311 | : '[' + partial.join(',') + ']'; 312 | gap = mind; 313 | return v; 314 | } 315 | 316 | // If the replacer is an array, use it to select the members to be stringified. 317 | 318 | if (rep && typeof rep === 'object') { 319 | length = rep.length; 320 | for (i = 0; i < length; i += 1) { 321 | if (typeof rep[i] === 'string') { 322 | k = rep[i]; 323 | v = str(k, value); 324 | if (v) { 325 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 326 | } 327 | } 328 | } 329 | } else { 330 | 331 | // Otherwise, iterate through all of the keys in the object. 332 | 333 | for (k in value) { 334 | if (Object.prototype.hasOwnProperty.call(value, k)) { 335 | v = str(k, value); 336 | if (v) { 337 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 338 | } 339 | } 340 | } 341 | } 342 | 343 | // Join all of the member texts together, separated with commas, 344 | // and wrap them in braces. 345 | 346 | v = partial.length === 0 347 | ? '{}' 348 | : gap 349 | ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' 350 | : '{' + partial.join(',') + '}'; 351 | gap = mind; 352 | return v; 353 | } 354 | } 355 | 356 | // If the JSON object does not yet have a stringify method, give it one. 357 | 358 | if (typeof JSON.stringify !== 'function') { 359 | JSON.stringify = function (value, replacer, space) { 360 | 361 | // The stringify method takes a value and an optional replacer, and an optional 362 | // space parameter, and returns a JSON text. The replacer can be a function 363 | // that can replace values, or an array of strings that will select the keys. 364 | // A default replacer method can be provided. Use of the space parameter can 365 | // produce text that is more easily readable. 366 | 367 | var i; 368 | gap = ''; 369 | indent = ''; 370 | 371 | // If the space parameter is a number, make an indent string containing that 372 | // many spaces. 373 | 374 | if (typeof space === 'number') { 375 | for (i = 0; i < space; i += 1) { 376 | indent += ' '; 377 | } 378 | 379 | // If the space parameter is a string, it will be used as the indent string. 380 | 381 | } else if (typeof space === 'string') { 382 | indent = space; 383 | } 384 | 385 | // If there is a replacer, it must be a function or an array. 386 | // Otherwise, throw an error. 387 | 388 | rep = replacer; 389 | if (replacer && typeof replacer !== 'function' && 390 | (typeof replacer !== 'object' || 391 | typeof replacer.length !== 'number')) { 392 | throw new Error('JSON.stringify'); 393 | } 394 | 395 | // Make a fake root object containing our value under the key of ''. 396 | // Return the result of stringifying the value. 397 | 398 | return str('', {'': value}); 399 | }; 400 | } 401 | 402 | 403 | // If the JSON object does not yet have a parse method, give it one. 404 | 405 | if (typeof JSON.parse !== 'function') { 406 | JSON.parse = function (text, reviver) { 407 | 408 | // The parse method takes a text and an optional reviver function, and returns 409 | // a JavaScript value if the text is a valid JSON text. 410 | 411 | var j; 412 | 413 | function walk(holder, key) { 414 | 415 | // The walk method is used to recursively walk the resulting structure so 416 | // that modifications can be made. 417 | 418 | var k, v, value = holder[key]; 419 | if (value && typeof value === 'object') { 420 | for (k in value) { 421 | if (Object.prototype.hasOwnProperty.call(value, k)) { 422 | v = walk(value, k); 423 | if (v !== undefined) { 424 | value[k] = v; 425 | } else { 426 | delete value[k]; 427 | } 428 | } 429 | } 430 | } 431 | return reviver.call(holder, key, value); 432 | } 433 | 434 | 435 | // Parsing happens in four stages. In the first stage, we replace certain 436 | // Unicode characters with escape sequences. JavaScript handles many characters 437 | // incorrectly, either silently deleting them, or treating them as line endings. 438 | 439 | text = String(text); 440 | cx.lastIndex = 0; 441 | if (cx.test(text)) { 442 | text = text.replace(cx, function (a) { 443 | return '\\u' + 444 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 445 | }); 446 | } 447 | 448 | // In the second stage, we run the text against regular expressions that look 449 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 450 | // because they can cause invocation, and '=' because it can cause mutation. 451 | // But just to be safe, we want to reject all unexpected forms. 452 | 453 | // We split the second stage into 4 regexp operations in order to work around 454 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 455 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 456 | // replace all simple value tokens with ']' characters. Third, we delete all 457 | // open brackets that follow a colon or comma or that begin the text. Finally, 458 | // we look to see that the remaining characters are only whitespace or ']' or 459 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 460 | 461 | if (/^[\],:{}\s]*$/ 462 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 463 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 464 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 465 | 466 | // In the third stage we use the eval function to compile the text into a 467 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 468 | // in JavaScript: it can begin a block or an object literal. We wrap the text 469 | // in parens to eliminate the ambiguity. 470 | 471 | j = eval('(' + text + ')'); 472 | 473 | // In the optional fourth stage, we recursively walk the new structure, passing 474 | // each name/value pair to a reviver function for possible transformation. 475 | 476 | return typeof reviver === 'function' 477 | ? walk({'': j}, '') 478 | : j; 479 | } 480 | 481 | // If the text is not JSON parseable, then a SyntaxError is thrown. 482 | 483 | throw new SyntaxError('JSON.parse'); 484 | }; 485 | } 486 | }()); 487 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/mturk/mt_segment_material.coffee: -------------------------------------------------------------------------------- 1 | $( -> 2 | template_args.width = $('#mt-container').width() - 4 3 | template_args.height = $(window).height() - $('#mt-top-nohover').height() - 16 4 | template_args.container_id = 'mt-container' 5 | $('#poly-container').width(template_args.width).height(template_args.height) 6 | window.controller_ui = new ControllerUI(template_args) 7 | ) 8 | 9 | btn_submit = -> 10 | window.mt_submit(window.controller_ui.get_submit_data) 11 | 12 | # wait for everything to load before allowing submit 13 | $(window).on('load', -> 14 | $('#btn-submit').on('click', btn_submit) 15 | ) 16 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/mturk/mt_submit.coffee: -------------------------------------------------------------------------------- 1 | # Handles submitting of tasks for mturk tasks 2 | # Required scripts: 3 | # get_url_params.coffee 4 | # Required templates: 5 | # modal_loading.html 6 | # modal_error.html 7 | 8 | window.load_start = Date.now() 9 | $(window).on('load', -> 10 | window.time_load_ms = +(Date.now() - window.load_start) 11 | ) 12 | 13 | $( -> 14 | mt_submit_ready = false 15 | 16 | # Ready to submit with the provided data 17 | window.mt_submit_ready = (data_callback) -> 18 | if not mt_submit_ready 19 | mt_submit_ready = true 20 | btn = $('#btn-submit').removeAttr('disabled') 21 | if data_callback? 22 | btn.on('click', window.mt_submit(data_callback)) 23 | 24 | # No longer ready to submit 25 | window.mt_submit_not_ready = (disable=true) -> 26 | if mt_submit_ready 27 | mt_submit_ready = false 28 | if disable then $('#btn-submit').attr('disabled', 'disabled').off('click') 29 | 30 | # Submit from javascript 31 | window.mt_submit = (data_callback) -> 32 | mt_submit_ready = true 33 | do_submit = mt_submit_impl(data_callback) 34 | 35 | if (window.ask_for_feedback == true and window.show_modal_feedback? and window.feedback_bonus?) 36 | window.show_modal_feedback( 37 | 'Thank you!', 38 | ("We will give a bonus of #{window.feedback_bonus} if you help us improve " + 39 | 'the task and answer these questions.
' + 40 | 'If you don\'t want to answer, just click "Submit".
'), 41 | do_submit 42 | ) 43 | else 44 | do_submit() 45 | 46 | # Submit a partially completed task 47 | window.mt_submit_partial = (data) -> 48 | console.log "partial submit data:" 49 | console.log data 50 | $.ajax( 51 | type: 'POST' 52 | url: window.location.href 53 | data: $.extend(true, { 54 | partial: true, 55 | screen_width: screen.width, 56 | screen_height: screen.height, 57 | time_load_ms: window.time_load_ms 58 | }, data) 59 | contentType: 'application/x-www-form-urlencoded; charset=UTF-8' 60 | dataType: 'json' 61 | success: (data, status, jqxhr) -> 62 | console.log "partial submit success: data:" 63 | console.log data 64 | error: -> 65 | console.log "partial submit error" 66 | ) 67 | 68 | # ===== private methods ===== 69 | 70 | mt_submit_error = (msg) -> 71 | hide_modal_loading( -> window.show_modal_error(msg) ) 72 | 73 | mt_submit_impl = (data_callback) -> -> 74 | if not mt_submit_ready then return 75 | 76 | data = data_callback() 77 | feedback = window.get_modal_feedback?() if window.ask_for_feedback 78 | if feedback? and not $.isEmptyObject(feedback) 79 | data.feedback = JSON.stringify(feedback) 80 | 81 | console.log "submit data:" 82 | console.log data 83 | 84 | window.show_modal_loading("Submitting...", 0) 85 | $.ajax( 86 | type: 'POST' 87 | url: window.location.href 88 | data: $.extend(true, { 89 | screen_width: screen.width 90 | screen_height: screen.height 91 | time_load_ms: window.time_load_ms 92 | }, data) 93 | contentType: 'application/x-www-form-urlencoded; charset=UTF-8' 94 | dataType: 'json' 95 | success: (data, status, jqxhr) -> 96 | console.log "success: data:" 97 | console.log data 98 | 99 | host = decodeURIComponent($(document).getUrlParam('turkSubmitTo')) 100 | console.log "host: #{host}" 101 | 102 | if data.result == "success" 103 | new_url = "#{host}/mturk/externalSubmit#{window.location.search}" 104 | console.log "success: redirecting to #{new_url}" 105 | window.location = new_url 106 | setInterval((-> window.location = new_url), 5000) 107 | else if data.result == "error" 108 | mt_submit_error("There was an error contacting the server; try submitting again after a few seconds... (#{data.message})") 109 | else 110 | mt_submit_error("There was an error contacting the server; try submitting again after a few seconds...") 111 | 112 | error: -> 113 | mt_submit_error("Could not connect to the server; try submitting again after a few seconds...") 114 | ) 115 | ) 116 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/poly/actions.coffee: -------------------------------------------------------------------------------- 1 | # Control encapsulated into UndoableEvent objects 2 | class UEToggleMode extends UndoableEvent 3 | constructor: -> 4 | @open_points = null 5 | @sel_poly_id = null 6 | 7 | run: (ui) -> 8 | if ui.s.draw_mode 9 | if ui.s.open_poly? 10 | @open_points = ui.s.open_poly.poly.clone_points() 11 | else 12 | @sel_poly_id = ui.s.sel_poly?.id 13 | ui.s.toggle_mode() 14 | ui.s.update_buttons() 15 | 16 | redo: (ui) -> 17 | ui.s.toggle_mode() 18 | ui.s.update_buttons() 19 | 20 | undo: (ui) -> 21 | ui.s.toggle_mode() 22 | if ui.s.draw_mode 23 | if @open_points? 24 | ui.s.create_poly(@open_points)?.update(ui) 25 | else 26 | if @sel_poly_id? 27 | ui.s.select_poly(ui, @sel_poly_id)?.update(ui) 28 | 29 | entry: -> { name: "UEToggleMode" } 30 | 31 | class UERemoveOpenPoly extends UndoableEvent 32 | constructor: -> 33 | @open_points = null 34 | 35 | run: (ui) -> 36 | if ui.s.open_poly? 37 | @open_points = ui.s.open_poly.poly.clone_points() 38 | ui.s.remove_open_poly() 39 | ui.s.update_buttons() 40 | 41 | redo: (ui) -> 42 | ui.s.remove_open_poly() 43 | ui.s.update_buttons() 44 | 45 | undo: (ui) -> 46 | if @open_points? 47 | ui.s.create_poly(@open_points)?.update(ui) 48 | 49 | entry: -> { name: "UERemoveOpenPoly" } 50 | 51 | class UEPushPoint extends UndoableEvent 52 | constructor: (p) -> @p = clone_pt(p) 53 | run: (ui) -> ui.s.push_point(@p)?.update(ui) 54 | undo: (ui) -> ui.s.pop_point()?.update(ui) 55 | entry: -> { name: "UEPushPoint", args: { p: @p } } 56 | 57 | class UECreatePolygon extends UndoableEvent 58 | constructor: (p) -> @p = clone_pt(p) 59 | run: (ui) -> ui.s.create_poly([@p])?.update(ui) 60 | undo: (ui) -> ui.s.remove_open_poly()?.update(ui) 61 | entry: -> { name: "UECreatePolygon", args: { p: @p } } 62 | 63 | class UEClosePolygon extends UndoableEvent 64 | run: (ui) -> ui.s.close_poly()?.update(ui) 65 | undo: (ui) -> ui.s.unclose_poly()?.update(ui) 66 | entry: -> { name: "UEClosePolygon" } 67 | 68 | class UESelectPolygon extends UndoableEvent 69 | constructor: (@id) -> 70 | run: (ui) -> 71 | @sel_poly_id = ui.s.sel_poly?.id 72 | ui.s.select_poly(ui, @id) 73 | undo: (ui) -> 74 | if @sel_poly_id? 75 | ui.s.select_poly(ui, @sel_poly_id) 76 | else 77 | ui.s.unselect_poly() 78 | redo: (ui) -> 79 | ui.s.select_poly(ui, @id) 80 | entry: -> { name: "UESelectPolygon", args: { id: @id } } 81 | 82 | class UEUnselectPolygon extends UndoableEvent 83 | constructor: () -> 84 | run: (ui) -> 85 | @sel_poly_id = ui.s.sel_poly?.id 86 | ui.s.unselect_poly() 87 | undo: (ui) -> 88 | if @sel_poly_id? 89 | ui.s.select_poly(ui, @sel_poly_id) 90 | redo: (ui) -> 91 | ui.s.unselect_poly() 92 | entry: -> { name: "UEUnselectPolygon" } 93 | 94 | class UEDeletePolygon extends UndoableEvent 95 | run: (ui) -> 96 | @points = ui.s.sel_poly.poly.clone_points() 97 | @time_ms = ui.s.sel_poly.time_ms 98 | @time_active_ms = ui.s.sel_poly.time_active_ms 99 | @sel_poly_id = ui.s.sel_poly.id 100 | ui.s.delete_sel_poly() 101 | for p,i in ui.s.closed_polys 102 | p.id = i 103 | p.update(ui) 104 | undo: (ui) -> 105 | ui.s.insert_closed_poly(@points, @sel_poly_id, 106 | @time_ms, @time_active_ms) 107 | for p,i in ui.s.closed_polys 108 | p.id = i 109 | p.update(ui) 110 | ui.s.select_poly(ui, @sel_poly_id) 111 | entry: -> { name: "UEDeletePolygon" } 112 | 113 | class UEDragVertex extends UndoableEvent 114 | constructor: (@i, p0, p1) -> 115 | @p0 = clone_pt(p0) 116 | @p1 = clone_pt(p1) 117 | run: (ui) -> 118 | sp = ui.s.sel_poly 119 | sp.poly.set_point(@i, @p1) 120 | sp.anchors[@i].setPosition(@p1.x, @p1.y) 121 | sp.update(ui) 122 | undo: (ui) -> 123 | sp = ui.s.sel_poly 124 | sp.poly.set_point(@i, @p0) 125 | sp.anchors[@i].setPosition(@p0.x, @p0.y) 126 | sp.update(ui) 127 | entry: -> { name: "UEDragVertex", args: { i: @i, p0: @p0, p1: @p1 } } 128 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/poly/controller_state.coffee: -------------------------------------------------------------------------------- 1 | # Holds UI state; when something is modified, any dirty items are returned. 2 | # an instance of this is held by ControllerUI 3 | class ControllerState 4 | constructor: (@ui, args) -> 5 | @loading = true 6 | 7 | # save id for get_submit_data 8 | @photo_id = args.photo_id if args.photo_id? 9 | 10 | # action log and undo/redo 11 | @undoredo = new UndoRedo(@ui, args) 12 | @log = new ActionLog() 13 | @log.action($.extend(true, {name:'init'}, args)) 14 | 15 | # true: draw, false: adjust 16 | @draw_mode = true 17 | 18 | # enabled when shift is held to drag the viewport around 19 | @panning = false 20 | 21 | # mouse state (w.r.t document page) 22 | @mousedown = false 23 | @mousepos = null 24 | 25 | # if true, the user was automagically zoomed in 26 | # after clicking on a polygon 27 | @zoomed_adjust = false 28 | 29 | # if nonzero, a modal is visible 30 | @modal_count = 0 31 | 32 | # buttons 33 | @btn_draw = if args.btn_draw? then args.btn_draw else '#btn-draw' 34 | @btn_edit = if args.btn_edit? then args.btn_edit else '#btn-edit' 35 | @btn_close = if args.btn_close? then args.btn_close else '#btn-close' 36 | @btn_submit = if args.btn_submit? then args.btn_submit else '#btn-submit' 37 | @btn_delete = if args.btn_delete? then args.btn_delete else '#btn-delete' 38 | @btn_zoom_reset = if args.btn_zoom_reset? then args.btn_zoom_reset else '#btn-zoom-reset' 39 | 40 | # gui elements 41 | @stage_ui = new StageUI(@ui, args) 42 | @closed_polys = [] # PolygonUI elements 43 | @open_poly = null 44 | @sel_poly = null 45 | @saved_point = null # start of drag 46 | 47 | # return data that will be submitted 48 | get_submit_data: => 49 | results_list = [] 50 | for poly in @closed_polys 51 | points_scaled = [] 52 | for p in poly.poly.points 53 | points_scaled.push(Math.max(0, Math.min(1, 54 | p.x / @stage_ui.size.width))) 55 | points_scaled.push(Math.max(0, Math.min(1, 56 | p.y / @stage_ui.size.height))) 57 | results_list.push(points_scaled) 58 | 59 | results = {} 60 | time_ms = {} 61 | time_active_ms = {} 62 | results[@photo_id] = results_list 63 | time_ms[@photo_id] = (p.time_ms for p in @closed_polys) 64 | time_active_ms[@photo_id] = (p.time_active_ms for p in @closed_polys) 65 | 66 | version: '1.0' 67 | results: JSON.stringify(results) 68 | time_ms: JSON.stringify(time_ms) 69 | time_active_ms: JSON.stringify(time_active_ms) 70 | action_log: @log.get_submit_data() 71 | 72 | # redraw the stage 73 | draw: => @stage_ui.draw() 74 | 75 | # get mouse position (after taking zoom into account) 76 | mouse_pos: => @stage_ui.mouse_pos() 77 | 78 | # zoom in/out by delta 79 | zoom_delta: (delta) => 80 | @zoomed_adjust = false 81 | @stage_ui.zoom_delta(delta) 82 | @update_buttons() 83 | @update_zoom() 84 | 85 | # reset to 1.0 zoom 86 | zoom_reset: => 87 | @zoomed_adjust = false 88 | @stage_ui.zoom_reset() 89 | @update_buttons() 90 | @update_zoom() 91 | 92 | update_zoom: (redraw=true) => 93 | inv_f = 1.0 / @stage_ui.get_zoom_factor() 94 | for poly in @closed_polys 95 | poly.update_zoom(@ui, inv_f, false) 96 | @open_poly?.update_zoom(@ui, inv_f, false) 97 | @sel_poly?.add_anchors(@ui) 98 | if redraw 99 | @draw() 100 | 101 | get_zoom_factor: => 102 | @stage_ui.get_zoom_factor() 103 | 104 | translate_delta: (x, y) => 105 | @stage_ui.translate_delta(x, y) 106 | 107 | # add a point to the current polygon at point p 108 | push_point: (p) -> 109 | @open_poly?.poly.push_point(p) 110 | @open_poly 111 | 112 | # delete the last point on the open polygon 113 | pop_point: -> 114 | @open_poly?.poly.pop_point() 115 | @open_poly 116 | 117 | # get the location of point i on polygon id 118 | get_pt: (id, i) -> 119 | @get_poly(id)?.poly.get_pt(i) 120 | 121 | # add an open polygon using points 122 | create_poly: (points) -> 123 | console.log 'create_poly:' 124 | console.log points 125 | @open_poly.remove_all() if @open_poly? 126 | poly = new Polygon(points) 127 | @open_poly = new PolygonUI(@closed_polys.length, poly, @stage_ui) 128 | @open_poly.timer = new ActiveTimer() 129 | @open_poly.timer.start() 130 | @update_buttons() 131 | @open_poly 132 | 133 | # add a closed polygon in the specified slot 134 | insert_closed_poly: (points, id, time_ms, time_active_ms) -> 135 | poly = new Polygon(points) 136 | poly.close() 137 | closed_poly = new PolygonUI(id, poly, @stage_ui) 138 | closed_poly.time_ms = time_ms 139 | closed_poly.time_active_ms = time_active_ms 140 | @closed_polys.splice(id, 0, closed_poly) 141 | @update_buttons() 142 | closed_poly 143 | 144 | # return polygon id 145 | get_poly: (id) -> 146 | for p in @closed_polys 147 | if p.id == id 148 | return p 149 | return null 150 | 151 | # return number of polygons 152 | num_polys: -> @closed_polys.length 153 | 154 | # delete the open polygon 155 | remove_open_poly: -> 156 | @open_poly?.remove_all() 157 | @open_poly = null 158 | 159 | # close the open polygon 160 | close_poly: -> 161 | if @open_poly? 162 | @open_poly.time_ms = @open_poly.timer.time_ms() 163 | @open_poly.time_active_ms = @open_poly.timer.time_active_ms() 164 | poly = @open_poly 165 | @open_poly.poly.close() 166 | @closed_polys.push(@open_poly) 167 | @open_poly = null 168 | @update_buttons() 169 | poly 170 | else 171 | null 172 | 173 | can_close: => 174 | if not @loading and @open_poly? 175 | if (window.min_vertices? and @open_poly.poly.num_points() < window.min_vertices) 176 | return false 177 | @open_poly.poly.can_close() 178 | else 179 | false 180 | 181 | # re-open the most recently closed polygon 182 | unclose_poly: -> 183 | if @draw_mode and not @open_poly? and @num_polys() > 0 184 | @open_poly = @closed_polys.pop() 185 | @open_poly.poly.unclose() 186 | @update_buttons() 187 | @open_poly 188 | else 189 | null 190 | 191 | # true if the selected polygon can be deleted 192 | can_delete_sel: -> 193 | not @loading and not @draw_mode and @sel_poly? and @num_polys() > 0 194 | 195 | # delete the currently selected polygon 196 | delete_sel_poly: -> 197 | if @can_delete_sel() 198 | for p,i in @closed_polys 199 | if p.id == @sel_poly.id 200 | @closed_polys.splice(i, 1) 201 | @sel_poly?.remove_all() 202 | @sel_poly = null 203 | break 204 | if @zoomed_adjust then @zoom_reset() 205 | @update_buttons() 206 | null 207 | else 208 | null 209 | 210 | # select the specified polygon 211 | select_poly: (ui, id) -> 212 | if @draw_mode then return 213 | if @sel_poly? 214 | if @sel_poly.id == id then return 215 | @unselect_poly(false) 216 | @sel_poly = @get_poly(id) 217 | @sel_poly.add_anchors(ui) 218 | @stage_ui.zoom_box(@sel_poly.poly.get_aabb()) 219 | @zoomed_adjust = true 220 | @update_buttons() 221 | @update_zoom(false) 222 | @draw() 223 | @sel_poly 224 | 225 | unselect_poly: (reset_zoomed_adjust=true) => 226 | @sel_poly?.remove_anchors() 227 | @sel_poly = null 228 | if reset_zoomed_adjust and @zoomed_adjust 229 | @zoom_reset() 230 | @update_buttons() 231 | null 232 | 233 | toggle_mode: -> 234 | @draw_mode = not @draw_mode 235 | if @draw_mode 236 | if @sel_poly? 237 | @unselect_poly() 238 | else 239 | if @open_poly? 240 | @remove_open_poly() 241 | @update_buttons() 242 | 243 | disable_buttons: -> 244 | set_btn_enabled(@btn_draw, false) 245 | set_btn_enabled(@btn_edit, false) 246 | set_btn_enabled(@btn_close, false) 247 | set_btn_enabled(@btn_submit, false) 248 | 249 | # update cursor only 250 | update_cursor: -> 251 | if @panning 252 | if $.browser.webkit 253 | if @mousedown 254 | $('canvas').css('cursor', '-webkit-grabing') 255 | else 256 | $('canvas').css('cursor', '-webkit-grab') 257 | else 258 | if @mousedown 259 | $('canvas').css('cursor', '-moz-grabing') 260 | else 261 | $('canvas').css('cursor', '-moz-grab') 262 | else if @draw_mode 263 | $('canvas').css('cursor', 'crosshair') 264 | else 265 | $('canvas').css('cursor', 'default') 266 | 267 | # update buttons and cursor 268 | update_buttons: -> 269 | @update_cursor() 270 | enable_submit = (not window.min_shapes? or 271 | @num_polys() >= window.min_shapes) 272 | set_btn_enabled(@btn_submit, enable_submit) 273 | set_btn_enabled(@btn_draw, not @loading) 274 | set_btn_enabled(@btn_edit, not @loading) 275 | set_btn_enabled(@btn_delete, @can_delete_sel()) 276 | set_btn_enabled(@btn_zoom_reset, 277 | not @loading and @stage_ui.zoom_exp > 0) 278 | if @draw_mode 279 | $(@btn_draw).button('toggle') 280 | set_btn_enabled(@btn_close, @can_close()) 281 | else 282 | $(@btn_edit).button('toggle') 283 | set_btn_enabled(@btn_close, false) 284 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/poly/controller_ui.coffee: -------------------------------------------------------------------------------- 1 | # Main control logic for the UI. Actions in this class delegate through 2 | # undo/redo and check whether something is feasible. 3 | class ControllerUI 4 | constructor: (args) -> 5 | @s = new ControllerState(@, args) 6 | 7 | # disable right click 8 | $(document).on('contextmenu', (e) => 9 | @click(e) 10 | false 11 | ) 12 | 13 | # capture all clicks and disable text selection 14 | $(document) 15 | .on('click', @click) 16 | .on('mousedown', @mousedown) 17 | .on('mouseup', @mouseup) 18 | .on('mousemove', @mousemove) 19 | .on('selectstart', -> false) 20 | 21 | # init buttons 22 | $(@s.btn_draw).on('click', => 23 | if not @s.draw_mode then @toggle_mode()) 24 | $(@s.btn_edit).on('click', => 25 | if @s.draw_mode then @toggle_mode()) 26 | $(@s.btn_close).on('click', => 27 | if not @s.loading then @close_poly()) 28 | $(@s.btn_delete).on('click', => 29 | if not @s.loading then @delete_sel_poly()) 30 | $(@s.btn_zoom_reset).on('click', => 31 | if not @s.loading then @zoom_reset()) 32 | 33 | # log instruction viewing 34 | $('#modal-instructions').on('show', => 35 | @s.log.action(name: "ShowInstructions") 36 | ) 37 | $('#modal-instructions').on('hide', => 38 | @s.log.action(name: "HideInstructions") 39 | ) 40 | 41 | # keep track of modal state 42 | # (since we want to allow scrolling) 43 | $('.modal').on('show', => 44 | @s.modal_count += 1 45 | true 46 | ) 47 | $('.modal').on('hide', => 48 | @s.modal_count -= 1 49 | true 50 | ) 51 | 52 | # listen for scrolling 53 | $(window).on('mousewheel DOMMouseScroll', @wheel) 54 | 55 | # listen for translation 56 | $(window) 57 | .on('keydown', @keydown) 58 | .on('keyup', @keyup) 59 | .on('blur', @blur) 60 | 61 | # keep track of invalid close attempts to show a 62 | # popup explaining the problem 63 | @num_failed_closes = 0 64 | 65 | # init photo 66 | if args.photo_url? then @set_photo(args.photo_url) 67 | 68 | get_submit_data: => 69 | @s.get_submit_data() 70 | 71 | set_photo: (photo_url) => 72 | @s.disable_buttons() 73 | @s.loading = true 74 | @s.stage_ui.set_photo(photo_url, @, => 75 | console.log "loaded photo_url: #{photo_url}" 76 | @s.loading = false 77 | @s.update_buttons() 78 | ) 79 | 80 | keydown: (e) => 81 | if @s.modal_count > 0 then return true 82 | switch e.keyCode 83 | when 37 # left 84 | @s.translate_delta(-20, 0) 85 | false 86 | when 38 # up 87 | @s.translate_delta(0, -20) 88 | false 89 | when 39 # right 90 | @s.translate_delta(20, 0) 91 | false 92 | when 40 # down 93 | @s.translate_delta(0, 20) 94 | false 95 | when 32 # space 96 | @s.panning = true 97 | @s.update_cursor() 98 | false 99 | when 68 # D 100 | if not @s.draw_mode then @toggle_mode() 101 | false 102 | when 65 # A 103 | if @s.draw_mode then @toggle_mode() 104 | false 105 | when 46,8 # delete,backspace 106 | if @s.draw_mode 107 | @remove_open_poly() 108 | else 109 | @delete_sel_poly() 110 | false 111 | when 27 # esc 112 | if @s.draw_mode 113 | @s.zoom_reset() 114 | else 115 | @unselect_poly() 116 | false 117 | else 118 | true 119 | 120 | keyup: (e) => 121 | @s.panning = false 122 | if @s.modal_count > 0 then return true 123 | @s.update_cursor() 124 | return true 125 | 126 | blur: (e) => 127 | @s.panning = false 128 | @s.mousedown = false 129 | if @s.modal_count > 0 then return true 130 | @s.update_cursor() 131 | return true 132 | 133 | wheel: (e) => 134 | if @s.modal_count > 0 then return true 135 | oe = e.originalEvent 136 | if oe.wheelDelta? 137 | @s.zoom_delta(oe.wheelDelta) 138 | else 139 | @s.zoom_delta(oe.detail * -60) 140 | window.scrollTo(0, 0) 141 | stop_event(e) 142 | 143 | zoom_reset: (e) => 144 | @s.zoom_reset() 145 | 146 | click: (e) => 147 | if @s.panning then return 148 | p = @s.mouse_pos() 149 | if not p? then return 150 | if not @s.loading and @s.draw_mode 151 | if e.button > 1 152 | @close_poly() 153 | else 154 | if @s.open_poly? 155 | ue = new UEPushPoint(p) 156 | if @s.open_poly.poly.can_push_point(p) 157 | @s.undoredo.run(ue) 158 | else 159 | @s.log.attempted(ue.entry()) 160 | else 161 | @s.undoredo.run(new UECreatePolygon( 162 | @s.stage_ui.mouse_pos())) 163 | @s.stage_ui.translate_mouse_click() 164 | 165 | mousedown: (e) => 166 | if @s.modal_count > 0 then return true 167 | @s.mousedown = true 168 | @s.mousepos = {x: e.pageX, y: e.pageY} 169 | @s.update_cursor() 170 | return not @s.panning 171 | 172 | mouseup: (e) => 173 | @s.mousedown = false 174 | if @s.modal_count > 0 then return true 175 | @s.update_cursor() 176 | return not @s.panning 177 | 178 | mousemove: (e) => 179 | if @s.modal_count > 0 then return true 180 | if @s.mousedown and @s.panning 181 | scale = 1.0 / @s.stage_ui.get_zoom_factor() 182 | @s.stage_ui.translate_delta( 183 | scale * (@s.mousepos.x - e.pageX), 184 | scale * (@s.mousepos.y - e.pageY), 185 | false) 186 | @s.mousepos = {x: e.pageX, y: e.pageY} 187 | return true 188 | 189 | update: => 190 | @s.open_poly?.update(@) 191 | @s.sel_poly?.update(@) 192 | 193 | close_poly: => if not @s.loading 194 | ue = new UEClosePolygon() 195 | if @s.can_close() 196 | @s.undoredo.run(ue) 197 | else 198 | @s.log.attempted(ue.entry()) 199 | if @s.open_poly? 200 | pts = @s.open_poly.poly.points 201 | if pts.length >= 2 202 | @s.stage_ui.error_line(pts[0], pts[pts.length - 1]) 203 | @num_failed_closes += 1 204 | 205 | if @num_failed_closes >= 3 206 | @num_failed_closes = 0 207 | $('#poly-modal-intersect').modal('show') 208 | 209 | select_poly: (id) => 210 | @s.undoredo.run(new UESelectPolygon(id)) 211 | 212 | unselect_poly: (id) => 213 | @s.undoredo.run(new UEUnselectPolygon()) 214 | 215 | remove_open_poly: (id) => 216 | @s.undoredo.run(new UERemoveOpenPoly()) 217 | 218 | delete_sel_poly: => 219 | ue = new UEDeletePolygon() 220 | if @s.can_delete_sel() 221 | @s.undoredo.run(ue) 222 | else 223 | @s.log.attempted(ue.entry()) 224 | 225 | start_drag_point: (i) => 226 | p = @s.sel_poly.poly.get_pt(i) 227 | @s.drag_valid_point = clone_pt(p) 228 | @s.drag_start_point = clone_pt(p) 229 | 230 | revert_drag_point: (i) => 231 | @s.undoredo.run(new UEDragVertex(i, 232 | @s.drag_start_point, @s.drag_valid_point)) 233 | 234 | progress_drag_point: (i, p) => 235 | @s.sel_poly.poly.set_point(i, p) 236 | if @drag_valid(i) then @s.drag_valid_point = clone_pt(p) 237 | 238 | finish_drag_point: (i, p) => 239 | @s.undoredo.run(new UEDragVertex(i, @s.drag_start_point, p)) 240 | @s.drag_valid_point = null 241 | @s.drag_start_point = null 242 | 243 | drag_valid: (i) => 244 | not @s.sel_poly.poly.self_intersects_at_index(i) 245 | 246 | toggle_mode: => 247 | @s.undoredo.run(new UEToggleMode()) 248 | 249 | on_photo_loaded: => 250 | @s.update_buttons() 251 | @s.stage_ui.init_events() 252 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/poly/polygon_ui.coffee: -------------------------------------------------------------------------------- 1 | # UI for one polygon 2 | class PolygonUI 3 | constructor: (@id, @poly, @stage) -> 4 | # @stage is an instance of StageUI 5 | @line = null 6 | @fill = null 7 | @text = null 8 | @hover_line = null 9 | @hover_fill = null 10 | @anchors = null 11 | @stroke_scale = 1.0 / @stage.get_zoom_factor() 12 | 13 | # update stroke scale 14 | update_zoom: (ui, inv_zoom_factor, redraw=true) -> 15 | @stroke_scale = inv_zoom_factor 16 | @update(ui, redraw) 17 | 18 | # update UI elements 19 | update: (ui, redraw=true) -> 20 | if @poly.open 21 | @remove_fill() 22 | @remove_text() 23 | @add_line() 24 | p = @stage.mouse_pos() 25 | if p? and not @poly.empty() 26 | @add_hover(p) 27 | else 28 | @remove_hover() 29 | else 30 | @remove_hover() 31 | @remove_line() 32 | @add_fill(ui) 33 | @add_text() 34 | if redraw 35 | @stage.draw() 36 | 37 | # remove UI elements 38 | remove_line: -> @stage.remove(@line); @line = null 39 | remove_fill: -> @stage.remove(@fill); @fill = null 40 | remove_text: -> @stage.remove(@text); @text = null 41 | remove_hover: -> 42 | @stage.remove(@hover_fill); @hover_fill = null 43 | @stage.remove(@hover_line); @hover_line = null 44 | remove_anchors: -> if @anchors? 45 | if @anchors.length < 8 46 | for a in @anchors 47 | @stage.remove(a, 0.4) 48 | else 49 | for a in @anchors 50 | @stage.remove(a, 0) 51 | @anchors = null 52 | @stage.draw() 53 | remove_all: -> 54 | @remove_line() 55 | @remove_fill() 56 | @remove_text() 57 | @remove_hover() 58 | @remove_anchors() 59 | 60 | # add polygon fill 61 | add_fill: (ui)-> 62 | if @fill? 63 | @fill.setPoints(@poly.points) 64 | @fill.setStrokeWidth(2 * @stroke_scale) 65 | else 66 | @fill = new Kinetic.Polygon( 67 | points: @poly.points, 68 | fill: POLYGON_COLORS[@id % POLYGON_COLORS.length], 69 | stroke: '#007', strokeWidth: 2 * @stroke_scale, 70 | lineJoin: 'round') 71 | @fill.on('click', => 72 | if not ui.s.panning 73 | ui.select_poly(@id) 74 | ) 75 | @stage.add(@fill, 0.4) 76 | 77 | # add text label 78 | add_text: -> 79 | cen = @poly.labelpos() 80 | label = String(@id + 1) 81 | pos = 82 | x: cen.x - 5 * label.length * @stroke_scale 83 | y: cen.y - 5 * @stroke_scale 84 | if @text? 85 | @text.setPosition(pos) 86 | @text.setText(label) 87 | @text.setFontSize(10 * @stroke_scale) 88 | else 89 | @text = new Kinetic.Text( 90 | text: label, fill: '#000', 91 | x: pos.x, y: pos.y, align: 'left', 92 | fontSize: 10 * @stroke_scale, 93 | fontFamily: 'Verdana', fontStyle: 'bold') 94 | @stage.add(@text, 1.0) 95 | 96 | add_line: -> 97 | if @line? 98 | @line.setPoints(@poly.points) 99 | @line.setStrokeWidth(3 * @stroke_scale) 100 | else 101 | @line = new Kinetic.Line( 102 | points: @poly.points, opacity: 0, stroke: "#00F", 103 | strokeWidth: 3 * @stroke_scale, lineJoin: "round") 104 | @stage.add(@line, 0.5) 105 | 106 | add_hover: (p) -> 107 | @add_hover_fill(p) 108 | @add_hover_line(p) 109 | 110 | add_hover_fill: (p) -> 111 | hover_points = @poly.points.concat([clone_pt p]) 112 | if @hover_fill? 113 | @hover_fill.setPoints(hover_points) 114 | else 115 | @hover_fill = new Kinetic.Polygon( 116 | points: hover_points, opacity: 0, fill: "#00F") 117 | @stage.add(@hover_fill, 0.15) 118 | 119 | add_hover_line: (p) -> 120 | hover_points = [clone_pt(p), @poly.points[@poly.num_points() - 1]] 121 | if @hover_line? 122 | @hover_line.setPoints(hover_points) 123 | @hover_line.setStrokeWidth(3 * @stroke_scale) 124 | else 125 | @hover_line = new Kinetic.Line( 126 | points: hover_points, opacity: 0, stroke: "#00F", 127 | strokeWidth: 3 * @stroke_scale, lineCap: "round") 128 | @stage.add(@hover_line, 0.5) 129 | 130 | if @poly.can_push_point(p) 131 | @hover_line.setStroke("#00F") 132 | @hover_line.setStrokeWidth(3 * @stroke_scale) 133 | else 134 | @hover_line.setStroke("#F00") 135 | @hover_line.setStrokeWidth(10 * @stroke_scale) 136 | 137 | add_anchors: (ui) -> 138 | if ui.s.draw_mode then return 139 | if @anchors? 140 | if @anchors.length == @poly.points.length 141 | for p, i in @poly.points 142 | @anchors[i].setPosition(p.x, p.y) 143 | @anchors[i].setStrokeWidth(2 * @stroke_scale) 144 | @anchors[i].setRadius(10 * @stroke_scale) 145 | return 146 | @remove_anchors() 147 | 148 | @anchors = [] 149 | for p, i in @poly.points 150 | v = new Kinetic.Circle( 151 | x: p.x, y: p.y, 152 | radius: 10 * @stroke_scale, 153 | strokeWidth: 2 * @stroke_scale, 154 | stroke: "#666", fill: "#ddd", 155 | opacity: 0, draggable: true) 156 | 157 | v.on('mouseover', do (v) => => 158 | if v.removing != true 159 | $('canvas').css('cursor', 'pointer') 160 | v.setStrokeWidth(4 * @stroke_scale) 161 | @stage.draw() 162 | ) 163 | v.on('mouseout', do (v) => => 164 | if v.removing != true 165 | $('canvas').css('cursor', 'default') 166 | v.setStrokeWidth(2 * @stroke_scale) 167 | @stage.draw() 168 | ) 169 | v.on('mousedown', do (i) => => 170 | if v.removing != true 171 | ui.start_drag_point(i) 172 | ) 173 | 174 | v.on('dragmove', do (i) => => 175 | ui.progress_drag_point(i, @anchors[i].getPosition()) 176 | if @fill 177 | if ui.drag_valid(i) 178 | @fill.setStrokeWidth(2 * @stroke_scale) 179 | @fill.setStroke("#007") 180 | else 181 | @fill.setStrokeWidth(10 * @stroke_scale) 182 | @fill.setStroke("#F00") 183 | ) 184 | 185 | v.on('dragend', do (i) => => 186 | if ui.drag_valid(i) 187 | ui.finish_drag_point(i, @anchors[i].getPosition()) 188 | else 189 | ps = ui.revert_drag_point(i) 190 | if ps? then @anchors[i].setPosition(ps.x, ps.y) 191 | @fill.setStrokeWidth(2 * @stroke_scale) 192 | @fill.setStroke("#007") 193 | @update(ui) 194 | ) 195 | 196 | if @poly.points.length < 8 197 | @stage.add(v, 0.5, 0.4) 198 | else 199 | @stage.add(v, 0.5, 0) 200 | @anchors.push(v) 201 | -------------------------------------------------------------------------------- /example_project/segmentation/static/js/poly/stage_ui.coffee: -------------------------------------------------------------------------------- 1 | # Wrapper for Kinetic.Stae 2 | class StageUI 3 | constructor: (ui, args) -> 4 | # maximum possible size 5 | @bbox = {width: args.width, height: args.height} 6 | # actual size 7 | @size = {width: args.width, height: args.height} 8 | 9 | # zoom information 10 | @origin = {x: 0, y: 0} 11 | @zoom_exp = 0 12 | @zoom_exp_max = 7 13 | 14 | @stage = new Kinetic.Stage( 15 | container: args.container_id, 16 | width: @size.width, 17 | height: @size.height) 18 | @layer = new Kinetic.Layer() 19 | @stage.add(@layer) 20 | 21 | @stage.on('mouseout', => @layer.draw()) 22 | @stage.on('mousemove', -> 23 | if not ui.s.panning 24 | ui.update() 25 | ) 26 | 27 | add: (o, opacity=1.0, duration=0.4) -> 28 | @layer.add(o) 29 | if duration > 0 30 | o.setOpacity(0) 31 | o.add_trans = o.transitionTo(opacity:opacity, duration:duration) 32 | else 33 | o.setOpacity(opacity) 34 | 35 | remove: (o, duration=0.4) -> if o? 36 | o.add_trans?.stop() 37 | if duration > 0 38 | o.removing = true 39 | o.transitionTo( 40 | opacity: 0 41 | duration: duration 42 | callback: do (o) -> -> o.remove() 43 | ) 44 | else 45 | o.remove() 46 | 47 | draw: -> @layer.draw() 48 | 49 | mouse_pos: -> 50 | p = @stage.getMousePosition() 51 | if not p? 52 | p 53 | else 54 | scale = Math.pow(2, -@zoom_exp) 55 | x: Math.min(Math.max(0, p.x * scale + @origin.x), @size.width) 56 | y: Math.min(Math.max(0, p.y * scale + @origin.y), @size.height) 57 | 58 | zoom_reset: (redraw=true) -> 59 | @zoom_exp = 0 60 | @origin = {x: 0, y: 0} 61 | @stage.setOffset(@origin.x, @origin.y) 62 | @stage.setScale(1.0) 63 | if redraw 64 | @stage.draw() 65 | 66 | # zoom in/out by delta (in log_2 units) 67 | zoom_delta: (delta, p=@stage.getMousePosition()) -> 68 | if delta? 69 | @zoom_set(@zoom_exp + delta * 0.001, p) 70 | 71 | get_zoom_factor: -> 72 | Math.pow(2, @zoom_exp) 73 | 74 | # set the zoom level (in log_2 units) 75 | zoom_set: (new_zoom_exp, p=@stage.getMousePosition()) -> 76 | if @k_loading? or not new_zoom_exp? or not p? then return 77 | old_scale = Math.pow(2, @zoom_exp) 78 | @zoom_exp = Math.min(@zoom_exp_max, new_zoom_exp) 79 | if @zoom_exp <= 0 80 | @zoom_reset() 81 | else 82 | new_scale = Math.pow(2, @zoom_exp) 83 | f = (1.0 / old_scale - 1.0 / new_scale) 84 | @origin.x += f * p.x 85 | @origin.y += f * p.y 86 | @stage.setOffset(@origin.x, @origin.y) 87 | @stage.setScale(new_scale) 88 | @stage.draw() 89 | 90 | # zoom to focus on a box 91 | zoom_box: (aabb) -> 92 | min = {x: aabb.min.x - 50, y: aabb.min.y - 50} 93 | max = {x: aabb.max.x + 50, y: aabb.max.y + 50} 94 | obj = {width: max.x - min.x, height: max.y - min.y} 95 | b = compute_dimensions(obj, @bbox, INF) 96 | @zoom_exp = Math.max(0, Math.min(@zoom_exp_max, 97 | Math.log(b.scale) / Math.log(2))) 98 | if @zoom_exp <= 0 99 | @zoom_reset() 100 | else 101 | @origin = min 102 | @stage.setOffset(@origin.x, @origin.y) 103 | @stage.setScale(Math.pow(2, @zoom_exp)) 104 | @stage.draw() 105 | 106 | # translate the zoomed in view by some amount 107 | translate_delta: (x, y, transition=true) -> 108 | if not @k_loading 109 | @origin.x += x 110 | @origin.y += y 111 | if transition 112 | @stage.transitionTo( 113 | offset: clone_pt(@origin) 114 | duration: 0.1 115 | ) 116 | else 117 | @stage.setOffset(@origin.x, @origin.y) 118 | @stage.draw() 119 | 120 | # translate the view if near the edge 121 | translate_mouse_click: -> 122 | if @zoom_exp > 0 and not @k_loading 123 | p = @stage.getMousePosition() 124 | p = 125 | x: p.x / @stage.getWidth() 126 | y: p.y / @stage.getHeight() 127 | console.log 'p:', p 128 | delta = { x: 0, y: 0 } 129 | factor = @get_zoom_factor() 130 | if p.x < 0.05 131 | delta.x = -200 / @get_zoom_factor() 132 | else if p.x > 0.95 133 | delta.x = 200 / @get_zoom_factor() 134 | if p.y < 0.05 135 | delta.y = -200 / @get_zoom_factor() 136 | else if p.y > 0.95 137 | delta.y = 200 / @get_zoom_factor() 138 | if delta.x != 0 or delta.y != 0 139 | @translate_delta(delta.x, delta.y) 140 | 141 | error_line: (p1, p2) -> 142 | el = new Kinetic.Line( 143 | points: [clone_pt(p1), clone_pt(p2)], opacity: 0.5, 144 | stroke: "#F00", strokeWidth: 10 / @get_zoom_factor(), 145 | lineCap: "round") 146 | @layer.add(el) 147 | @remove(el) 148 | 149 | add_loading: -> if not @k_loading? 150 | @k_loading = new Kinetic.Text( 151 | x: 30, y: 30, text: "Loading...", align: "left", 152 | fontSize: 32, fontFamily: "Helvetica,Verdana,Ariel", 153 | textFill: "#000") 154 | @add(@k_loading) 155 | @draw() 156 | 157 | remove_loading: -> if @k_loading? 158 | @remove(@k_loading) 159 | @k_loading = null 160 | @draw() 161 | 162 | set_photo: (photo_url, ui, on_load) -> 163 | @add_loading() 164 | @photo_obj = new Image() 165 | @photo_obj.src = photo_url 166 | @photo_obj.onload = do() => => 167 | @remove_loading() 168 | @size = compute_dimensions(@photo_obj, @bbox) 169 | #@stage.setWidth(@size.width) 170 | #@stage.setHeight(@size.height) 171 | @photo = new Kinetic.Image( 172 | x: 0, y: 0, image: @photo_obj, 173 | width: @size.width, height:@size.height) 174 | @layer.add(@photo) 175 | @photo.moveToBottom() 176 | @ready = true 177 | @photo.on('mousedown', -> 178 | if not ui.s.panning 179 | ui.unselect_poly() 180 | ) 181 | on_load?() 182 | -------------------------------------------------------------------------------- /example_project/segmentation/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |{{ message|safe }}
8 |15 | {% if override == "task" %} 16 | View another HIT 17 | View instructions 18 | {% else %} 19 | View random HIT 20 | {% endif %} 21 |
22 |Note: the shape does not have to be a flat surface; please make the regions as large as possible.
12 |Different polygons can contain different materials, but each polygon must contain only one substance or texture.
13 |Each polygon must exactly follow the material boundary. Please adjust your shapes before submitting.
14 |Draw {{ min_shapes|apnumber }} polygon{{ min_shapes|pluralize }} to complete the task.
15 |This will be your photograph.
21 |Please draw shapes around regions that have a 4 | single material or texture. Some example materials: wood, steel, 5 | bricks, tiles, ceramic, plastic. If you don't know what to draw, floors, 6 | walls, and countertops are usually one material -- just be careful to exclude 7 | things on top.
8 | 9 |Please make the region as tight as possible. Your shape should exactly 10 | follow the material boundary. Use the "adjust" button (or 11 | press 'A') to improve the shape boundary.
12 | 13 |If there is something like a brick wall or tiled floor, please include all 14 | the tiles; do not draw a square around one tile. Large regions are 15 | preferable.
16 | 17 |Example: if a wood cabinet has a metal handle, then the metal portion is a 18 | different material and should be excluded. If you are drawing a shape around a 19 | person, their skin and clothing should not be in two different shapes. You can 20 | also exclude regions by overlapping shapes. See the below examples.
21 | 22 |Draw {{ min_shapes|apnumber }} polygon{{ min_shapes|pluralize }} to complete 23 | the task.
24 | 25 |Large flat regions are preferable, though not necessary.
34 |Make the boundary as tight as possible.
40 |Use separate shapes for different flat surfaces.
47 |Be careful to not include items of a different material.
56 |Shapes can be nested.
62 |Shapes can intersect.
68 |Please do not draw around individual bricks; draw a shape around larger wall portions.
76 |We will reject HITs that do not follow these instructions.
91 | 92 |These are sloppy shapes.
97 |Please do not do this.
103 |Please make the regions bigger than this.
111 |These shapes contain multiple materials and are nowhere near the correct boundaries.
117 |This shape includes more than one type of material.
125 |This includes other objects; the other objects should have shapes around them.
130 |This includes the edges of other objects.
136 |This shape is too sloppy.
141 |This contains more than one type of material.
147 |This contains many substances.
152 |162 | 1 Click on the material boundary.
163 |165 | 2 Continue clicking along the boundary.
166 |168 | 3 Right-click to close the polygon.
169 |To close the shape, you do not need to click near the first point. You can 175 | right-click to connect the first and last point.
176 | 177 |After drawing a polygon, please adjust its edges so that it better fits the true boundary. 179 | When you are done adjusting, switch back to "Draw" mode to draw more polygons.
180 |You can press the D and A key to quickly switch modes.
181 | 182 |186 | 1 Switch to "Adjust" mode.
187 |189 | 2 Click on the polygon.
190 |192 | 3 Drag the small white circles.
193 |Scroll with the mouse wheel to zoom in. Click "Reset Zoom" or press escape to reset back to normal.
200 |Press 'space' and drag with the mouse to move around in the image (panning). You can also move press the arrow keys to move.
204 |If there is a mirror, draw a shape around the mirror surface itself, not the 228 | things that you can see in the reflection. Similarly, draw a shape around the 229 | window glass, and not the things you can see through the window.
230 | 231 |We are building a database of materials from images on the web. This is part of ongoing research at Cornell University.
234 |Thank you for participating!
235 |