├── .gitignore ├── LICENCE ├── README ├── README.coding_style ├── README.commits ├── TODO ├── clients ├── java │ └── detect_wrapper │ │ ├── .classpath │ │ ├── .project │ │ ├── .settings │ │ └── org.eclipse.jdt.core.prefs │ │ ├── bin │ │ └── .gitkeep │ │ ├── detect_wrapper-0.0.2.jar │ │ └── src │ │ └── detect │ │ └── api │ │ ├── DetectWrapper.java │ │ ├── models │ │ ├── ApiError.java │ │ ├── Image.java │ │ └── Region.java │ │ └── util │ │ └── DOMParser.java ├── javascript │ └── detect_result_parser.js └── ruby │ ├── README │ ├── Rakefile │ ├── VERSION │ └── lib │ └── rdetectionapi.rb ├── doc ├── activity.dia ├── activity.png ├── highview.dia ├── highview.png ├── usecase.dia └── usecase.png ├── research └── recognition │ ├── README │ ├── app.rb │ ├── build_all.sh │ ├── detectface.cpp │ ├── detectperson.cpp │ ├── eigen.c │ ├── haarcascades │ └── haarcascade_frontalface_alt.xml │ ├── test.txt │ └── train.txt ├── runner ├── Gemfile ├── README ├── config │ ├── detect_settings.rb.example │ └── image_settings_example.example ├── daemon.rb ├── detect_server.rb ├── image_server.rb └── images │ └── test.jpg ├── spoc ├── Rakefile ├── VERSION ├── install.sh ├── lib │ ├── file_convert.rb │ ├── file_transfer.rb │ ├── haarcascade_frontalface_alt_tree.xml │ ├── light_cv.rb │ └── spoc.rb ├── module-light-opencv │ ├── LightOpencv.c │ └── extconf.rb └── spec │ ├── light_cv_spec.rb │ └── test.jpg ├── ubuntu_setup.sh └── ui-server ├── .gitignore ├── Gemfile ├── README ├── Rakefile ├── app ├── controllers │ ├── api │ │ └── v2 │ │ │ └── detect_controller.rb │ ├── application_controller.rb │ ├── backend │ │ ├── detect_result_controller.rb │ │ └── scheduler_controller.rb │ └── web_controller.rb ├── helpers │ ├── api │ │ └── v2 │ │ │ └── detect_helper.rb │ ├── application_helper.rb │ ├── backend │ │ ├── detect_result_helper.rb │ │ └── scheduler_helper.rb │ └── web_helper.rb ├── models │ ├── failure.rb │ ├── image.rb │ ├── region.rb │ └── runner.rb └── views │ ├── api │ └── v2 │ │ └── detect │ │ ├── _error.builder │ │ └── _new.builder │ ├── backend │ └── scheduler │ │ ├── _runner.haml │ │ └── index.erb │ ├── layouts │ └── application.html.erb │ └── web │ ├── _canvas.erb │ ├── _donate.html.erb │ ├── _footer.html.erb │ ├── _other_user_images.erb │ ├── _urlbox.erb │ ├── demo.erb │ ├── index.erb │ ├── info.erb │ └── solutions.erb ├── config.ru ├── config ├── application.rb ├── boot.rb ├── database.yml.example ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── backtrace_silencers.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── secret_token.rb │ └── session_store.rb ├── locales │ └── en.yml └── routes.rb ├── db ├── migrate │ ├── 20110107183219_create_runners.rb │ ├── 20110107194812_create_images.rb │ ├── 20110107194829_create_regions.rb │ ├── 20110107202655_add_runner_id_to_image.rb │ ├── 20110109162704_add_counter_cache_to_runners.rb │ ├── 20110118121344_create_failures.rb │ ├── 20110118123905_remove_failed_from_image.rb │ ├── 20110201074743_add_karma_to_images.rb │ └── 20110201100222_add_jobs_per_minute.rb ├── schema.rb └── seeds.rb ├── doc └── README_FOR_APP ├── lib ├── scheduler.rb └── tasks │ ├── .gitkeep │ └── backup.rake ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico ├── images │ ├── forkme.png │ ├── grup.jpg │ ├── java_icon.gif │ ├── loader.gif │ ├── rails.png │ ├── ruby_icon.jpg │ └── sample.png ├── javascripts │ ├── application.js │ ├── controls.js │ ├── detect_client.js │ ├── detect_result_parser.js │ ├── dragdrop.js │ ├── effects.js │ ├── prototype.js │ └── rails.js ├── robots.txt └── stylesheets │ ├── .gitkeep │ └── sinatra.css ├── script └── rails ├── test ├── factories │ ├── image_factory.rb │ ├── region_factory.rb │ └── runner_factory.rb ├── fixtures │ ├── failures.yml │ ├── images.yml │ ├── regions.yml │ └── runners.yml ├── functional │ ├── api │ │ └── v2 │ │ │ └── detect_controller_test.rb │ ├── backend │ │ ├── detect_result_controller_test.rb │ │ └── scheduler_controller_test.rb │ └── web_controller_test.rb ├── performance │ └── browsing_test.rb ├── test_helper.rb └── unit │ ├── failure_test.rb │ ├── helpers │ ├── api │ │ └── v2 │ │ │ └── detect_helper_test.rb │ ├── backend │ │ ├── detect_result_helper_test.rb │ │ └── scheduler_helper_test.rb │ └── web_helper_test.rb │ ├── image_test.rb │ ├── region_test.rb │ └── runner_test.rb └── vendor └── plugins └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | spoc/pkg/* 2 | spoc/output.jpg 3 | spoc/Makefile 4 | spoc/images 5 | ui-server/Gemfile.lock 6 | ui-server/config/database.yml 7 | *.log 8 | *.o 9 | *.so 10 | *~ 11 | *.class 12 | *.lock 13 | research/recognition/detectface 14 | research/recognition/detectperson 15 | research/recognition/eigen 16 | research/recognition/people/s*/* 17 | research/recognition/facedata.xml 18 | runner/config/detect_settings.rb 19 | runner/config/image_settings.rb 20 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) Detection API and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Detection API nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | == License 2 | 3 | OpenCV is under BSD License and the API is under MIT/GPL 4 | 5 | You can try the API at http://detection.myvhost.de/ 6 | 7 | == Install 8 | 9 | 1. Install ruby and git 10 | 2. Install postgresql server 11 | 3. Run the setup script with sudo rights 12 | 4. Start the rails application 13 | 5. Start one runner, register it and you should be ready to go. 14 | (this will be done more painless in the future) 15 | 16 | == How it works 17 | 18 | TODO 19 | -------------------------------------------------------------------------------- /README.coding_style: -------------------------------------------------------------------------------- 1 | Please respect standard ruby coding style. 2 | 3 | Two spapces not TABS. 4 | 5 | Code fearlessly! 6 | http://cam.ly/blog/2010/12/code-fearlessly/ 7 | -------------------------------------------------------------------------------- /README.commits: -------------------------------------------------------------------------------- 1 | Commits are changes to the codebase, consisting of the code change itself and a 2 | commit message. Creating a good commit is sometimes quite an art. This 3 | guidelines should help you with it. 4 | 5 | The most important rule for commits is quite simple: 6 | 7 | Value the time of your readers/reviewers much more than yours. 8 | 9 | Everything else is mostly a corollary. 10 | 11 | Code changes 12 | ------------ 13 | 14 | One commit should solve one problem. It should be an atomical unit -- before and 15 | after the commit, the code should be in consistent state. If you can break the 16 | problem into more subproblems, it is usually good idea to do it and split the 17 | change into more commits. This makes the understanding and reviewing the commits 18 | much easier. The important exceptions are: 19 | 20 | 1. You are doing the same or very similar change on many places. 21 | 22 | 2. You are rewriting a part of the code completely. 23 | 24 | You can label related commits using the commit message (e.g. "Foo refactoring 25 | 1/3: Extracting methods"). 26 | 27 | Commit messages 28 | --------------- 29 | 30 | The purpose of a commit message is: 31 | 32 | 1. To describe a change in the code for other people and your own future self. 33 | 34 | 2. To explain why it was done. 35 | 36 | The first point usually isn't a problem, but the second can be tricky. 37 | 38 | Commit messages describing the change well help tremendously when reviewing, 39 | debugging, digging through history, etc. -- mainly because they allow separating 40 | relevant changes from the irrelevant. When you need to look at the diff to 41 | actually determine if the commit is relevant for you, the description is bad. 42 | 43 | Good description does not need to be long -- for trivial changes like 44 | formatting, description "Improve formatting" is adequate. But it should be 45 | specific and contain relevant keywords so that grep in the commit log works 46 | sanely. 47 | 48 | Example: 49 | 50 | When porting test fixes from master to onsite_1_1 branch, grepping through the 51 | master's log for "fix tests" and similar phrases helped quite a lot to find 52 | the fixes. It wouldn't work if the descriptions were bad. 53 | 54 | When you are making a change in the code, you are making it for some reason. 55 | Sometimes the reason is simple and obvious (e.g. adding new functionality), 56 | sometimes it is quite complicated (e.g. result of a lengthy discussion weighting 57 | many factors or several hours of bug investigation). Whenever you make a change 58 | for non-obvious reason and don't state the reason anywhere, you basically just 59 | thrown the effort of coming to it out of the window. 60 | 61 | Example: 62 | 63 | You spend the whole day hunting down a bug and add two lines of code as a 64 | result. But a year after someone will be rewriting that part of the program. 65 | If he won't understand from the "git blame" why the change was made, it is 66 | quite likely he'll just throw the code out as unnecessary -- which will cause 67 | a regression and probably another day lost by someone hunting the bug down 68 | again. And no, the rewriter will not ask you -- because at the time, you may 69 | be working on other project or company. 70 | 71 | Code that nobody understands has no value. And the gap in understanding usually 72 | isn't because one doesn't see what is happening in the code, but *why*. So, 73 | please preserve information about your reasoning when doing a changes. It may be 74 | in comments, commit messages, or bugs referenced from them -- that does not 75 | matter much. It matters that the information is available. These two minutes 76 | spent writing it down are spent well. 77 | 78 | To reference bugs at bugzilla.novell.com, use the format bnc#123456. 79 | 80 | To reference FATE entries at fate.novell.com, use the format fate#123456. 81 | 82 | Examples of well-written commit messages: 83 | 84 | Example 1: 85 | 86 | work around ruby >= 1.8.7-p248 segfault bug #2781 87 | 88 | This seems to avoid triggering whatever craziness is causing 89 | the segfault. For more info see: 90 | 91 | http://redmine.ruby-lang.org/issues/show/2781 92 | 93 | (http://bit.ly/9MijD8) 94 | 95 | Example 2: 96 | 97 | Set X-Cascade header when using pass 98 | 99 | Setting X-Cascade: pass allows middleware outside the Sinatra stack 100 | to continue trying to match the request. 101 | 102 | (http://bit.ly/9aV9g3) 103 | 104 | Example 3: 105 | 106 | fix a weird integration issue. 107 | some processes run the 108 | provide/require processor after scope creation. That means 109 | that TypedScopeCreator can't assign types to the 110 | new namespaces. Normal closure compiler is moving towards a model 111 | where TypedScopeCreator declares namespaces in the global 112 | scope. 113 | It's not clear how we should resolve this inconsistency, but 114 | for now this shim will make things better. 115 | 116 | (http://code.google.com/p/closure-compiler/source/detail?r=419) 117 | 118 | Example 4: 119 | 120 | Attach types to literals at scope-creation time instead of at 121 | inference time. 122 | Scope-creation already attaches types to function literals at 123 | scope-creation type, so this makes the other literals more consistent 124 | with function literals. 125 | 126 | (http://code.google.com/p/closure-compiler/source/detail?r=411) 127 | 128 | Note that the first line usually describes the change, others 129 | explain the reasoning. 130 | 131 | Git has some specific rules for formatting commit message that other version 132 | systems don't have. If you break them, nothing fails, but if you adhere to them, 133 | Git will work better for you and for others. They are nicely summarized e.g. 134 | here: 135 | 136 | http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 137 | 138 | Consider these rules part of these guidelines and use them as much as 139 | practically possible. 140 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Logging 2 | Exceptions 3 | Testcoverage 4 | 5 | 6 | $("notice").innerHTML = ""; 7 | -------------------------------------------------------------------------------- /clients/java/detect_wrapper/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /clients/java/detect_wrapper/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | detect_wrapper 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /clients/java/detect_wrapper/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | #Tue Jan 18 22:43:24 CET 2011 2 | eclipse.preferences.version=1 3 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.6 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 12 | org.eclipse.jdt.core.compiler.source=1.6 13 | -------------------------------------------------------------------------------- /clients/java/detect_wrapper/bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/clients/java/detect_wrapper/bin/.gitkeep -------------------------------------------------------------------------------- /clients/java/detect_wrapper/detect_wrapper-0.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/clients/java/detect_wrapper/detect_wrapper-0.0.2.jar -------------------------------------------------------------------------------- /clients/java/detect_wrapper/src/detect/api/DetectWrapper.java: -------------------------------------------------------------------------------- 1 | package detect.api; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.InputStream; 5 | import java.io.InputStreamReader; 6 | import java.net.HttpURLConnection; 7 | import java.net.URL; 8 | 9 | import detect.api.models.ApiError; 10 | import detect.api.models.Image; 11 | import detect.api.util.DOMParser; 12 | 13 | public class DetectWrapper { 14 | 15 | private static String BASE_URL = "http://localhost:3000"; 16 | private DOMParser dp; 17 | private String url; 18 | private ApiError ae = null; 19 | private Image img = null; 20 | 21 | /** 22 | * DetectWrapper d = new DetectWrapper(""); 23 | if (d.isSuccessful()) { 24 | Image img = d.getImage(); 25 | img.getStatus(); 26 | } else { 27 | ApiError ae = d.getApiError(); 28 | ae.getErrorCode(); 29 | } 30 | * @param url 31 | * @throws Exception 32 | */ 33 | public DetectWrapper(String url) throws Exception { 34 | this.url = url; 35 | request(); 36 | } 37 | 38 | public DetectWrapper(String url, String base_url) throws Exception { 39 | this.url = url; 40 | BASE_URL = base_url; 41 | request(); 42 | } 43 | 44 | public void refresh() throws Exception { 45 | request(); 46 | } 47 | 48 | public boolean isSuccessful() { 49 | return img != null; 50 | } 51 | 52 | public Image getImage() { 53 | return img; 54 | } 55 | 56 | public ApiError getApiError() { 57 | return ae; 58 | } 59 | 60 | private void request() throws Exception { 61 | this.ae = null; 62 | this.img = null; 63 | 64 | String xml = executeHttpGet("/api/v2/detect/new?url=" + url); 65 | this.dp = new DOMParser(xml); 66 | if (dp.hasNodeNamed("error")) { 67 | this.ae = dp.getApiError(); 68 | } else { 69 | this.img = dp.getImage(); 70 | } 71 | } 72 | 73 | private String executeHttpGet(String uri) throws Exception { 74 | URL url = new URL(BASE_URL + uri); 75 | HttpURLConnection con = (HttpURLConnection) url.openConnection(); 76 | con.setRequestMethod("GET"); 77 | InputStream is = con.getInputStream(); 78 | InputStreamReader isr = new InputStreamReader(is); 79 | BufferedReader in = new BufferedReader(isr); 80 | 81 | StringBuffer sb = new StringBuffer(""); 82 | String line = ""; 83 | String NL = System.getProperty("line.separator"); 84 | while ((line = in.readLine()) != null) { 85 | sb.append(line + NL); 86 | } 87 | in.close(); 88 | return sb.toString(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /clients/java/detect_wrapper/src/detect/api/models/ApiError.java: -------------------------------------------------------------------------------- 1 | package detect.api.models; 2 | 3 | public class ApiError { 4 | 5 | private String errorCode; 6 | private String errorDescription; 7 | 8 | public ApiError() { 9 | 10 | } 11 | 12 | public String getErrorCode() { 13 | return errorCode; 14 | } 15 | 16 | public void setErrorCode(String errorCode) { 17 | this.errorCode = errorCode; 18 | } 19 | 20 | public String getErrorDescription() { 21 | return errorDescription; 22 | } 23 | 24 | public void setErrorDescription(String errorDescription) { 25 | this.errorDescription = errorDescription; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /clients/java/detect_wrapper/src/detect/api/models/Image.java: -------------------------------------------------------------------------------- 1 | package detect.api.models; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class Image { 6 | 7 | private int id; 8 | private String status; 9 | private String url; 10 | private int estimatedTimeOfArrival; 11 | private ArrayList regions; 12 | 13 | public static final String STATUS_COMPLETED = "completed"; 14 | public static final String STATUS_FAILED = "failed"; 15 | public static final String STATUS_PROCESSING = "processing"; 16 | 17 | public Image() { 18 | regions = new ArrayList(); 19 | } 20 | 21 | public void addRegion(Region r) { 22 | regions.add(r); 23 | } 24 | 25 | public boolean isCompleted() { 26 | return getStatus().equals(STATUS_COMPLETED); 27 | } 28 | 29 | public ArrayList getRegions() { 30 | return regions; 31 | } 32 | public void setRegions(ArrayList regions) { 33 | this.regions = regions; 34 | } 35 | public int getId() { 36 | return id; 37 | } 38 | public void setId(int id) { 39 | this.id = id; 40 | } 41 | public String getStatus() { 42 | return status; 43 | } 44 | public void setStatus(String status) { 45 | this.status = status; 46 | } 47 | public String getUrl() { 48 | return url; 49 | } 50 | public void setUrl(String url) { 51 | this.url = url; 52 | } 53 | public int getEstimatedTimeOfArrival() { 54 | return estimatedTimeOfArrival; 55 | } 56 | public void setEstimatedTimeOfArrival(int estimatedTimeOfArrival) { 57 | this.estimatedTimeOfArrival = estimatedTimeOfArrival; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /clients/java/detect_wrapper/src/detect/api/models/Region.java: -------------------------------------------------------------------------------- 1 | package detect.api.models; 2 | 3 | public class Region { 4 | 5 | private int tlx, tly, brx, bry; 6 | 7 | public Region() { 8 | 9 | } 10 | 11 | public int getTlx() { 12 | return tlx; 13 | } 14 | 15 | public void setTlx(int tlx) { 16 | this.tlx = tlx; 17 | } 18 | 19 | public int getTly() { 20 | return tly; 21 | } 22 | 23 | public void setTly(int tly) { 24 | this.tly = tly; 25 | } 26 | 27 | public int getBrx() { 28 | return brx; 29 | } 30 | 31 | public void setBrx(int brx) { 32 | this.brx = brx; 33 | } 34 | 35 | public int getBry() { 36 | return bry; 37 | } 38 | 39 | public void setBry(int bry) { 40 | this.bry = bry; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /clients/java/detect_wrapper/src/detect/api/util/DOMParser.java: -------------------------------------------------------------------------------- 1 | package detect.api.util; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.InputStream; 5 | import java.util.ArrayList; 6 | 7 | import javax.xml.parsers.DocumentBuilder; 8 | import javax.xml.parsers.DocumentBuilderFactory; 9 | 10 | import org.w3c.dom.Document; 11 | import org.w3c.dom.Element; 12 | import org.w3c.dom.NamedNodeMap; 13 | import org.w3c.dom.Node; 14 | import org.w3c.dom.NodeList; 15 | 16 | import detect.api.models.ApiError; 17 | import detect.api.models.Image; 18 | import detect.api.models.Region; 19 | 20 | public class DOMParser { 21 | 22 | private Document doc; 23 | 24 | public DOMParser(String xml) throws Exception { 25 | this.doc = getNormalizedDocument(xml); 26 | } 27 | 28 | public boolean hasNodeNamed(String name) { 29 | boolean result = false; 30 | try { 31 | result = doc.getElementsByTagName(name).getLength() != 0; 32 | } catch (Exception e) { 33 | System.out.println("Could not parse xml!"); 34 | //e.printStackTrace(); 35 | } 36 | return result; 37 | } 38 | 39 | public ApiError getApiError() { 40 | ApiError ae = new ApiError(); 41 | String[] tagList = {"code", "description"}; 42 | ArrayList error = getValues("error", tagList); 43 | ae.setErrorCode(error.get(0)); 44 | ae.setErrorDescription(error.get(1)); 45 | return ae; 46 | } 47 | 48 | public Image getImage() { 49 | Image img = new Image(); 50 | String[] tagList = {"id", "status", "url"}; 51 | ArrayList values = getValues("image", tagList); 52 | img.setId(Integer.parseInt(values.get(0))); 53 | img.setStatus(values.get(1)); 54 | img.setUrl(values.get(2)); 55 | 56 | NodeList nList = doc.getElementsByTagName("regions"); 57 | for (int temp = 0; temp < nList.getLength(); temp++) { 58 | Node nNode = nList.item(temp); 59 | if (nNode.getNodeType() == Node.ELEMENT_NODE) { 60 | Element eElement = (Element) nNode; 61 | NodeList nlList= eElement.getElementsByTagName("region"); 62 | 63 | for (int i = 0; i < nlList.getLength(); i++) { 64 | Node oneRegion = nlList.item(i); 65 | NamedNodeMap attr = oneRegion.getAttributes(); 66 | img.addRegion(parseRegion(attr)); 67 | } 68 | } 69 | } 70 | return img; 71 | } 72 | 73 | private Region parseRegion(NamedNodeMap attr) { 74 | int tlx = Integer.valueOf(attr.getNamedItem("top_left_x").getNodeValue()); 75 | int tly = Integer.valueOf(attr.getNamedItem("top_left_y").getNodeValue()); 76 | int brx = Integer.valueOf(attr.getNamedItem("bottom_right_x").getNodeValue()); 77 | int bry = Integer.valueOf(attr.getNamedItem("bottom_right_y").getNodeValue()); 78 | 79 | Region r = new Region(); 80 | r.setTlx(tlx); 81 | r.setTly(tly); 82 | r.setBrx(brx); 83 | r.setBry(bry); 84 | 85 | return r; 86 | } 87 | 88 | private ArrayList getValues(String nodeListName, String[] tagList){ 89 | ArrayList result = new ArrayList(); 90 | NodeList nList = doc.getElementsByTagName(nodeListName); 91 | 92 | for (int temp = 0; temp < nList.getLength(); temp++) { 93 | Node nNode = nList.item(temp); 94 | if (nNode.getNodeType() == Node.ELEMENT_NODE) { 95 | Element eElement = (Element) nNode; 96 | for (String tag : tagList) { 97 | result.add(getTagValue(tag, eElement)); 98 | } 99 | } 100 | } 101 | return result; 102 | } 103 | 104 | private Document getNormalizedDocument(String xml) throws Exception { 105 | DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); 106 | DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); 107 | InputStream is = new ByteArrayInputStream(xml.getBytes("UTF-8")); 108 | Document doc = dBuilder.parse(is); 109 | doc.getDocumentElement().normalize(); 110 | return doc; 111 | } 112 | 113 | private String getTagValue(String sTag, Element eElement){ 114 | NodeList nlList= eElement.getElementsByTagName(sTag).item(0).getChildNodes(); 115 | Node nValue = (Node) nlList.item(0); 116 | // sucks that I have to do this.. 117 | try { 118 | return nValue.getNodeValue(); 119 | } catch (NullPointerException e) { 120 | return ""; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /clients/javascript/detect_result_parser.js: -------------------------------------------------------------------------------- 1 | function ApiError(code, description) { 2 | this.code = code; 3 | this.description = description; 4 | } 5 | 6 | function Region(tlx, tly, brx, bry) { 7 | this.top_left_x = tlx; 8 | this.top_left_y = tly; 9 | this.bottom_right_x = brx; 10 | this.bottom_right_y = bry; 11 | } 12 | 13 | function DetectResultParser(req) { 14 | this.req = req; 15 | 16 | this.getRegions = function() { 17 | var result = new Array(); 18 | var myregions = xmlDoc.getElementsByTagName("region"); 19 | for ( i = 0; i < myregions.length; i = i + 1) 20 | { 21 | region = myregions[i]; 22 | tlx = parseFloat(region.getAttribute("top_left_x")); 23 | tly = parseFloat(region.getAttribute("top_left_y")); 24 | brx = parseFloat(region.getAttribute("bottom_right_x")); 25 | bry = parseFloat(region.getAttribute("bottom_right_y")); 26 | result[i] = new Region(tlx, tly, brx, bry); 27 | } 28 | return result; 29 | }; 30 | 31 | this.isCompleted = function() { 32 | status = xmlDoc.getElementsByTagName("status")[0].childNodes[0].nodeValue; 33 | return status == "completed"; 34 | }; 35 | 36 | this.getError = function() { 37 | code = xmlDoc.getElementsByTagName("code")[0].childNodes[0].nodeValue; 38 | description = xmlDoc.getElementsByTagName("description")[0].childNodes[0].nodeValue; 39 | return new ApiError(code, description); 40 | } 41 | 42 | this.isError = function() { 43 | return xmlDoc.getElementsByTagName("error")[0] != null; 44 | }; 45 | 46 | if (window.DOMParser) 47 | { 48 | parser=new DOMParser(); 49 | xmlDoc=parser.parseFromString(req,"text/xml"); 50 | } 51 | else // Internet Explorer 52 | { 53 | xmlDoc=new ActiveXObject("Microsoft.XMLDOM"); 54 | xmlDoc.async="false"; 55 | xmlDoc.loadXML(req); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /clients/ruby/README: -------------------------------------------------------------------------------- 1 | Wrapper for DetectionAPI at http://detection.myvhost.de/ written in ruby 2 | GPLv3 Licence 3 | 4 | 5 | Example usage: 6 | 7 | puts RDetectionAPI::Detect.new("http://sphotos.ak.fbcdn.net/photos-ak-snc1/v272/12/29/1352704123/n1352704123_30009292_5638.jpg") 8 | 9 | 10 | TODO: 11 | 12 | parse the output 13 | possibility to use a different base urls 14 | -------------------------------------------------------------------------------- /clients/ruby/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | 3 | begin 4 | require 'jeweler' 5 | Jeweler::Tasks.new do |s| 6 | s.name = %q{rdetectionapi} 7 | s.summary = %q{Ruby gem for DetectionAPI} 8 | s.description = %q{Nothing yet} 9 | 10 | s.files = ["lib/*.rb"] 11 | s.require_path = 'lib' 12 | s.test_files = Dir[*['test/*_test.rb']] 13 | 14 | s.has_rdoc = false 15 | 16 | s.author = 'Cristian Mircea Messel' 17 | s.email = 'mess110@gmail.com' 18 | 19 | s.homepage = "https://github.com/mess110/detection" 20 | 21 | s.platform = Gem::Platform::RUBY 22 | 23 | #s.add_dependency('json') 24 | s.add_dependency('httparty') 25 | end 26 | Jeweler::GemcutterTasks.new 27 | rescue LoadError 28 | puts "Jeweler not available. Install it with: gem install jeweler" 29 | end 30 | -------------------------------------------------------------------------------- /clients/ruby/VERSION: -------------------------------------------------------------------------------- 1 | 0.0.1 2 | -------------------------------------------------------------------------------- /clients/ruby/lib/rdetectionapi.rb: -------------------------------------------------------------------------------- 1 | require 'httparty' 2 | 3 | module RDetectionAPI 4 | class Detect 5 | include HTTParty 6 | base_uri 'http://detection.myvhost.de' 7 | 8 | def self.new url 9 | get("/api/v2/detect/new?url=#{url}") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /doc/activity.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/doc/activity.dia -------------------------------------------------------------------------------- /doc/activity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/doc/activity.png -------------------------------------------------------------------------------- /doc/highview.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/doc/highview.dia -------------------------------------------------------------------------------- /doc/highview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/doc/highview.png -------------------------------------------------------------------------------- /doc/usecase.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/doc/usecase.dia -------------------------------------------------------------------------------- /doc/usecase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/doc/usecase.png -------------------------------------------------------------------------------- /research/recognition/README: -------------------------------------------------------------------------------- 1 | detectface grabs rectangles containing faces from webcam and transforms them. 2 | 3 | eigen trains a face from the grabed rectangles detectface used 4 | 5 | use app.rb as an interface between these two 6 | 7 | detectperson tries to guess which person it is. 8 | -------------------------------------------------------------------------------- /research/recognition/app.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | class TrainingData 4 | attr_accessor :pics 5 | 6 | TRAINING_FILE = 'train.txt' 7 | 8 | def initialize 9 | @pics = [] 10 | File.open(TRAINING_FILE, 'r') do |f| 11 | while line = f.gets 12 | parse_and_add(line) 13 | end 14 | end 15 | end 16 | 17 | def count_subjects 18 | count = 0 19 | unique = [] 20 | @pics.each do |p| 21 | if !unique.include?(p[:id]) 22 | count = count + 1; 23 | unique << p[:id] 24 | end 25 | end 26 | count 27 | end 28 | 29 | def add_subject 30 | system("./detectface") 31 | 32 | new_subject_id = count_subjects + 1 33 | Dir.mkdir("people") unless File.exists?("people") 34 | Dir.mkdir("people/s#{new_subject_id}") unless File.exists?("people/s#{new_subject_id}") 35 | 36 | Dir["./people/*.pgm"].each do |pic| 37 | dest = "people/s#{new_subject_id}/" 38 | FileUtils.mv(pic, dest) 39 | end 40 | 41 | File.open(TRAINING_FILE, 'a') do |f| 42 | Dir["people/s#{new_subject_id}/*.pgm"].each do |pic| 43 | f.puts "#{new_subject_id} #{pic}" 44 | end 45 | end 46 | end 47 | 48 | def parse_and_add(s) 49 | details = s.split(" ") 50 | @pics << { 51 | :id => details[0], 52 | :pic_path => details[1] 53 | } 54 | end 55 | end 56 | 57 | samples = TrainingData.new 58 | 59 | if ARGV[0] == "new" 60 | samples.add_subject 61 | else 62 | puts "--help\n" 63 | puts " new - add a new subject" 64 | end 65 | -------------------------------------------------------------------------------- /research/recognition/build_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | g++ -ggdb `pkg-config --cflags opencv` -o `basename detectface .cpp` detectface.cpp `pkg-config --libs opencv`; 3 | gcc -ggdb `pkg-config --cflags opencv` -o `basename eigen .c` eigen.c `pkg-config --libs opencv`; 4 | g++ -ggdb `pkg-config --cflags opencv` -o `basename detectperson .cpp` detectperson.cpp `pkg-config --libs opencv`; 5 | -------------------------------------------------------------------------------- /research/recognition/detectface.cpp: -------------------------------------------------------------------------------- 1 | #define CV_NO_BACKWARD_COMPATIBILITY 2 | 3 | #include "cv.h" 4 | #include "highgui.h" 5 | 6 | #include 7 | #include 8 | 9 | #ifdef _EiC 10 | #define WIN32 11 | #endif 12 | 13 | using namespace std; 14 | using namespace cv; 15 | 16 | String cascadeName = "haarcascades/haarcascade_frontalface_alt.xml"; 17 | void cleanup(CvCapture* capture); 18 | int detectFace( Mat& img, CascadeClassifier& cascade, double scale, int count); 19 | 20 | int main( int argc, const char** argv ) 21 | { 22 | CvCapture* capture = 0; 23 | 24 | cvNamedWindow( "result", 1 ); 25 | 26 | CascadeClassifier cascade; 27 | if( !cascade.load( cascadeName ) ) 28 | { 29 | cerr << "ERROR: Could not load classifier cascade" << endl; 30 | return -1; 31 | } 32 | 33 | capture = cvCaptureFromCAM(0); 34 | if( capture ) 35 | { 36 | Mat frame, frameCopy; 37 | int count = 1; 38 | for(;;) 39 | { 40 | IplImage* iplImg = cvQueryFrame( capture ); 41 | frame = iplImg; 42 | if( frame.empty() ) 43 | break; 44 | if( iplImg->origin == IPL_ORIGIN_TL ) 45 | frame.copyTo( frameCopy ); 46 | else 47 | flip( frame, frameCopy, 0 ); 48 | 49 | count = detectFace(frame, cascade, 1, count); 50 | 51 | if( waitKey( 10 ) >= 0 ) { 52 | cleanup(capture); 53 | break; 54 | } 55 | } 56 | } 57 | cvDestroyWindow("result"); 58 | return 0; 59 | } 60 | 61 | int detectFace( Mat& img, CascadeClassifier& cascade, double scale, int count) 62 | { 63 | int i = 0; 64 | double t = 0; 65 | vector faces; 66 | Mat gray, smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale), CV_8UC1 ); 67 | 68 | cvtColor( img, gray, CV_BGR2GRAY ); 69 | resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR ); 70 | equalizeHist( smallImg, smallImg ); 71 | 72 | cascade.detectMultiScale( smallImg, faces, 73 | 1.1, 2, 0 |CV_HAAR_FIND_BIGGEST_OBJECT, Size(80, 80) ); 74 | 75 | Mat smallImgROI; 76 | if (faces.size() == 1) 77 | { 78 | //get the face 79 | smallImgROI = smallImg(*faces.begin()); 80 | IplImage *imageProcessed; 81 | imageProcessed = cvCreateImage(cvSize(92, 112), IPL_DEPTH_8U, 1); 82 | Mat dst(imageProcessed); 83 | //resize 84 | cv::resize(smallImgROI, dst, dst.size(), 0, 0, INTER_LINEAR); 85 | //equalize 86 | cv::equalizeHist( dst, dst ); 87 | 88 | char buffer[128]; 89 | snprintf(buffer, sizeof(buffer), "people/%d%s", count, ".pgm"); 90 | std::cout << buffer << std::endl; 91 | cv::imwrite(buffer, dst); 92 | cv::imshow( "result", dst ); 93 | sleep( 0.5 ); 94 | count++; 95 | } 96 | return count; 97 | } 98 | 99 | void cleanup(CvCapture* capture) 100 | { 101 | cvReleaseCapture( &capture ); 102 | } 103 | -------------------------------------------------------------------------------- /research/recognition/detectperson.cpp: -------------------------------------------------------------------------------- 1 | #define CV_NO_BACKWARD_COMPATIBILITY 2 | 3 | #include "cv.h" 4 | #include "cvaux.h" 5 | #include "highgui.h" 6 | 7 | #include 8 | #include 9 | 10 | #ifdef _EiC 11 | #define WIN32 12 | #endif 13 | 14 | using namespace std; 15 | using namespace cv; 16 | 17 | int nTrainFaces = 0; 18 | int nEigens = 0; 19 | IplImage ** faceImgArr = 0; 20 | CvMat * personNumTruthMat = 0; 21 | IplImage * pAvgTrainImg = 0; 22 | IplImage ** eigenVectArr = 0; 23 | CvMat * eigenValMat = 0; 24 | CvMat * projectedTrainFaceMat = 0; 25 | String cascadeName = "haarcascades/haarcascade_frontalface_alt.xml"; 26 | 27 | int findNearestNeighbor(float * projectedTestFace); 28 | void detectFace( Mat& img, CascadeClassifier& cascade, double scale); 29 | void recognize(); 30 | int loadFaceImgArr(char * filename); 31 | int loadTrainingData(CvMat ** pTrainPersonNumMat, char* storageData); 32 | void rec(Mat& img, char* storageData); 33 | 34 | int main( int argc, const char** argv ) 35 | { 36 | CvCapture* capture = 0; 37 | 38 | cvNamedWindow( "result", 1 ); 39 | 40 | // load cascade 41 | CascadeClassifier cascade; 42 | if( !cascade.load( cascadeName ) ) 43 | { 44 | cerr << "ERROR: Could not load classifier cascade" << endl; 45 | return -1; 46 | } 47 | 48 | // start capturing 49 | capture = cvCaptureFromCAM(0); 50 | if( capture ) 51 | { 52 | Mat frame, frameCopy; 53 | for(;;) 54 | { 55 | IplImage* iplImg = cvQueryFrame( capture ); 56 | frame = iplImg; 57 | if( frame.empty() ) 58 | break; 59 | if( iplImg->origin == IPL_ORIGIN_TL ) 60 | frame.copyTo( frameCopy ); 61 | else 62 | flip( frame, frameCopy, 0 ); 63 | 64 | detectFace(frame, cascade, 1); 65 | 66 | if( waitKey( 10 ) >= 0 ) { 67 | cvReleaseCapture( &capture ); 68 | break; 69 | } 70 | } 71 | } 72 | cvDestroyWindow("result"); 73 | return 0; 74 | } 75 | 76 | void detectFace( Mat& img, CascadeClassifier& cascade, double scale) 77 | { 78 | int i = 0; 79 | double t = 0; 80 | vector faces; 81 | Mat gray, smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale), CV_8UC1 ); 82 | 83 | cvtColor( img, gray, CV_BGR2GRAY ); 84 | resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR ); 85 | equalizeHist( smallImg, smallImg ); 86 | 87 | cascade.detectMultiScale( smallImg, faces, 88 | 1.1, 2, 0 |CV_HAAR_FIND_BIGGEST_OBJECT, Size(100, 100) ); 89 | 90 | Mat smallImgROI; 91 | if (faces.size() == 1) 92 | { 93 | //get the face 94 | smallImgROI = smallImg(*faces.begin()); 95 | IplImage *imageProcessed; 96 | imageProcessed = cvCreateImage(cvSize(92, 112), IPL_DEPTH_8U, 1); 97 | Mat dst(imageProcessed); 98 | //resize 99 | cv::resize(smallImgROI, dst, dst.size(), 0, 0, INTER_LINEAR); 100 | //equalize 101 | cv::equalizeHist( dst, dst ); 102 | rec(dst, (char*) "facedata.xml"); 103 | cv::imshow( "result", img ); 104 | } 105 | } 106 | 107 | void rec(Mat& face, char* storageData) 108 | { 109 | int i, nTestFaces = 0; // the number of test images 110 | CvMat * trainPersonNumMat = 0; // the person numbers during training 111 | float * projectedTestFace = 0; 112 | 113 | if( !loadTrainingData( &trainPersonNumMat, storageData ) ) return; 114 | 115 | projectedTestFace = (float *)cvAlloc( nEigens*sizeof(float) ); 116 | int iNearest, nearest, truth; 117 | 118 | //convert from Mat& to IplImage* 119 | IplImage iplFace = face; 120 | // project the test image onto the PCA subspace 121 | cvEigenDecomposite(&iplFace, nEigens, eigenVectArr, 0, 0, pAvgTrainImg, projectedTestFace); 122 | 123 | iNearest = findNearestNeighbor(projectedTestFace); 124 | cout << iNearest << endl; 125 | } 126 | 127 | int loadTrainingData(CvMat ** pTrainPersonNumMat, char* storageData) 128 | { 129 | CvFileStorage * fileStorage; 130 | int i; 131 | 132 | fileStorage = cvOpenFileStorage( storageData, 0, CV_STORAGE_READ ); 133 | if( !fileStorage ) 134 | { 135 | fprintf(stderr, "Can't open training data! %s\n", storageData); 136 | return 0; 137 | } 138 | 139 | nEigens = cvReadIntByName(fileStorage, 0, "nEigens", 0); 140 | nTrainFaces = cvReadIntByName(fileStorage, 0, "nTrainFaces", 0); 141 | *pTrainPersonNumMat = (CvMat *)cvReadByName(fileStorage, 0, "trainPersonNumMat", 0); 142 | eigenValMat = (CvMat *)cvReadByName(fileStorage, 0, "eigenValMat", 0); 143 | projectedTrainFaceMat = (CvMat *)cvReadByName(fileStorage, 0, "projectedTrainFaceMat", 0); 144 | pAvgTrainImg = (IplImage *)cvReadByName(fileStorage, 0, "avgTrainImg", 0); 145 | eigenVectArr = (IplImage **)cvAlloc(nTrainFaces*sizeof(IplImage *)); 146 | for(i=0; idata.fl[iTrain*nEigens + i]; 172 | distSq += d_i*d_i / eigenValMat->data.fl[i]; // Mahalanobis 173 | //distSq += d_i*d_i; // Euclidean 174 | } 175 | 176 | if(distSq < leastDistSq) 177 | { 178 | leastDistSq = distSq; 179 | iNearest = iTrain; 180 | } 181 | } 182 | 183 | return iNearest; 184 | } 185 | -------------------------------------------------------------------------------- /research/recognition/eigen.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "cv.h" 4 | #include "cvaux.h" 5 | #include "highgui.h" 6 | 7 | 8 | //// Global variables 9 | IplImage ** faceImgArr = 0; // array of face images 10 | CvMat * personNumTruthMat = 0; // array of person numbers 11 | int nTrainFaces = 0; // the number of training images 12 | int nEigens = 0; // the number of eigenvalues 13 | IplImage * pAvgTrainImg = 0; // the average image 14 | IplImage ** eigenVectArr = 0; // eigenvectors 15 | CvMat * eigenValMat = 0; // eigenvalues 16 | CvMat * projectedTrainFaceMat = 0; // projected training faces 17 | 18 | 19 | //// Function prototypes 20 | void learn(); 21 | void recognize(); 22 | void doPCA(); 23 | void storeTrainingData(); 24 | int loadTrainingData(CvMat ** pTrainPersonNumMat); 25 | int findNearestNeighbor(float * projectedTestFace); 26 | int loadFaceImgArray(char * filename); 27 | void printUsage(); 28 | 29 | 30 | ////////////////////////////////// 31 | // main() 32 | // 33 | void main( int argc, char** argv ) 34 | { 35 | // validate that an input was specified 36 | if( argc != 2 ) 37 | { 38 | printUsage(); 39 | return; 40 | } 41 | 42 | if( !strcmp(argv[1], "train") ) learn(); 43 | else if( !strcmp(argv[1], "test") ) recognize(); 44 | else 45 | { 46 | printf("Unknown command: %s\n", argv[1]); 47 | printUsage(); 48 | } 49 | } 50 | 51 | 52 | ////////////////////////////////// 53 | // learn() 54 | // 55 | void learn() 56 | { 57 | int i, offset; 58 | 59 | // load training data 60 | nTrainFaces = loadFaceImgArray("train.txt"); 61 | if( nTrainFaces < 2 ) 62 | { 63 | fprintf(stderr, 64 | "Need 2 or more training faces\n" 65 | "Input file contains only %d\n", nTrainFaces); 66 | return; 67 | } 68 | 69 | // do PCA on the training faces 70 | doPCA(); 71 | 72 | // project the training images onto the PCA subspace 73 | projectedTrainFaceMat = cvCreateMat( nTrainFaces, nEigens, CV_32FC1 ); 74 | offset = projectedTrainFaceMat->step / sizeof(float); 75 | for(i=0; idata.fl + i*nEigens); 85 | projectedTrainFaceMat->data.fl + i*offset); 86 | } 87 | 88 | // store the recognition data as an xml file 89 | storeTrainingData(); 90 | } 91 | 92 | 93 | ////////////////////////////////// 94 | // recognize() 95 | // 96 | void recognize() 97 | { 98 | int i, nTestFaces = 0; // the number of test images 99 | CvMat * trainPersonNumMat = 0; // the person numbers during training 100 | float * projectedTestFace = 0; 101 | 102 | // load test images and ground truth for person number 103 | nTestFaces = loadFaceImgArray("test.txt"); 104 | printf("%d test faces loaded\n", nTestFaces); 105 | 106 | // load the saved training data 107 | if( !loadTrainingData( &trainPersonNumMat ) ) return; 108 | 109 | // project the test images onto the PCA subspace 110 | projectedTestFace = (float *)cvAlloc( nEigens*sizeof(float) ); 111 | for(i=0; idata.i[i]; 126 | nearest = trainPersonNumMat->data.i[iNearest]; 127 | 128 | printf("nearest = %d, Truth = %d\n", nearest, truth); 129 | } 130 | } 131 | 132 | 133 | ////////////////////////////////// 134 | // loadTrainingData() 135 | // 136 | int loadTrainingData(CvMat ** pTrainPersonNumMat) 137 | { 138 | CvFileStorage * fileStorage; 139 | int i; 140 | 141 | // create a file-storage interface 142 | fileStorage = cvOpenFileStorage( "facedata.xml", 0, CV_STORAGE_READ ); 143 | if( !fileStorage ) 144 | { 145 | fprintf(stderr, "Can't open facedata.xml\n"); 146 | return 0; 147 | } 148 | 149 | nEigens = cvReadIntByName(fileStorage, 0, "nEigens", 0); 150 | nTrainFaces = cvReadIntByName(fileStorage, 0, "nTrainFaces", 0); 151 | *pTrainPersonNumMat = (CvMat *)cvReadByName(fileStorage, 0, "trainPersonNumMat", 0); 152 | eigenValMat = (CvMat *)cvReadByName(fileStorage, 0, "eigenValMat", 0); 153 | projectedTrainFaceMat = (CvMat *)cvReadByName(fileStorage, 0, "projectedTrainFaceMat", 0); 154 | pAvgTrainImg = (IplImage *)cvReadByName(fileStorage, 0, "avgTrainImg", 0); 155 | eigenVectArr = (IplImage **)cvAlloc(nTrainFaces*sizeof(IplImage *)); 156 | for(i=0; idata.fl[iTrain*nEigens + i]; 218 | //distSq += d_i*d_i / eigenValMat->data.fl[i]; // Mahalanobis 219 | distSq += d_i*d_i; // Euclidean 220 | } 221 | 222 | if(distSq < leastDistSq) 223 | { 224 | leastDistSq = distSq; 225 | iNearest = iTrain; 226 | } 227 | } 228 | 229 | return iNearest; 230 | } 231 | 232 | 233 | ////////////////////////////////// 234 | // doPCA() 235 | // 236 | void doPCA() 237 | { 238 | int i; 239 | CvTermCriteria calcLimit; 240 | CvSize faceImgSize; 241 | 242 | // set the number of eigenvalues to use 243 | nEigens = nTrainFaces-1; 244 | 245 | // allocate the eigenvector images 246 | faceImgSize.width = faceImgArr[0]->width; 247 | faceImgSize.height = faceImgArr[0]->height; 248 | eigenVectArr = (IplImage**)cvAlloc(sizeof(IplImage*) * nEigens); 249 | for(i=0; idata.fl); 272 | 273 | cvNormalize(eigenValMat, eigenValMat, 1, 0, CV_L1, 0); 274 | } 275 | 276 | 277 | ////////////////////////////////// 278 | // loadFaceImgArray() 279 | // 280 | int loadFaceImgArray(char * filename) 281 | { 282 | FILE * imgListFile = 0; 283 | char imgFilename[512]; 284 | int iFace, nFaces=0; 285 | 286 | 287 | // open the input file 288 | if( !(imgListFile = fopen(filename, "r")) ) 289 | { 290 | fprintf(stderr, "Can\'t open file %s\n", filename); 291 | return 0; 292 | } 293 | 294 | // count the number of faces 295 | while( fgets(imgFilename, 512, imgListFile) ) ++nFaces; 296 | rewind(imgListFile); 297 | 298 | // allocate the face-image array and person number matrix 299 | faceImgArr = (IplImage **)cvAlloc( nFaces*sizeof(IplImage *) ); 300 | personNumTruthMat = cvCreateMat( 1, nFaces, CV_32SC1 ); 301 | 302 | // store the face images in an array 303 | for(iFace=0; iFacedata.i+iFace, imgFilename); 308 | 309 | // load the face image 310 | faceImgArr[iFace] = cvLoadImage(imgFilename, CV_LOAD_IMAGE_GRAYSCALE); 311 | 312 | if( !faceImgArr[iFace] ) 313 | { 314 | fprintf(stderr, "Can\'t load image from %s\n", imgFilename); 315 | return 0; 316 | } 317 | } 318 | 319 | fclose(imgListFile); 320 | 321 | return nFaces; 322 | } 323 | 324 | 325 | ////////////////////////////////// 326 | // printUsage() 327 | // 328 | void printUsage() 329 | { 330 | printf("Usage: eigenface \n", 331 | " Valid commands are\n" 332 | " train\n" 333 | " test\n"); 334 | } 335 | -------------------------------------------------------------------------------- /research/recognition/test.txt: -------------------------------------------------------------------------------- 1 | 1 s1/1.pgm 2 | 4 s2/2.pgm 3 | -------------------------------------------------------------------------------- /research/recognition/train.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/research/recognition/train.txt -------------------------------------------------------------------------------- /runner/Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | # List gems to bundle here: 3 | gem 'sinatra' 4 | gem 'rest-client' 5 | gem 'daemons' 6 | gem 'spoc' 7 | gem 'shotgun' 8 | -------------------------------------------------------------------------------- /runner/README: -------------------------------------------------------------------------------- 1 | shotgun gem is required for development 2 | 3 | To start the detect runner type: 4 | ruby detect_server.rb 5 | 6 | To start the image server in development mode type: 7 | shotgun -I. image_server.rb 8 | -------------------------------------------------------------------------------- /runner/config/detect_settings.rb.example: -------------------------------------------------------------------------------- 1 | set :ui_server, 'localhost:3000' 2 | set :user, 'changeme' 3 | set :pass, 'changeme' 4 | 5 | set :host, 'localhost' 6 | set :port, 3001 7 | set :file_transfer_port, 4001 8 | 9 | helpers do 10 | def logger 11 | LOGGER 12 | end 13 | end 14 | 15 | configure do 16 | LOGGER = Logger.new("detect_server.log") 17 | end 18 | -------------------------------------------------------------------------------- /runner/config/image_settings_example.example: -------------------------------------------------------------------------------- 1 | set :ui_server, 'localhost:3000' 2 | set :user, 'changeme' 3 | set :pass, 'changeme' 4 | 5 | set :host, 'localhost' 6 | set :port, 3002 7 | 8 | helpers do 9 | def logger 10 | LOGGER 11 | end 12 | end 13 | 14 | configure do 15 | LOGGER = Logger.new("image_server.log") 16 | end 17 | -------------------------------------------------------------------------------- /runner/daemon.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'yaml' 3 | require 'daemons' 4 | require 'rest-client' 5 | require 'spoc' 6 | require 'fileutils' 7 | require 'logger' 8 | 9 | id = ARGV[0] 10 | url = ARGV[1] 11 | $user = ARGV[2] 12 | $pass = ARGV[3] 13 | 14 | # remember and change back to current directory because daemonize chages to '/' 15 | current_directory = File.dirname(File.expand_path($0)) 16 | Daemons.daemonize 17 | FileUtils.cd(current_directory) 18 | 19 | $logger = Logger.new("runner.log") 20 | begin 21 | $logger.info "--------------------------" 22 | $logger.info "Downloading from #{url}" 23 | begin 24 | file_path = Spoc::FileTransfer.download_file(url,"images") 25 | rescue 26 | raise Exception.new("Can not download image") 27 | end 28 | #file_path = "images/test.jpg" 29 | $logger.info "Saved to #{file_path}" 30 | 31 | $logger.info "Scanning" 32 | begin 33 | regions = Spoc::LightCV.find(file_path) 34 | rescue 35 | raise Exception.new("Can not open image to scan for faces") 36 | end 37 | yml = YAML::dump(regions) 38 | 39 | $logger.info "Sending result to image with ID = #{id}" 40 | RestClient.get "http://#{$user}:#{$pass}@localhost:3000/backend/detect_result", {:params => { :image_id => id, :regions => yml}} 41 | 42 | $logger.info "deleting file #{file_path}" 43 | FileUtils.rm(file_path) 44 | $logger.info "--------------------------" 45 | rescue Exception => e 46 | RestClient.get "http://#{$user}:#{$pass}@localhost:3000/backend/detect_result", {:params => { :image_id => id, :error_message => e.message}} 47 | $logger.info e.message 48 | $logger.info "--------------------------" 49 | end 50 | -------------------------------------------------------------------------------- /runner/detect_server.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'rest_client' 4 | require 'shellwords' 5 | require 'logger' 6 | 7 | begin 8 | require './config/detect_settings.rb' 9 | rescue LoadError => e 10 | puts "Edit ./config/detect_settings.rb!" 11 | exit 0 12 | end 13 | 14 | get '/' do 15 | 'detect server' 16 | end 17 | 18 | get '/register' do 19 | params = { 20 | :host => settings.host, 21 | :port => settings.port, 22 | :file_transfer_port => settings.file_transfer_port 23 | } 24 | RestClient.get "http://#{settings.ui_server}/backend/register", {:params => params} 25 | 'done' 26 | end 27 | 28 | get '/detect' do 29 | url = params[:url] 30 | image_id = params[:image_id] 31 | logger.info "Request to download #{url} with image_id #{image_id} received" 32 | 33 | cmd = "ruby daemon.rb #{image_id} #{url.shellescape} #{settings.user.shellescape} #{settings.pass.shellescape}&" 34 | logger.info "Executing: #{cmd}" 35 | system(cmd) 36 | '42' 37 | end 38 | -------------------------------------------------------------------------------- /runner/image_server.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'sinatra' 3 | require 'rest_client' 4 | require 'shellwords' 5 | require 'logger' 6 | 7 | begin 8 | require './config/image_settings.rb' 9 | rescue LoadError => e 10 | puts "Edit ./config/image_settings.rb!" 11 | exit 0 12 | end 13 | 14 | get '/' do 15 | 'image server' 16 | end 17 | 18 | get '/register' do 19 | params = { 20 | :host => settings.host, 21 | :port => settings.port, 22 | :file_transfer_port => settings.file_transfer_port 23 | } 24 | RestClient.get "http://#{settings.ui_server}/backend/register", {:params => params} 25 | 'done' 26 | end 27 | 28 | get '/detect' do 29 | url = params[:url] 30 | image_id = params[:image_id] 31 | logger.info "Request to download #{url} with image_id #{image_id} received" 32 | 33 | cmd = "ruby daemon.rb #{image_id} #{url.shellescape} #{settings.user.shellescape} #{settings.pass.shellescape}&" 34 | logger.info "Executing: #{cmd}" 35 | system(cmd) 36 | '42' 37 | end 38 | -------------------------------------------------------------------------------- /runner/images/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/runner/images/test.jpg -------------------------------------------------------------------------------- /spoc/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | 3 | begin 4 | require 'jeweler' 5 | Jeweler::Tasks.new do |s| 6 | s.name = %q{spoc} 7 | s.summary = %q{Single Point Of Contact Mr. Spok!} 8 | s.description = %q{Nothing yet} 9 | 10 | s.files = ["lib/haarcascade_frontalface_alt_tree.xml","lib/*.rb"] 11 | s.require_path = 'lib' 12 | s.test_files = Dir[*['test/*_test.rb']] 13 | 14 | s.has_rdoc = false 15 | 16 | s.author = 'Cristian Mircea Messel' 17 | s.email = 'mess110@gmail.com' 18 | 19 | s.homepage = "https://github.com/mess110/detection" 20 | 21 | s.platform = Gem::Platform::RUBY 22 | 23 | #s.add_dependency('json') 24 | end 25 | Jeweler::GemcutterTasks.new 26 | rescue LoadError 27 | puts "Jeweler not available. Install it with: gem install jeweler" 28 | end 29 | -------------------------------------------------------------------------------- /spoc/VERSION: -------------------------------------------------------------------------------- 1 | 1.0.1 -------------------------------------------------------------------------------- /spoc/install.sh: -------------------------------------------------------------------------------- 1 | ruby module-light-opencv/extconf.rb 2 | make 3 | sudo make install 4 | sudo rake install 5 | -------------------------------------------------------------------------------- /spoc/lib/file_convert.rb: -------------------------------------------------------------------------------- 1 | require 'base64' 2 | 3 | module Spoc 4 | class FileConvert 5 | def self.encode(file_path) 6 | file = File.open(file_path, "rb") 7 | contents = file.read 8 | file.close 9 | Base64.encode64(contents) 10 | end 11 | 12 | def self.decode(string, output_file_path) 13 | decoded = Base64.decode64(string) 14 | new_file = File.open(output_file_path, "wb") 15 | new_file.write(decoded) 16 | new_file.close 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spoc/lib/file_transfer.rb: -------------------------------------------------------------------------------- 1 | require 'socket' 2 | require 'digest/md5' 3 | require 'fileutils' 4 | require 'open-uri' 5 | require 'net/http' 6 | 7 | module Spoc 8 | class FileTransfer 9 | 10 | FILE_TRANSFER_COMPLETE = "it_ain't_over_till_the_fat_lady_sings\n" 11 | 12 | def self.server(port, dir) 13 | create_dir(dir) 14 | 15 | server = TCPServer.open(port) 16 | loop { 17 | Thread.start(server.accept) do |client| 18 | begin 19 | client_file = "" 20 | while (line = client.gets) 21 | if line == FILE_TRANSFER_COMPLETE 22 | break 23 | end 24 | client_file += line 25 | end 26 | file_path = get_file_path(dir) 27 | Spoc::FileConvert.decode(client_file, file_path) 28 | client.puts file_path 29 | ensure 30 | client.close unless client.nil? 31 | end 32 | end 33 | } 34 | end 35 | 36 | def self.client(host, port, file_path) 37 | begin 38 | file = Spoc::FileConvert.encode(file_path) 39 | s = TCPSocket.open(host, port) 40 | s.puts file 41 | s.puts FILE_TRANSFER_COMPLETE 42 | while line = s.gets 43 | puts line 44 | end 45 | ensure 46 | s.close unless s.nil? 47 | end 48 | end 49 | 50 | def self.download_file(url, dir) 51 | create_dir(dir) 52 | 53 | uri = URI.parse(url) 54 | file_path = get_file_path(dir) 55 | Net::HTTP.start( uri.host ) { |http| 56 | response = http.get( uri.request_uri ) 57 | open(file_path, "wb") { |file| 58 | file.write(response.body) 59 | } 60 | } 61 | file_path 62 | end 63 | 64 | private 65 | 66 | def self.get_file_path(dir) 67 | File.join(dir, Digest::MD5.hexdigest(Time.now.to_s)) 68 | end 69 | 70 | def self.create_dir(dir) 71 | FileUtils.mkdir(dir) unless File.exists? dir 72 | end 73 | 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spoc/lib/light_cv.rb: -------------------------------------------------------------------------------- 1 | require "light_opencv" 2 | include LightOpencv 3 | 4 | module Spoc 5 | 6 | DEFAULT_CASCADE_PATH = File.join(File.dirname(__FILE__), "haarcascade_frontalface_alt_tree.xml") 7 | 8 | class LightCV 9 | class << self 10 | def find( file ) 11 | # TODO: specific exceptions 12 | raise "could not load image" if !File.exists?(file) 13 | raise "could not load cascade" if !File.exists?(DEFAULT_CASCADE_PATH) 14 | 15 | light_opencv_array = detect(file, DEFAULT_CASCADE_PATH) 16 | raise "invalid image" if light_opencv_array == -1 17 | 18 | return format light_opencv_array 19 | end 20 | end 21 | 22 | private 23 | 24 | def self.format light_opencv_array 25 | regions = [] 26 | 0.step(light_opencv_array.size - 1, 4) { |idx| 27 | regions << { 28 | :tlx => light_opencv_array[idx + 0], 29 | :tly => light_opencv_array[idx + 2], 30 | :brx => light_opencv_array[idx + 1], 31 | :bry => light_opencv_array[idx + 3] 32 | } 33 | } 34 | regions 35 | end 36 | end 37 | end 38 | 39 | -------------------------------------------------------------------------------- /spoc/lib/spoc.rb: -------------------------------------------------------------------------------- 1 | require 'light_cv' 2 | require 'file_convert' 3 | require 'file_transfer' 4 | -------------------------------------------------------------------------------- /spoc/module-light-opencv/LightOpencv.c: -------------------------------------------------------------------------------- 1 | // Include the Ruby headers and goodies 2 | #include "ruby.h" 3 | 4 | #include "cv.h" 5 | #include "highgui.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | VALUE LightOpencv = Qnil; 18 | void Init_light_opencv(); 19 | VALUE method_detect(VALUE self, VALUE filename, VALUE cascade_path); 20 | 21 | //const char* cascade_name = "haarcascade_frontalface_alt_tree.xml"; 22 | 23 | void Init_light_opencv() { 24 | LightOpencv = rb_define_module("LightOpencv"); 25 | rb_define_method(LightOpencv, "detect", method_detect, 2); 26 | } 27 | 28 | VALUE method_detect(VALUE self, VALUE filename, VALUE cascade_path) { 29 | VALUE ary = rb_ary_new(); 30 | IplImage *img = cvLoadImage(StringValuePtr(filename),1); 31 | if (!img) 32 | { 33 | fprintf(stderr, "ERROR: Could not load image\n"); 34 | return -1; 35 | } 36 | static CvMemStorage* storage = 0; 37 | static CvHaarClassifierCascade* cascade = 0; 38 | int scale = 1; 39 | int i; 40 | cascade = (CvHaarClassifierCascade*)cvLoad( StringValuePtr(cascade_path), 0, 0, 0 ); 41 | 42 | if( !cascade ) 43 | { 44 | fprintf( stderr, "ERROR: Could not load classifier cascade\n" ); 45 | return ary; 46 | } 47 | 48 | storage = cvCreateMemStorage(0); 49 | cvClearMemStorage( storage ); 50 | 51 | CvSeq* faces = cvHaarDetectObjects( img, cascade, storage, 52 | 1.1, 2, CV_HAAR_DO_CANNY_PRUNING, 53 | cvSize(40, 40) ); 54 | for( i = 0; i < (faces ? faces->total : 0); i++ ) 55 | { 56 | CvRect* r = (CvRect*)cvGetSeqElem( faces, i ); 57 | rb_ary_push(ary, INT2NUM(r->x*scale)); // pt1.x 58 | rb_ary_push(ary, INT2NUM((r->x+r->width)*scale)); // pt2.x 59 | rb_ary_push(ary, INT2NUM(r->y*scale)); // pt1.y 60 | rb_ary_push(ary, INT2NUM((r->y+r->height)*scale)); // pt2.y 61 | } 62 | cvReleaseImage( &img ); 63 | return ary; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /spoc/module-light-opencv/extconf.rb: -------------------------------------------------------------------------------- 1 | # Loads mkmf which is used to make makefiles for Ruby extensions 2 | require 'mkmf' 3 | 4 | # Give it a name 5 | extension_name = 'light_opencv' 6 | 7 | dir_config(extension_name) 8 | 9 | if CONFIG["arch"].include?("darwin") 10 | dir_config("ffcall", "/opt/local/include", "/opt/local/lib") 11 | else 12 | dir_config("ffcall", "/usr/local/include", "/usr/local/lib") 13 | end 14 | 15 | opencv_libraries = ["cxcore", "cv", "highgui"] 16 | 17 | puts ">> check require libraries..." 18 | case CONFIG["arch"] 19 | when /mswin32/ 20 | have_library("msvcrt", nil) 21 | opencv_libraries.each{|lib| 22 | have_library(lib) 23 | } 24 | else 25 | opencv_libraries.each{|lib| 26 | raise "lib#{lib} not found." unless have_library(lib) 27 | } 28 | have_library("stdc++") 29 | end 30 | 31 | # TODO!!! checking for headers 32 | 33 | create_makefile(extension_name) 34 | 35 | # since I don't know how to use mkmf properly, I write the required stuff 36 | # manually 37 | # This adds -L and -I flags 38 | def get_file_as_string(filename) 39 | data = '' 40 | f = File.open(filename, "r") 41 | f.each_line do |line| 42 | if line =~ /^ldflags\ \ \=\ \-\L\./ 43 | data += line.gsub("\n","") + " #{`pkg-config --libs opencv`}\n" 44 | elsif line =~ /^INCFLAGS\ \=\ \-\I\./ 45 | data += line.gsub("\n","") + " #{`pkg-config --cflags opencv`}\n" 46 | else 47 | data += line 48 | end 49 | end 50 | return data 51 | end 52 | 53 | makefile = get_file_as_string 'Makefile' 54 | File.open("Makefile","w"){ |f| f.puts makefile } 55 | -------------------------------------------------------------------------------- /spoc/spec/light_cv_spec.rb: -------------------------------------------------------------------------------- 1 | require 'light_cv' 2 | 3 | describe Spoc::LightCV do 4 | it "should be installed" do 5 | lambda { require 'light_opencv' }.should_not raise_error 6 | end 7 | 8 | it "should return 3 faces" do 9 | file_path = File.join(File.dirname(__FILE__), "test.jpg") 10 | faces = Spoc::LightCV.find(file_path) 11 | faces.should be_an_instance_of Array 12 | faces.count.should be 3 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spoc/spec/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/spoc/spec/test.jpg -------------------------------------------------------------------------------- /ubuntu_setup.sh: -------------------------------------------------------------------------------- 1 | DETECTION_DIRECTORY=$(cd `dirname $0` && pwd) 2 | 3 | # installing dependecies 4 | sudo apt-get update 5 | sudo apt-get upgrade 6 | sudo apt-get install git-core subversion cmake build-essential libgtk2.0-dev libjpeg-dev pkg-config postgresql libpq-dev libopenssl-ruby 7 | 8 | # setting up opencv 9 | mkdir ~/opencv 10 | cd ~/opencv 11 | svn co https://code.ros.org/svn/opencv/tags/latest_tested_snapshot 12 | cd latest_tested_snapshot/opencv 13 | mkdir release 14 | cd release 15 | cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local .. 16 | make 17 | sudo make install 18 | sudo ldconfig 19 | 20 | # installing light plugin 21 | sudo gem install jeweler rake 22 | cd $DETECTION_DIRECTORY 23 | cd spoc 24 | sudo sh install.sh 25 | 26 | cd $DETECTION_DIRECTORY 27 | cd runner 28 | sudo bundle install 29 | 30 | cd $DETECTION_DIRECTORY 31 | cd ui-server 32 | sudo bundle install 33 | -------------------------------------------------------------------------------- /ui-server/.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | db/*.sqlite3 3 | log/*.log 4 | tmp/**/* 5 | -------------------------------------------------------------------------------- /ui-server/Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '3.0.3' 4 | 5 | # Bundle edge Rails instead: 6 | # gem 'rails', :git => 'git://github.com/rails/rails.git' 7 | 8 | gem 'pg' 9 | 10 | # Use unicorn as the web server 11 | # gem 'unicorn' 12 | 13 | # Deploy with Capistrano 14 | # gem 'capistrano' 15 | 16 | # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) 17 | # gem 'ruby-debug' 18 | # gem 'ruby-debug19' 19 | 20 | # Bundle the extra gems: 21 | # gem 'bj' 22 | # gem 'nokogiri' 23 | # gem 'sqlite3-ruby', :require => 'sqlite3' 24 | # gem 'aws-s3', :require => 'aws/s3' 25 | gem 'rest-client' 26 | gem 'daemons' 27 | gem 'haml' 28 | gem 'json' 29 | 30 | # Bundle gems for the local environment. Make sure to 31 | # put test-only gems in this group so their generators 32 | # and rake tasks are available in development mode: 33 | group :development, :test do 34 | # gem 'webrat' 35 | gem 'mocha' 36 | gem 'fakeweb' 37 | gem 'factory_girl' 38 | end 39 | -------------------------------------------------------------------------------- /ui-server/README: -------------------------------------------------------------------------------- 1 | == Welcome to Rails 2 | 3 | Rails is a web-application framework that includes everything needed to create 4 | database-backed web applications according to the Model-View-Control pattern. 5 | 6 | This pattern splits the view (also called the presentation) into "dumb" 7 | templates that are primarily responsible for inserting pre-built data in between 8 | HTML tags. The model contains the "smart" domain objects (such as Account, 9 | Product, Person, Post) that holds all the business logic and knows how to 10 | persist themselves to a database. The controller handles the incoming requests 11 | (such as Save New Account, Update Product, Show Post) by manipulating the model 12 | and directing data to the view. 13 | 14 | In Rails, the model is handled by what's called an object-relational mapping 15 | layer entitled Active Record. This layer allows you to present the data from 16 | database rows as objects and embellish these data objects with business logic 17 | methods. You can read more about Active Record in 18 | link:files/vendor/rails/activerecord/README.html. 19 | 20 | The controller and view are handled by the Action Pack, which handles both 21 | layers by its two parts: Action View and Action Controller. These two layers 22 | are bundled in a single package due to their heavy interdependence. This is 23 | unlike the relationship between the Active Record and Action Pack that is much 24 | more separate. Each of these packages can be used independently outside of 25 | Rails. You can read more about Action Pack in 26 | link:files/vendor/rails/actionpack/README.html. 27 | 28 | 29 | == Getting Started 30 | 31 | 1. At the command prompt, create a new Rails application: 32 | rails new myapp (where myapp is the application name) 33 | 34 | 2. Change directory to myapp and start the web server: 35 | cd myapp; rails server (run with --help for options) 36 | 37 | 3. Go to http://localhost:3000/ and you'll see: 38 | "Welcome aboard: You're riding Ruby on Rails!" 39 | 40 | 4. Follow the guidelines to start developing your application. You can find 41 | the following resources handy: 42 | 43 | * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html 44 | * Ruby on Rails Tutorial Book: http://www.railstutorial.org/ 45 | 46 | 47 | == Debugging Rails 48 | 49 | Sometimes your application goes wrong. Fortunately there are a lot of tools that 50 | will help you debug it and get it back on the rails. 51 | 52 | First area to check is the application log files. Have "tail -f" commands 53 | running on the server.log and development.log. Rails will automatically display 54 | debugging and runtime information to these files. Debugging info will also be 55 | shown in the browser on requests from 127.0.0.1. 56 | 57 | You can also log your own messages directly into the log file from your code 58 | using the Ruby logger class from inside your controllers. Example: 59 | 60 | class WeblogController < ActionController::Base 61 | def destroy 62 | @weblog = Weblog.find(params[:id]) 63 | @weblog.destroy 64 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") 65 | end 66 | end 67 | 68 | The result will be a message in your log file along the lines of: 69 | 70 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! 71 | 72 | More information on how to use the logger is at http://www.ruby-doc.org/core/ 73 | 74 | Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are 75 | several books available online as well: 76 | 77 | * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) 78 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) 79 | 80 | These two books will bring you up to speed on the Ruby language and also on 81 | programming in general. 82 | 83 | 84 | == Debugger 85 | 86 | Debugger support is available through the debugger command when you start your 87 | Mongrel or WEBrick server with --debugger. This means that you can break out of 88 | execution at any point in the code, investigate and change the model, and then, 89 | resume execution! You need to install ruby-debug to run the server in debugging 90 | mode. With gems, use sudo gem install ruby-debug. Example: 91 | 92 | class WeblogController < ActionController::Base 93 | def index 94 | @posts = Post.find(:all) 95 | debugger 96 | end 97 | end 98 | 99 | So the controller will accept the action, run the first line, then present you 100 | with a IRB prompt in the server window. Here you can do things like: 101 | 102 | >> @posts.inspect 103 | => "[#nil, "body"=>nil, "id"=>"1"}>, 105 | #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" 107 | >> @posts.first.title = "hello from a debugger" 108 | => "hello from a debugger" 109 | 110 | ...and even better, you can examine how your runtime objects actually work: 111 | 112 | >> f = @posts.first 113 | => #nil, "body"=>nil, "id"=>"1"}> 114 | >> f. 115 | Display all 152 possibilities? (y or n) 116 | 117 | Finally, when you're ready to resume execution, you can enter "cont". 118 | 119 | 120 | == Console 121 | 122 | The console is a Ruby shell, which allows you to interact with your 123 | application's domain model. Here you'll have all parts of the application 124 | configured, just like it is when the application is running. You can inspect 125 | domain models, change values, and save to the database. Starting the script 126 | without arguments will launch it in the development environment. 127 | 128 | To start the console, run rails console from the application 129 | directory. 130 | 131 | Options: 132 | 133 | * Passing the -s, --sandbox argument will rollback any modifications 134 | made to the database. 135 | * Passing an environment name as an argument will load the corresponding 136 | environment. Example: rails console production. 137 | 138 | To reload your controllers and models after launching the console run 139 | reload! 140 | 141 | More information about irb can be found at: 142 | link:http://www.rubycentral.com/pickaxe/irb.html 143 | 144 | 145 | == dbconsole 146 | 147 | You can go to the command line of your database directly through rails 148 | dbconsole. You would be connected to the database with the credentials 149 | defined in database.yml. Starting the script without arguments will connect you 150 | to the development database. Passing an argument will connect you to a different 151 | database, like rails dbconsole production. Currently works for MySQL, 152 | PostgreSQL and SQLite 3. 153 | 154 | == Description of Contents 155 | 156 | The default directory structure of a generated Ruby on Rails application: 157 | 158 | |-- app 159 | | |-- controllers 160 | | |-- helpers 161 | | |-- mailers 162 | | |-- models 163 | | `-- views 164 | | `-- layouts 165 | |-- config 166 | | |-- environments 167 | | |-- initializers 168 | | `-- locales 169 | |-- db 170 | |-- doc 171 | |-- lib 172 | | `-- tasks 173 | |-- log 174 | |-- public 175 | | |-- images 176 | | |-- javascripts 177 | | `-- stylesheets 178 | |-- script 179 | |-- test 180 | | |-- fixtures 181 | | |-- functional 182 | | |-- integration 183 | | |-- performance 184 | | `-- unit 185 | |-- tmp 186 | | |-- cache 187 | | |-- pids 188 | | |-- sessions 189 | | `-- sockets 190 | `-- vendor 191 | `-- plugins 192 | 193 | app 194 | Holds all the code that's specific to this particular application. 195 | 196 | app/controllers 197 | Holds controllers that should be named like weblogs_controller.rb for 198 | automated URL mapping. All controllers should descend from 199 | ApplicationController which itself descends from ActionController::Base. 200 | 201 | app/models 202 | Holds models that should be named like post.rb. Models descend from 203 | ActiveRecord::Base by default. 204 | 205 | app/views 206 | Holds the template files for the view that should be named like 207 | weblogs/index.html.erb for the WeblogsController#index action. All views use 208 | eRuby syntax by default. 209 | 210 | app/views/layouts 211 | Holds the template files for layouts to be used with views. This models the 212 | common header/footer method of wrapping views. In your views, define a layout 213 | using the layout :default and create a file named default.html.erb. 214 | Inside default.html.erb, call <% yield %> to render the view using this 215 | layout. 216 | 217 | app/helpers 218 | Holds view helpers that should be named like weblogs_helper.rb. These are 219 | generated for you automatically when using generators for controllers. 220 | Helpers can be used to wrap functionality for your views into methods. 221 | 222 | config 223 | Configuration files for the Rails environment, the routing map, the database, 224 | and other dependencies. 225 | 226 | db 227 | Contains the database schema in schema.rb. db/migrate contains all the 228 | sequence of Migrations for your schema. 229 | 230 | doc 231 | This directory is where your application documentation will be stored when 232 | generated using rake doc:app 233 | 234 | lib 235 | Application specific libraries. Basically, any kind of custom code that 236 | doesn't belong under controllers, models, or helpers. This directory is in 237 | the load path. 238 | 239 | public 240 | The directory available for the web server. Contains subdirectories for 241 | images, stylesheets, and javascripts. Also contains the dispatchers and the 242 | default HTML files. This should be set as the DOCUMENT_ROOT of your web 243 | server. 244 | 245 | script 246 | Helper scripts for automation and generation. 247 | 248 | test 249 | Unit and functional tests along with fixtures. When using the rails generate 250 | command, template test files will be generated for you and placed in this 251 | directory. 252 | 253 | vendor 254 | External libraries that the application depends on. Also includes the plugins 255 | subdirectory. If the app has frozen rails, those gems also go here, under 256 | vendor/rails/. This directory is in the load path. 257 | -------------------------------------------------------------------------------- /ui-server/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | require 'rake' 6 | 7 | UiServer::Application.load_tasks 8 | -------------------------------------------------------------------------------- /ui-server/app/controllers/api/v2/detect_controller.rb: -------------------------------------------------------------------------------- 1 | #http://sphotos.ak.fbcdn.net/photos-ak-snc1/v272/12/29/1352704123/n1352704123_30009292_5638.jpg 2 | class Api::V2::DetectController < ApplicationController 3 | 4 | protect_from_forgery :except => :new 5 | 6 | def new 7 | if request.get? 8 | if params[:url].nil? 9 | render_api_error('invalid_url', 'url parameter required') and return 10 | end 11 | 12 | if @img = Image.find_by_url(params[:url]) 13 | if @img.failed? 14 | render_api_error('invalid_image', @img.failures.last.message) and return 15 | end 16 | 17 | @eta = 0 18 | else 19 | @img = Image.new(:url => params[:url]) 20 | 21 | if !@img.valid? 22 | render_api_error(@img.errors[:url].first, @img.errors[:url].first) and return 23 | end 24 | 25 | @eta = Scheduler.process_image @img 26 | if @eta == Scheduler::RUNNERS_FULL 27 | render_api_error('over_capacity', "Our runners are full. Please try again later.") and return 28 | end 29 | end 30 | 31 | render :partial => "new" and return 32 | elsif request.post? 33 | # TODO: remove this when implemented 34 | render_api_error('invalid_request', 'not yet implemented') and return 35 | 36 | if params[:file].nil? 37 | render_api_error('invalid_url', 'file parameter required') and return 38 | end 39 | 40 | filename = params[:file].original_filename 41 | path = File.join("public", filename) 42 | file_content = params[:file].read 43 | File.open(path, "wb") { |f| f.write(file_content) } 44 | render_api_error('invalid_request', "console output") and return 45 | else 46 | render_api_error('invalid_request', 'put and delete now allowed') and return 47 | end 48 | end 49 | 50 | end 51 | -------------------------------------------------------------------------------- /ui-server/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | 4 | def render_api_error(error_code, error_description) 5 | render :partial => "api/v2/detect/error", :locals => { :params => { :error_code => error_code, :error_description => error_description }} 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /ui-server/app/controllers/backend/detect_result_controller.rb: -------------------------------------------------------------------------------- 1 | class Backend::DetectResultController < ApplicationController 2 | USER_ID, PASSWORD = "changeme", "changeme" 3 | 4 | # Require authentication only for edit and delete operation 5 | before_filter :authenticate, :only => [ :report ] 6 | 7 | def report 8 | image_id = params[:image_id] 9 | if params[:error_message].present? 10 | img = Image.find(image_id) 11 | img.failures << Failure.create!(:message => params[:error_message]) 12 | else 13 | regions = YAML::load(params[:regions]) 14 | regions.each do |r| 15 | Region.create!(:image_id => image_id, :tlx => r[:tlx], :tly => r[:tly], :brx => r[:brx], :bry => r[:bry]) 16 | end 17 | Image.find(image_id).complete! 18 | end 19 | render :text => "200" 20 | end 21 | 22 | private 23 | 24 | def authenticate 25 | authenticate_or_request_with_http_basic do |id, password| 26 | id == USER_ID && password == PASSWORD 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /ui-server/app/controllers/backend/scheduler_controller.rb: -------------------------------------------------------------------------------- 1 | class Backend::SchedulerController < ApplicationController 2 | def index 3 | end 4 | 5 | def register 6 | Runner.create!({:host => params[:host], :port => params[:port], 7 | :file_transfer_port => params[:file_transfer_port]}) 8 | render :text => "200" 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /ui-server/app/controllers/web_controller.rb: -------------------------------------------------------------------------------- 1 | class WebController < ApplicationController 2 | DEMO_URL = "http://sphotos.ak.fbcdn.net/photos-ak-snc1/v272/12/29/1352704123/n1352704123_30009292_5638.jpg" 3 | 4 | def index 5 | #@fln = JSON.parse(RestClient.get "http://github.com/api/v2/json/repos/show/mess110/detection") || 0 6 | @images = Image.samples 7 | end 8 | 9 | def demo 10 | @demo_url = DEMO_URL 11 | if !params[:demo_url].nil? 12 | @demo_url = params[:demo_url] 13 | end 14 | @images = Image.samples 15 | end 16 | 17 | def info 18 | @img = Image.new(:url => "http://url.to.my/image.jpg") 19 | @img.id = 42 20 | regions = [Region.new(:tlx => 10, :tly => 20, :brx => 70, :bry => 80), 21 | Region.new(:tlx => 60, :tly => 10, :brx => 80, :bry => 90)] 22 | @img.regions = regions 23 | @eta = 2 24 | end 25 | 26 | def solutions 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /ui-server/app/helpers/api/v2/detect_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::V2::DetectHelper 2 | def format_error_description description 3 | case description 4 | when Image::INVALID_PROTOCOL 5 | "The only allowed protocols are http and https" 6 | when Image::INVALID_IMAGE_FORMAT 7 | "Only URLs to jpg/jpeg images" 8 | else 9 | description 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /ui-server/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /ui-server/app/helpers/backend/detect_result_helper.rb: -------------------------------------------------------------------------------- 1 | module Backend::DetectResultHelper 2 | end 3 | -------------------------------------------------------------------------------- /ui-server/app/helpers/backend/scheduler_helper.rb: -------------------------------------------------------------------------------- 1 | module Backend::SchedulerHelper 2 | end 3 | -------------------------------------------------------------------------------- /ui-server/app/helpers/web_helper.rb: -------------------------------------------------------------------------------- 1 | module WebHelper 2 | end 3 | -------------------------------------------------------------------------------- /ui-server/app/models/failure.rb: -------------------------------------------------------------------------------- 1 | class Failure < ActiveRecord::Base 2 | belongs_to :image 3 | end 4 | -------------------------------------------------------------------------------- /ui-server/app/models/image.rb: -------------------------------------------------------------------------------- 1 | class Image < ActiveRecord::Base 2 | 3 | INVALID_PROTOCOL = "invalid_protocol" 4 | INVALID_IMAGE_FORMAT = "invalid_image_fornat" 5 | 6 | STATUS_COMPLETED = "completed" 7 | STATUS_FAILED = "failed" 8 | STATUS_PROCESSING = "processing" 9 | 10 | has_many :regions 11 | has_many :failures 12 | belongs_to :runner, :counter_cache => true 13 | 14 | validates_format_of :url, 15 | :with => URI::regexp(%w(http https)), :message => INVALID_PROTOCOL 16 | validates_format_of :url, 17 | :with => /.*\.(jpg|jpeg)$/i, :message => INVALID_IMAGE_FORMAT 18 | 19 | scope :samples, lambda { 20 | where('completed = ? and karma >= ?', true, 1.0).order("images.created_at DESC").limit(6) 21 | } 22 | 23 | def complete! 24 | self.completed = true 25 | self.save! 26 | end 27 | 28 | def failed? 29 | self.failures.count != 0 30 | end 31 | 32 | def status 33 | if completed? 34 | return STATUS_COMPLETED 35 | elsif failed? 36 | return STATUS_FAILED 37 | else 38 | return STATUS_PROCESSING 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ui-server/app/models/region.rb: -------------------------------------------------------------------------------- 1 | class Region < ActiveRecord::Base 2 | belongs_to :image 3 | end 4 | -------------------------------------------------------------------------------- /ui-server/app/models/runner.rb: -------------------------------------------------------------------------------- 1 | class Runner < ActiveRecord::Base 2 | 3 | has_many :images 4 | 5 | scope :free, lambda { 6 | Runner.all.select{ |r| r if r.has_free_jobs } 7 | } 8 | 9 | def has_free_jobs 10 | max_jobs_per_minute > jobs_per_minute 11 | end 12 | 13 | private 14 | 15 | def jobs_per_minute 16 | Image.where('created_at > ? AND runner_id = ?', Time.now - 60, id).count 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /ui-server/app/views/api/v2/detect/_error.builder: -------------------------------------------------------------------------------- 1 | xml.error do 2 | xml.code params[:error_code] 3 | xml.description format_error_description params[:error_description] 4 | end 5 | -------------------------------------------------------------------------------- /ui-server/app/views/api/v2/detect/_new.builder: -------------------------------------------------------------------------------- 1 | xml.image do 2 | xml.id @img.id 3 | xml.status @img.status 4 | xml.url @img.url 5 | if @img.completed? 6 | xml.regions do 7 | @img.regions.each do |r| 8 | xml.region( :top_left_x => r[:tlx], 9 | :top_left_y => r[:tly], 10 | :bottom_right_x => r[:brx], 11 | :bottom_right_y => r[:bry]) 12 | end 13 | end 14 | else 15 | xml.estimated_time_arrival @eta 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /ui-server/app/views/backend/scheduler/_runner.haml: -------------------------------------------------------------------------------- 1 | %td= runner.id 2 | %td= runner.host 3 | %td= runner.port 4 | %td= runner.file_transfer_port 5 | %td= runner.images_count 6 | -------------------------------------------------------------------------------- /ui-server/app/views/backend/scheduler/index.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <% Runner.all.each do |r| %> 11 | <%= render :partial => "runner.haml", :locals => { :runner => r } %> 12 | <% end %> 13 | 14 |
idhostportfile_portimages
15 | -------------------------------------------------------------------------------- /ui-server/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | Face Detection Made Simple. Free! 14 | <%= stylesheet_link_tag "sinatra", :media => "screen,projection" %> 15 | <%= javascript_include_tag 'detect_client' %> 16 | <%= javascript_include_tag 'detect_result_parser' %> 17 | <%= javascript_include_tag :defaults %> 18 | 19 | 20 |
21 | 22 | Fork me on GitHub 23 | 34 |
35 | <%= yield %> 36 | email: detection-api@googlegroups.com 37 |
38 | 41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /ui-server/app/views/web/_canvas.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | Your Browser is not supported! We recommend Chromium, Mozilla Firefox, Google Chrome or Opera 6 | 7 | -------------------------------------------------------------------------------- /ui-server/app/views/web/_donate.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | 5 | 6 | 7 |
8 | 9 | -------------------------------------------------------------------------------- /ui-server/app/views/web/_footer.html.erb: -------------------------------------------------------------------------------- 1 | 9 | 12 | -------------------------------------------------------------------------------- /ui-server/app/views/web/_other_user_images.erb: -------------------------------------------------------------------------------- 1 |
2 | 21 |
22 | -------------------------------------------------------------------------------- /ui-server/app/views/web/_urlbox.erb: -------------------------------------------------------------------------------- 1 |

2 | URL 3 | 5 | TEST
6 |

7 | -------------------------------------------------------------------------------- /ui-server/app/views/web/demo.erb: -------------------------------------------------------------------------------- 1 | <%= render :partial => "other_user_images", :locals => { :is_on_demo_page => true } %> 2 |
3 |
4 | <%= render :partial => "urlbox" %> 5 |

Try one of the images above (that users scanned) or provide your own URL (to a jpeg)

6 | <%= render :partial => "canvas" %> 7 |
8 |
9 | 15 | -------------------------------------------------------------------------------- /ui-server/app/views/web/index.erb: -------------------------------------------------------------------------------- 1 |
2 |

The API explained

3 | 4 |
5 |
6 |

face detection made simple

7 |
8 |

The API allows you to find the positions of frontal faces in JPEG images. Until now, there was no easy to way to do this. And it is only one HTTP request away.

9 |
10 |
11 |

The API lets you tell it what image to analyze and returns coordinates of the faces found.

12 |

To find out more about how the API works or how to use it, please read the info page or the wiki.

13 |
14 |
15 |

This is an open source project so feel free to contribute. Join the mailing list, report issues, fork the github project etc.

16 |

17 |
18 |
19 | -------------------------------------------------------------------------------- /ui-server/app/views/web/info.erb: -------------------------------------------------------------------------------- 1 |

API Overview

2 |

The API solves the complex problem of detecting the position of faces in images. This is achieved transparent to the user and since it is offered as an API, all the user needs to do is make a HTTP Request.

3 |

Details about how everything works can be found behind the scenes.

4 |

This service is free and it will stay free! If you want you can donate to help with hosting/beer fees. (student life is fun)

5 |
<%= render :partial => "donate" %>
6 |
7 |

Examples usage - using curl

8 |
9 |

detect request - initiates a new face detection request

10 |

Detecting faces in images is achieved when a user makes a new detect request to the API.

11 |

12 | curl -X GET 'http://detection.myvhost.de/api/v2/detect/new?url=[http://url.to.my/image.jpg]'
13 | 
14 |

Below you will find an example response. One of the more interesting nodes is <%= "" %>. The value is processing, which means the process is not completed but the request is.

15 |

16 | <%= render :partial => "api/v2/detect/new.builder" %>
17 | 
18 |
19 |

detect request (again) - returns the coordinates of faces from the detect request

20 |

Since we did not get the results yet, we will make the same request.

21 |

22 | curl -X GET 'http://detection.myvhost.de/api/v2/detect/new?url=[http://url.to.my/image.jpg]'
23 | 
24 |

Below we notice the response changed. <%= "" %> is now "completed" and the regions array contains the coordinates of faces.

25 |

26 | <% @img.completed = true %>
27 | <%= render :partial => "api/v2/detect/new.builder" %>
28 | 
29 |
30 |

31 |

error response - returned an error from a detect request

32 |

In case of an error (eg: URL doesn't point to jpeg) the fallowing response will be shown to the user.

33 |

34 | <%= render :partial => "api/v2/detect/error.builder", :locals => { :params => {:error_code => "invalid_image", :error_description => "You will find a description here. The code node is also useful!"}} %>
35 | 
36 | 37 |

Any questions?

38 | -------------------------------------------------------------------------------- /ui-server/app/views/web/solutions.erb: -------------------------------------------------------------------------------- 1 |
2 |

This page is dedicated to custom made solutions.

3 |

Please don't hesitate to contact us.

4 |
5 | -------------------------------------------------------------------------------- /ui-server/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run UiServer::Application 5 | -------------------------------------------------------------------------------- /ui-server/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # If you have a Gemfile, require the gems listed there, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(:default, Rails.env) if defined?(Bundler) 8 | 9 | module UiServer 10 | class Application < Rails::Application 11 | config.autoload_paths += Dir["#{config.root}/lib/**/"] 12 | # Settings in config/environments/* take precedence over those specified here. 13 | # Application configuration should go into files in config/initializers 14 | # -- all .rb files in that directory are automatically loaded. 15 | 16 | # Custom directories with classes and modules you want to be autoloadable. 17 | # config.autoload_paths += %W(#{config.root}/extras) 18 | 19 | # Only load the plugins named here, in the order given (default is alphabetical). 20 | # :all can be used as a placeholder for all plugins not explicitly named. 21 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 22 | 23 | # Activate observers that should always be running. 24 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 25 | 26 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 27 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 28 | # config.time_zone = 'Central Time (US & Canada)' 29 | 30 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 31 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 32 | # config.i18n.default_locale = :de 33 | 34 | # JavaScript files you want as :defaults (application.js is always included). 35 | # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) 36 | 37 | # Configure the default encoding used in templates for Ruby 1.9. 38 | config.encoding = "utf-8" 39 | 40 | # Configure sensitive parameters which will be filtered from the log file. 41 | config.filter_parameters += [:password] 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /ui-server/config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | gemfile = File.expand_path('../../Gemfile', __FILE__) 5 | begin 6 | ENV['BUNDLE_GEMFILE'] = gemfile 7 | require 'bundler' 8 | Bundler.setup 9 | rescue Bundler::GemNotFound => e 10 | STDERR.puts e.message 11 | STDERR.puts "Try running `bundle install`." 12 | exit! 13 | end if File.exist?(gemfile) 14 | -------------------------------------------------------------------------------- /ui-server/config/database.yml.example: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 7.4 and 8.x are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On Mac OS X with macports: 6 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 7 | # On Windows: 8 | # gem install pg 9 | # Choose the win32 build. 10 | # Install PostgreSQL and put its /bin directory on your path. 11 | development: 12 | adapter: postgresql 13 | encoding: unicode 14 | database: ui-server_development 15 | pool: 5 16 | username: ui-server 17 | password: 18 | 19 | # Connect on a TCP socket. Omitted by default since the client uses a 20 | # domain socket that doesn't need configuration. Windows does not have 21 | # domain sockets, so uncomment these lines. 22 | #host: localhost 23 | #port: 5432 24 | 25 | # Schema search path. The server defaults to $user,public 26 | #schema_search_path: myapp,sharedapp,public 27 | 28 | # Minimum log levels, in increasing order: 29 | # debug5, debug4, debug3, debug2, debug1, 30 | # log, notice, warning, error, fatal, and panic 31 | # The server defaults to notice. 32 | #min_messages: warning 33 | 34 | # Warning: The database defined as "test" will be erased and 35 | # re-generated from your development database when you run "rake". 36 | # Do not set this db to the same as development or production. 37 | test: 38 | adapter: postgresql 39 | encoding: unicode 40 | database: ui-server_test 41 | pool: 5 42 | username: ui-server 43 | password: 44 | 45 | production: 46 | adapter: postgresql 47 | encoding: unicode 48 | database: ui-server_production 49 | pool: 5 50 | username: ui-server 51 | password: 52 | -------------------------------------------------------------------------------- /ui-server/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | UiServer::Application.initialize! 6 | -------------------------------------------------------------------------------- /ui-server/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | UiServer::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the webserver when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_view.debug_rjs = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Don't care if the mailer can't send 18 | config.action_mailer.raise_delivery_errors = false 19 | 20 | # Print deprecation notices to the Rails logger 21 | config.active_support.deprecation = :log 22 | 23 | # Only use best-standards-support built into browsers 24 | config.action_dispatch.best_standards_support = :builtin 25 | end 26 | 27 | -------------------------------------------------------------------------------- /ui-server/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | UiServer::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The production environment is meant for finished, "live" apps. 5 | # Code is not reloaded between requests 6 | config.cache_classes = true 7 | 8 | # Full error reports are disabled and caching is turned on 9 | config.consider_all_requests_local = false 10 | config.action_controller.perform_caching = true 11 | 12 | # Specifies the header that your server uses for sending files 13 | config.action_dispatch.x_sendfile_header = "X-Sendfile" 14 | 15 | # For nginx: 16 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' 17 | 18 | # If you have no front-end server that supports something like X-Sendfile, 19 | # just comment this out and Rails will serve the files 20 | 21 | # See everything in the log (default is :info) 22 | # config.log_level = :debug 23 | 24 | # Use a different logger for distributed setups 25 | # config.logger = SyslogLogger.new 26 | 27 | # Use a different cache store in production 28 | # config.cache_store = :mem_cache_store 29 | 30 | # Disable Rails's static asset server 31 | # In production, Apache or nginx will already do this 32 | config.serve_static_assets = false 33 | 34 | # Enable serving of images, stylesheets, and javascripts from an asset server 35 | # config.action_controller.asset_host = "http://assets.example.com" 36 | 37 | # Disable delivery errors, bad email addresses will be ignored 38 | # config.action_mailer.raise_delivery_errors = false 39 | 40 | # Enable threaded mode 41 | # config.threadsafe! 42 | 43 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 44 | # the I18n.default_locale when a translation can not be found) 45 | config.i18n.fallbacks = true 46 | 47 | # Send deprecation notices to registered listeners 48 | config.active_support.deprecation = :notify 49 | end 50 | -------------------------------------------------------------------------------- /ui-server/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | UiServer::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Log error messages when you accidentally call methods on nil. 11 | config.whiny_nils = true 12 | 13 | # Show full error reports and disable caching 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Raise exceptions instead of rendering exception templates 18 | config.action_dispatch.show_exceptions = false 19 | 20 | # Disable request forgery protection in test environment 21 | config.action_controller.allow_forgery_protection = false 22 | 23 | # Tell Action Mailer not to deliver emails to the real world. 24 | # The :test delivery method accumulates sent emails in the 25 | # ActionMailer::Base.deliveries array. 26 | config.action_mailer.delivery_method = :test 27 | 28 | # Use SQL instead of Active Record's schema dumper when creating the test database. 29 | # This is necessary if your schema can't be completely dumped by the schema dumper, 30 | # like if you have constraints or database-specific column types 31 | # config.active_record.schema_format = :sql 32 | 33 | # Print deprecation notices to the stderr 34 | config.active_support.deprecation = :stderr 35 | end 36 | -------------------------------------------------------------------------------- /ui-server/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /ui-server/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /ui-server/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /ui-server/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | UiServer::Application.config.secret_token = '878beb8fb780509b37cb2545d8049661a836ba4207b71b6b2204bb1bf4e5ceae9f71e8c1054ef38451dbd373cdc227b38698c4f9924f1c51ce76cd8c645864af' 8 | -------------------------------------------------------------------------------- /ui-server/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | UiServer::Application.config.session_store :cookie_store, :key => '_ui-server_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # UiServer::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /ui-server/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /ui-server/config/routes.rb: -------------------------------------------------------------------------------- 1 | UiServer::Application.routes.draw do 2 | 3 | match 'home' => 'web#index' 4 | match 'solutions' => 'web#solutions' 5 | match 'info' => 'web#info' 6 | match 'demo' => 'web#demo' 7 | 8 | match 'api/v2/detect/new' => 'api/v2/detect#new' 9 | match 'api/v2/detect/show' => 'api/v2/detect#show' 10 | match 'backend' => 'backend/scheduler#index' 11 | match 'backend/detect_result' => 'backend/detect_result#report' 12 | match 'backend/register' => 'backend/scheduler#register' 13 | # The priority is based upon order of creation: 14 | # first created -> highest priority. 15 | 16 | # Sample of regular route: 17 | # match 'products/:id' => 'catalog#view' 18 | # Keep in mind you can assign values other than :controller and :action 19 | 20 | # Sample of named route: 21 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 22 | # This route can be invoked with purchase_url(:id => product.id) 23 | 24 | # Sample resource route (maps HTTP verbs to controller actions automatically): 25 | # resources :products 26 | 27 | # Sample resource route with options: 28 | # resources :products do 29 | # member do 30 | # get 'short' 31 | # post 'toggle' 32 | # end 33 | # 34 | # collection do 35 | # get 'sold' 36 | # end 37 | # end 38 | 39 | # Sample resource route with sub-resources: 40 | # resources :products do 41 | # resources :comments, :sales 42 | # resource :seller 43 | # end 44 | 45 | # Sample resource route with more complex sub-resources 46 | # resources :products do 47 | # resources :comments 48 | # resources :sales do 49 | # get 'recent', :on => :collection 50 | # end 51 | # end 52 | 53 | # Sample resource route within a namespace: 54 | # namespace :admin do 55 | # # Directs /admin/products/* to Admin::ProductsController 56 | # # (app/controllers/admin/products_controller.rb) 57 | # resources :products 58 | # end 59 | 60 | # You can have the root of your site routed with "root" 61 | # just remember to delete public/index.html. 62 | root :to => "web#index" 63 | 64 | # See how all your routes lay out with "rake routes" 65 | 66 | # This is a legacy wild controller route that's not recommended for RESTful applications. 67 | # Note: This route will make all actions in every controller accessible via GET requests. 68 | match ':controller(/:action(/:id(.:format)))' 69 | end 70 | -------------------------------------------------------------------------------- /ui-server/db/migrate/20110107183219_create_runners.rb: -------------------------------------------------------------------------------- 1 | class CreateRunners < ActiveRecord::Migration 2 | def self.up 3 | create_table :runners do |t| 4 | t.string :host 5 | t.integer :port 6 | t.integer :file_transfer_port 7 | t.timestamps 8 | end 9 | end 10 | 11 | def self.down 12 | drop_table :runners 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /ui-server/db/migrate/20110107194812_create_images.rb: -------------------------------------------------------------------------------- 1 | class CreateImages < ActiveRecord::Migration 2 | def self.up 3 | create_table :images do |t| 4 | t.string :url 5 | t.boolean :completed, :default => false 6 | t.boolean :failed, :default => false 7 | t.timestamps 8 | end 9 | end 10 | 11 | def self.down 12 | drop_table :images 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /ui-server/db/migrate/20110107194829_create_regions.rb: -------------------------------------------------------------------------------- 1 | class CreateRegions < ActiveRecord::Migration 2 | def self.up 3 | create_table :regions do |t| 4 | t.integer :image_id 5 | t.integer :tlx 6 | t.integer :tly 7 | t.integer :brx 8 | t.integer :bry 9 | t.timestamps 10 | end 11 | end 12 | 13 | def self.down 14 | drop_table :regions 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /ui-server/db/migrate/20110107202655_add_runner_id_to_image.rb: -------------------------------------------------------------------------------- 1 | class AddRunnerIdToImage < ActiveRecord::Migration 2 | def self.up 3 | add_column :images, :runner_id, :integer 4 | end 5 | 6 | def self.down 7 | remove_column :images, :runner_id 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /ui-server/db/migrate/20110109162704_add_counter_cache_to_runners.rb: -------------------------------------------------------------------------------- 1 | class AddCounterCacheToRunners < ActiveRecord::Migration 2 | def self.up 3 | add_column :runners, :images_count, :integer, :default => 0 4 | 5 | Runner.reset_column_information 6 | Runner.all.each do |r| 7 | Runner.update_counters r.id, :images_count => r.images.length 8 | end 9 | end 10 | 11 | def self.down 12 | remove_column :runners, :images_count 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /ui-server/db/migrate/20110118121344_create_failures.rb: -------------------------------------------------------------------------------- 1 | class CreateFailures < ActiveRecord::Migration 2 | def self.up 3 | create_table :failures do |t| 4 | t.integer :image_id 5 | t.string :message 6 | t.timestamps 7 | end 8 | end 9 | 10 | def self.down 11 | drop_table :failures 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /ui-server/db/migrate/20110118123905_remove_failed_from_image.rb: -------------------------------------------------------------------------------- 1 | class RemoveFailedFromImage < ActiveRecord::Migration 2 | def self.up 3 | remove_column :images, :failed 4 | end 5 | 6 | def self.down 7 | add_column :images, :failed, :boolean, :default => false 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /ui-server/db/migrate/20110201074743_add_karma_to_images.rb: -------------------------------------------------------------------------------- 1 | class AddKarmaToImages < ActiveRecord::Migration 2 | def self.up 3 | add_column :images, :karma, :float, :default => 1.0, :null => false 4 | end 5 | 6 | def self.down 7 | remove_column :images, :karma 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /ui-server/db/migrate/20110201100222_add_jobs_per_minute.rb: -------------------------------------------------------------------------------- 1 | class AddJobsPerMinute < ActiveRecord::Migration 2 | def self.up 3 | add_column :runners, :max_jobs_per_minute, :integer, :default => 20, :null => false 4 | end 5 | 6 | def self.down 7 | remove_column :runners, :max_jobs_per_minute 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /ui-server/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended to check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(:version => 20110201100222) do 14 | 15 | create_table "failures", :force => true do |t| 16 | t.integer "image_id" 17 | t.string "message" 18 | t.datetime "created_at" 19 | t.datetime "updated_at" 20 | end 21 | 22 | create_table "images", :force => true do |t| 23 | t.string "url" 24 | t.boolean "completed", :default => false 25 | t.datetime "created_at" 26 | t.datetime "updated_at" 27 | t.integer "runner_id" 28 | t.float "karma", :default => 1.0, :null => false 29 | end 30 | 31 | create_table "regions", :force => true do |t| 32 | t.integer "image_id" 33 | t.integer "tlx" 34 | t.integer "tly" 35 | t.integer "brx" 36 | t.integer "bry" 37 | t.datetime "created_at" 38 | t.datetime "updated_at" 39 | end 40 | 41 | create_table "runners", :force => true do |t| 42 | t.string "host" 43 | t.integer "port" 44 | t.integer "file_transfer_port" 45 | t.datetime "created_at" 46 | t.datetime "updated_at" 47 | t.integer "images_count", :default => 0 48 | t.integer "max_jobs_per_minute", :default => 20, :null => false 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /ui-server/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) 7 | # Mayor.create(:name => 'Daley', :city => cities.first) 8 | -------------------------------------------------------------------------------- /ui-server/doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /ui-server/lib/scheduler.rb: -------------------------------------------------------------------------------- 1 | class Scheduler 2 | 3 | RUNNERS_FULL = -1 4 | 5 | def self.process_image img 6 | r = Runner.free.min {|a,b| a.images_count <=> b.images_count } 7 | return RUNNERS_FULL if r.nil? 8 | 9 | img.runner_id = r.id 10 | img.save! 11 | 12 | eta = RestClient.get "http://#{r.host}:#{r.port}/detect", { :params => { :image_id => img.id, :url => img.url } } 13 | return eta 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /ui-server/lib/tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/ui-server/lib/tasks/.gitkeep -------------------------------------------------------------------------------- /ui-server/lib/tasks/backup.rake: -------------------------------------------------------------------------------- 1 | namespace :db do 2 | desc 'Save the production database to backup.psql' 3 | task :backup do 4 | system("pg_dump ui-server_production > backup.psql") 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /ui-server/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /ui-server/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /ui-server/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /ui-server/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/ui-server/public/favicon.ico -------------------------------------------------------------------------------- /ui-server/public/images/forkme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/ui-server/public/images/forkme.png -------------------------------------------------------------------------------- /ui-server/public/images/grup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/ui-server/public/images/grup.jpg -------------------------------------------------------------------------------- /ui-server/public/images/java_icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/ui-server/public/images/java_icon.gif -------------------------------------------------------------------------------- /ui-server/public/images/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/ui-server/public/images/loader.gif -------------------------------------------------------------------------------- /ui-server/public/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/ui-server/public/images/rails.png -------------------------------------------------------------------------------- /ui-server/public/images/ruby_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/ui-server/public/images/ruby_icon.jpg -------------------------------------------------------------------------------- /ui-server/public/images/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/ui-server/public/images/sample.png -------------------------------------------------------------------------------- /ui-server/public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | var api = new DetectClient(); 2 | var NOT_COMPLETED_TIMEOUT = 3000; 3 | 4 | function clearInput(element) { 5 | content = element.value; 6 | if ((content.substring(0,4) != 'http') && (content.substring(0,3) != 'ftp')) { 7 | element.value = ''; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ui-server/public/javascripts/controls.js: -------------------------------------------------------------------------------- 1 | // script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 2 | 3 | // Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 4 | // (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan) 5 | // (c) 2005-2009 Jon Tirsen (http://www.tirsen.com) 6 | // Contributors: 7 | // Richard Livsey 8 | // Rahul Bhargava 9 | // Rob Wills 10 | // 11 | // script.aculo.us is freely distributable under the terms of an MIT-style license. 12 | // For details, see the script.aculo.us web site: http://script.aculo.us/ 13 | 14 | // Autocompleter.Base handles all the autocompletion functionality 15 | // that's independent of the data source for autocompletion. This 16 | // includes drawing the autocompletion menu, observing keyboard 17 | // and mouse events, and similar. 18 | // 19 | // Specific autocompleters need to provide, at the very least, 20 | // a getUpdatedChoices function that will be invoked every time 21 | // the text inside the monitored textbox changes. This method 22 | // should get the text for which to provide autocompletion by 23 | // invoking this.getToken(), NOT by directly accessing 24 | // this.element.value. This is to allow incremental tokenized 25 | // autocompletion. Specific auto-completion logic (AJAX, etc) 26 | // belongs in getUpdatedChoices. 27 | // 28 | // Tokenized incremental autocompletion is enabled automatically 29 | // when an autocompleter is instantiated with the 'tokens' option 30 | // in the options parameter, e.g.: 31 | // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); 32 | // will incrementally autocomplete with a comma as the token. 33 | // Additionally, ',' in the above example can be replaced with 34 | // a token array, e.g. { tokens: [',', '\n'] } which 35 | // enables autocompletion on multiple tokens. This is most 36 | // useful when one of the tokens is \n (a newline), as it 37 | // allows smart autocompletion after linebreaks. 38 | 39 | if(typeof Effect == 'undefined') 40 | throw("controls.js requires including script.aculo.us' effects.js library"); 41 | 42 | var Autocompleter = { }; 43 | Autocompleter.Base = Class.create({ 44 | baseInitialize: function(element, update, options) { 45 | element = $(element); 46 | this.element = element; 47 | this.update = $(update); 48 | this.hasFocus = false; 49 | this.changed = false; 50 | this.active = false; 51 | this.index = 0; 52 | this.entryCount = 0; 53 | this.oldElementValue = this.element.value; 54 | 55 | if(this.setOptions) 56 | this.setOptions(options); 57 | else 58 | this.options = options || { }; 59 | 60 | this.options.paramName = this.options.paramName || this.element.name; 61 | this.options.tokens = this.options.tokens || []; 62 | this.options.frequency = this.options.frequency || 0.4; 63 | this.options.minChars = this.options.minChars || 1; 64 | this.options.onShow = this.options.onShow || 65 | function(element, update){ 66 | if(!update.style.position || update.style.position=='absolute') { 67 | update.style.position = 'absolute'; 68 | Position.clone(element, update, { 69 | setHeight: false, 70 | offsetTop: element.offsetHeight 71 | }); 72 | } 73 | Effect.Appear(update,{duration:0.15}); 74 | }; 75 | this.options.onHide = this.options.onHide || 76 | function(element, update){ new Effect.Fade(update,{duration:0.15}) }; 77 | 78 | if(typeof(this.options.tokens) == 'string') 79 | this.options.tokens = new Array(this.options.tokens); 80 | // Force carriage returns as token delimiters anyway 81 | if (!this.options.tokens.include('\n')) 82 | this.options.tokens.push('\n'); 83 | 84 | this.observer = null; 85 | 86 | this.element.setAttribute('autocomplete','off'); 87 | 88 | Element.hide(this.update); 89 | 90 | Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); 91 | Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); 92 | }, 93 | 94 | show: function() { 95 | if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); 96 | if(!this.iefix && 97 | (Prototype.Browser.IE) && 98 | (Element.getStyle(this.update, 'position')=='absolute')) { 99 | new Insertion.After(this.update, 100 | ''); 103 | this.iefix = $(this.update.id+'_iefix'); 104 | } 105 | if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); 106 | }, 107 | 108 | fixIEOverlapping: function() { 109 | Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); 110 | this.iefix.style.zIndex = 1; 111 | this.update.style.zIndex = 2; 112 | Element.show(this.iefix); 113 | }, 114 | 115 | hide: function() { 116 | this.stopIndicator(); 117 | if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); 118 | if(this.iefix) Element.hide(this.iefix); 119 | }, 120 | 121 | startIndicator: function() { 122 | if(this.options.indicator) Element.show(this.options.indicator); 123 | }, 124 | 125 | stopIndicator: function() { 126 | if(this.options.indicator) Element.hide(this.options.indicator); 127 | }, 128 | 129 | onKeyPress: function(event) { 130 | if(this.active) 131 | switch(event.keyCode) { 132 | case Event.KEY_TAB: 133 | case Event.KEY_RETURN: 134 | this.selectEntry(); 135 | Event.stop(event); 136 | case Event.KEY_ESC: 137 | this.hide(); 138 | this.active = false; 139 | Event.stop(event); 140 | return; 141 | case Event.KEY_LEFT: 142 | case Event.KEY_RIGHT: 143 | return; 144 | case Event.KEY_UP: 145 | this.markPrevious(); 146 | this.render(); 147 | Event.stop(event); 148 | return; 149 | case Event.KEY_DOWN: 150 | this.markNext(); 151 | this.render(); 152 | Event.stop(event); 153 | return; 154 | } 155 | else 156 | if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 157 | (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; 158 | 159 | this.changed = true; 160 | this.hasFocus = true; 161 | 162 | if(this.observer) clearTimeout(this.observer); 163 | this.observer = 164 | setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); 165 | }, 166 | 167 | activate: function() { 168 | this.changed = false; 169 | this.hasFocus = true; 170 | this.getUpdatedChoices(); 171 | }, 172 | 173 | onHover: function(event) { 174 | var element = Event.findElement(event, 'LI'); 175 | if(this.index != element.autocompleteIndex) 176 | { 177 | this.index = element.autocompleteIndex; 178 | this.render(); 179 | } 180 | Event.stop(event); 181 | }, 182 | 183 | onClick: function(event) { 184 | var element = Event.findElement(event, 'LI'); 185 | this.index = element.autocompleteIndex; 186 | this.selectEntry(); 187 | this.hide(); 188 | }, 189 | 190 | onBlur: function(event) { 191 | // needed to make click events working 192 | setTimeout(this.hide.bind(this), 250); 193 | this.hasFocus = false; 194 | this.active = false; 195 | }, 196 | 197 | render: function() { 198 | if(this.entryCount > 0) { 199 | for (var i = 0; i < this.entryCount; i++) 200 | this.index==i ? 201 | Element.addClassName(this.getEntry(i),"selected") : 202 | Element.removeClassName(this.getEntry(i),"selected"); 203 | if(this.hasFocus) { 204 | this.show(); 205 | this.active = true; 206 | } 207 | } else { 208 | this.active = false; 209 | this.hide(); 210 | } 211 | }, 212 | 213 | markPrevious: function() { 214 | if(this.index > 0) this.index--; 215 | else this.index = this.entryCount-1; 216 | this.getEntry(this.index).scrollIntoView(true); 217 | }, 218 | 219 | markNext: function() { 220 | if(this.index < this.entryCount-1) this.index++; 221 | else this.index = 0; 222 | this.getEntry(this.index).scrollIntoView(false); 223 | }, 224 | 225 | getEntry: function(index) { 226 | return this.update.firstChild.childNodes[index]; 227 | }, 228 | 229 | getCurrentEntry: function() { 230 | return this.getEntry(this.index); 231 | }, 232 | 233 | selectEntry: function() { 234 | this.active = false; 235 | this.updateElement(this.getCurrentEntry()); 236 | }, 237 | 238 | updateElement: function(selectedElement) { 239 | if (this.options.updateElement) { 240 | this.options.updateElement(selectedElement); 241 | return; 242 | } 243 | var value = ''; 244 | if (this.options.select) { 245 | var nodes = $(selectedElement).select('.' + this.options.select) || []; 246 | if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); 247 | } else 248 | value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); 249 | 250 | var bounds = this.getTokenBounds(); 251 | if (bounds[0] != -1) { 252 | var newValue = this.element.value.substr(0, bounds[0]); 253 | var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); 254 | if (whitespace) 255 | newValue += whitespace[0]; 256 | this.element.value = newValue + value + this.element.value.substr(bounds[1]); 257 | } else { 258 | this.element.value = value; 259 | } 260 | this.oldElementValue = this.element.value; 261 | this.element.focus(); 262 | 263 | if (this.options.afterUpdateElement) 264 | this.options.afterUpdateElement(this.element, selectedElement); 265 | }, 266 | 267 | updateChoices: function(choices) { 268 | if(!this.changed && this.hasFocus) { 269 | this.update.innerHTML = choices; 270 | Element.cleanWhitespace(this.update); 271 | Element.cleanWhitespace(this.update.down()); 272 | 273 | if(this.update.firstChild && this.update.down().childNodes) { 274 | this.entryCount = 275 | this.update.down().childNodes.length; 276 | for (var i = 0; i < this.entryCount; i++) { 277 | var entry = this.getEntry(i); 278 | entry.autocompleteIndex = i; 279 | this.addObservers(entry); 280 | } 281 | } else { 282 | this.entryCount = 0; 283 | } 284 | 285 | this.stopIndicator(); 286 | this.index = 0; 287 | 288 | if(this.entryCount==1 && this.options.autoSelect) { 289 | this.selectEntry(); 290 | this.hide(); 291 | } else { 292 | this.render(); 293 | } 294 | } 295 | }, 296 | 297 | addObservers: function(element) { 298 | Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); 299 | Event.observe(element, "click", this.onClick.bindAsEventListener(this)); 300 | }, 301 | 302 | onObserverEvent: function() { 303 | this.changed = false; 304 | this.tokenBounds = null; 305 | if(this.getToken().length>=this.options.minChars) { 306 | this.getUpdatedChoices(); 307 | } else { 308 | this.active = false; 309 | this.hide(); 310 | } 311 | this.oldElementValue = this.element.value; 312 | }, 313 | 314 | getToken: function() { 315 | var bounds = this.getTokenBounds(); 316 | return this.element.value.substring(bounds[0], bounds[1]).strip(); 317 | }, 318 | 319 | getTokenBounds: function() { 320 | if (null != this.tokenBounds) return this.tokenBounds; 321 | var value = this.element.value; 322 | if (value.strip().empty()) return [-1, 0]; 323 | var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); 324 | var offset = (diff == this.oldElementValue.length ? 1 : 0); 325 | var prevTokenPos = -1, nextTokenPos = value.length; 326 | var tp; 327 | for (var index = 0, l = this.options.tokens.length; index < l; ++index) { 328 | tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); 329 | if (tp > prevTokenPos) prevTokenPos = tp; 330 | tp = value.indexOf(this.options.tokens[index], diff + offset); 331 | if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; 332 | } 333 | return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); 334 | } 335 | }); 336 | 337 | Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { 338 | var boundary = Math.min(newS.length, oldS.length); 339 | for (var index = 0; index < boundary; ++index) 340 | if (newS[index] != oldS[index]) 341 | return index; 342 | return boundary; 343 | }; 344 | 345 | Ajax.Autocompleter = Class.create(Autocompleter.Base, { 346 | initialize: function(element, update, url, options) { 347 | this.baseInitialize(element, update, options); 348 | this.options.asynchronous = true; 349 | this.options.onComplete = this.onComplete.bind(this); 350 | this.options.defaultParams = this.options.parameters || null; 351 | this.url = url; 352 | }, 353 | 354 | getUpdatedChoices: function() { 355 | this.startIndicator(); 356 | 357 | var entry = encodeURIComponent(this.options.paramName) + '=' + 358 | encodeURIComponent(this.getToken()); 359 | 360 | this.options.parameters = this.options.callback ? 361 | this.options.callback(this.element, entry) : entry; 362 | 363 | if(this.options.defaultParams) 364 | this.options.parameters += '&' + this.options.defaultParams; 365 | 366 | new Ajax.Request(this.url, this.options); 367 | }, 368 | 369 | onComplete: function(request) { 370 | this.updateChoices(request.responseText); 371 | } 372 | }); 373 | 374 | // The local array autocompleter. Used when you'd prefer to 375 | // inject an array of autocompletion options into the page, rather 376 | // than sending out Ajax queries, which can be quite slow sometimes. 377 | // 378 | // The constructor takes four parameters. The first two are, as usual, 379 | // the id of the monitored textbox, and id of the autocompletion menu. 380 | // The third is the array you want to autocomplete from, and the fourth 381 | // is the options block. 382 | // 383 | // Extra local autocompletion options: 384 | // - choices - How many autocompletion choices to offer 385 | // 386 | // - partialSearch - If false, the autocompleter will match entered 387 | // text only at the beginning of strings in the 388 | // autocomplete array. Defaults to true, which will 389 | // match text at the beginning of any *word* in the 390 | // strings in the autocomplete array. If you want to 391 | // search anywhere in the string, additionally set 392 | // the option fullSearch to true (default: off). 393 | // 394 | // - fullSsearch - Search anywhere in autocomplete array strings. 395 | // 396 | // - partialChars - How many characters to enter before triggering 397 | // a partial match (unlike minChars, which defines 398 | // how many characters are required to do any match 399 | // at all). Defaults to 2. 400 | // 401 | // - ignoreCase - Whether to ignore case when autocompleting. 402 | // Defaults to true. 403 | // 404 | // It's possible to pass in a custom function as the 'selector' 405 | // option, if you prefer to write your own autocompletion logic. 406 | // In that case, the other options above will not apply unless 407 | // you support them. 408 | 409 | Autocompleter.Local = Class.create(Autocompleter.Base, { 410 | initialize: function(element, update, array, options) { 411 | this.baseInitialize(element, update, options); 412 | this.options.array = array; 413 | }, 414 | 415 | getUpdatedChoices: function() { 416 | this.updateChoices(this.options.selector(this)); 417 | }, 418 | 419 | setOptions: function(options) { 420 | this.options = Object.extend({ 421 | choices: 10, 422 | partialSearch: true, 423 | partialChars: 2, 424 | ignoreCase: true, 425 | fullSearch: false, 426 | selector: function(instance) { 427 | var ret = []; // Beginning matches 428 | var partial = []; // Inside matches 429 | var entry = instance.getToken(); 430 | var count = 0; 431 | 432 | for (var i = 0; i < instance.options.array.length && 433 | ret.length < instance.options.choices ; i++) { 434 | 435 | var elem = instance.options.array[i]; 436 | var foundPos = instance.options.ignoreCase ? 437 | elem.toLowerCase().indexOf(entry.toLowerCase()) : 438 | elem.indexOf(entry); 439 | 440 | while (foundPos != -1) { 441 | if (foundPos == 0 && elem.length != entry.length) { 442 | ret.push("
  • " + elem.substr(0, entry.length) + "" + 443 | elem.substr(entry.length) + "
  • "); 444 | break; 445 | } else if (entry.length >= instance.options.partialChars && 446 | instance.options.partialSearch && foundPos != -1) { 447 | if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { 448 | partial.push("
  • " + elem.substr(0, foundPos) + "" + 449 | elem.substr(foundPos, entry.length) + "" + elem.substr( 450 | foundPos + entry.length) + "
  • "); 451 | break; 452 | } 453 | } 454 | 455 | foundPos = instance.options.ignoreCase ? 456 | elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 457 | elem.indexOf(entry, foundPos + 1); 458 | 459 | } 460 | } 461 | if (partial.length) 462 | ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); 463 | return "
      " + ret.join('') + "
    "; 464 | } 465 | }, options || { }); 466 | } 467 | }); 468 | 469 | // AJAX in-place editor and collection editor 470 | // Full rewrite by Christophe Porteneuve (April 2007). 471 | 472 | // Use this if you notice weird scrolling problems on some browsers, 473 | // the DOM might be a bit confused when this gets called so do this 474 | // waits 1 ms (with setTimeout) until it does the activation 475 | Field.scrollFreeActivate = function(field) { 476 | setTimeout(function() { 477 | Field.activate(field); 478 | }, 1); 479 | }; 480 | 481 | Ajax.InPlaceEditor = Class.create({ 482 | initialize: function(element, url, options) { 483 | this.url = url; 484 | this.element = element = $(element); 485 | this.prepareOptions(); 486 | this._controls = { }; 487 | arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! 488 | Object.extend(this.options, options || { }); 489 | if (!this.options.formId && this.element.id) { 490 | this.options.formId = this.element.id + '-inplaceeditor'; 491 | if ($(this.options.formId)) 492 | this.options.formId = ''; 493 | } 494 | if (this.options.externalControl) 495 | this.options.externalControl = $(this.options.externalControl); 496 | if (!this.options.externalControl) 497 | this.options.externalControlOnly = false; 498 | this._originalBackground = this.element.getStyle('background-color') || 'transparent'; 499 | this.element.title = this.options.clickToEditText; 500 | this._boundCancelHandler = this.handleFormCancellation.bind(this); 501 | this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); 502 | this._boundFailureHandler = this.handleAJAXFailure.bind(this); 503 | this._boundSubmitHandler = this.handleFormSubmission.bind(this); 504 | this._boundWrapperHandler = this.wrapUp.bind(this); 505 | this.registerListeners(); 506 | }, 507 | checkForEscapeOrReturn: function(e) { 508 | if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; 509 | if (Event.KEY_ESC == e.keyCode) 510 | this.handleFormCancellation(e); 511 | else if (Event.KEY_RETURN == e.keyCode) 512 | this.handleFormSubmission(e); 513 | }, 514 | createControl: function(mode, handler, extraClasses) { 515 | var control = this.options[mode + 'Control']; 516 | var text = this.options[mode + 'Text']; 517 | if ('button' == control) { 518 | var btn = document.createElement('input'); 519 | btn.type = 'submit'; 520 | btn.value = text; 521 | btn.className = 'editor_' + mode + '_button'; 522 | if ('cancel' == mode) 523 | btn.onclick = this._boundCancelHandler; 524 | this._form.appendChild(btn); 525 | this._controls[mode] = btn; 526 | } else if ('link' == control) { 527 | var link = document.createElement('a'); 528 | link.href = '#'; 529 | link.appendChild(document.createTextNode(text)); 530 | link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; 531 | link.className = 'editor_' + mode + '_link'; 532 | if (extraClasses) 533 | link.className += ' ' + extraClasses; 534 | this._form.appendChild(link); 535 | this._controls[mode] = link; 536 | } 537 | }, 538 | createEditField: function() { 539 | var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); 540 | var fld; 541 | if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { 542 | fld = document.createElement('input'); 543 | fld.type = 'text'; 544 | var size = this.options.size || this.options.cols || 0; 545 | if (0 < size) fld.size = size; 546 | } else { 547 | fld = document.createElement('textarea'); 548 | fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); 549 | fld.cols = this.options.cols || 40; 550 | } 551 | fld.name = this.options.paramName; 552 | fld.value = text; // No HTML breaks conversion anymore 553 | fld.className = 'editor_field'; 554 | if (this.options.submitOnBlur) 555 | fld.onblur = this._boundSubmitHandler; 556 | this._controls.editor = fld; 557 | if (this.options.loadTextURL) 558 | this.loadExternalText(); 559 | this._form.appendChild(this._controls.editor); 560 | }, 561 | createForm: function() { 562 | var ipe = this; 563 | function addText(mode, condition) { 564 | var text = ipe.options['text' + mode + 'Controls']; 565 | if (!text || condition === false) return; 566 | ipe._form.appendChild(document.createTextNode(text)); 567 | }; 568 | this._form = $(document.createElement('form')); 569 | this._form.id = this.options.formId; 570 | this._form.addClassName(this.options.formClassName); 571 | this._form.onsubmit = this._boundSubmitHandler; 572 | this.createEditField(); 573 | if ('textarea' == this._controls.editor.tagName.toLowerCase()) 574 | this._form.appendChild(document.createElement('br')); 575 | if (this.options.onFormCustomization) 576 | this.options.onFormCustomization(this, this._form); 577 | addText('Before', this.options.okControl || this.options.cancelControl); 578 | this.createControl('ok', this._boundSubmitHandler); 579 | addText('Between', this.options.okControl && this.options.cancelControl); 580 | this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); 581 | addText('After', this.options.okControl || this.options.cancelControl); 582 | }, 583 | destroy: function() { 584 | if (this._oldInnerHTML) 585 | this.element.innerHTML = this._oldInnerHTML; 586 | this.leaveEditMode(); 587 | this.unregisterListeners(); 588 | }, 589 | enterEditMode: function(e) { 590 | if (this._saving || this._editing) return; 591 | this._editing = true; 592 | this.triggerCallback('onEnterEditMode'); 593 | if (this.options.externalControl) 594 | this.options.externalControl.hide(); 595 | this.element.hide(); 596 | this.createForm(); 597 | this.element.parentNode.insertBefore(this._form, this.element); 598 | if (!this.options.loadTextURL) 599 | this.postProcessEditField(); 600 | if (e) Event.stop(e); 601 | }, 602 | enterHover: function(e) { 603 | if (this.options.hoverClassName) 604 | this.element.addClassName(this.options.hoverClassName); 605 | if (this._saving) return; 606 | this.triggerCallback('onEnterHover'); 607 | }, 608 | getText: function() { 609 | return this.element.innerHTML.unescapeHTML(); 610 | }, 611 | handleAJAXFailure: function(transport) { 612 | this.triggerCallback('onFailure', transport); 613 | if (this._oldInnerHTML) { 614 | this.element.innerHTML = this._oldInnerHTML; 615 | this._oldInnerHTML = null; 616 | } 617 | }, 618 | handleFormCancellation: function(e) { 619 | this.wrapUp(); 620 | if (e) Event.stop(e); 621 | }, 622 | handleFormSubmission: function(e) { 623 | var form = this._form; 624 | var value = $F(this._controls.editor); 625 | this.prepareSubmission(); 626 | var params = this.options.callback(form, value) || ''; 627 | if (Object.isString(params)) 628 | params = params.toQueryParams(); 629 | params.editorId = this.element.id; 630 | if (this.options.htmlResponse) { 631 | var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); 632 | Object.extend(options, { 633 | parameters: params, 634 | onComplete: this._boundWrapperHandler, 635 | onFailure: this._boundFailureHandler 636 | }); 637 | new Ajax.Updater({ success: this.element }, this.url, options); 638 | } else { 639 | var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 640 | Object.extend(options, { 641 | parameters: params, 642 | onComplete: this._boundWrapperHandler, 643 | onFailure: this._boundFailureHandler 644 | }); 645 | new Ajax.Request(this.url, options); 646 | } 647 | if (e) Event.stop(e); 648 | }, 649 | leaveEditMode: function() { 650 | this.element.removeClassName(this.options.savingClassName); 651 | this.removeForm(); 652 | this.leaveHover(); 653 | this.element.style.backgroundColor = this._originalBackground; 654 | this.element.show(); 655 | if (this.options.externalControl) 656 | this.options.externalControl.show(); 657 | this._saving = false; 658 | this._editing = false; 659 | this._oldInnerHTML = null; 660 | this.triggerCallback('onLeaveEditMode'); 661 | }, 662 | leaveHover: function(e) { 663 | if (this.options.hoverClassName) 664 | this.element.removeClassName(this.options.hoverClassName); 665 | if (this._saving) return; 666 | this.triggerCallback('onLeaveHover'); 667 | }, 668 | loadExternalText: function() { 669 | this._form.addClassName(this.options.loadingClassName); 670 | this._controls.editor.disabled = true; 671 | var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 672 | Object.extend(options, { 673 | parameters: 'editorId=' + encodeURIComponent(this.element.id), 674 | onComplete: Prototype.emptyFunction, 675 | onSuccess: function(transport) { 676 | this._form.removeClassName(this.options.loadingClassName); 677 | var text = transport.responseText; 678 | if (this.options.stripLoadedTextTags) 679 | text = text.stripTags(); 680 | this._controls.editor.value = text; 681 | this._controls.editor.disabled = false; 682 | this.postProcessEditField(); 683 | }.bind(this), 684 | onFailure: this._boundFailureHandler 685 | }); 686 | new Ajax.Request(this.options.loadTextURL, options); 687 | }, 688 | postProcessEditField: function() { 689 | var fpc = this.options.fieldPostCreation; 690 | if (fpc) 691 | $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); 692 | }, 693 | prepareOptions: function() { 694 | this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); 695 | Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); 696 | [this._extraDefaultOptions].flatten().compact().each(function(defs) { 697 | Object.extend(this.options, defs); 698 | }.bind(this)); 699 | }, 700 | prepareSubmission: function() { 701 | this._saving = true; 702 | this.removeForm(); 703 | this.leaveHover(); 704 | this.showSaving(); 705 | }, 706 | registerListeners: function() { 707 | this._listeners = { }; 708 | var listener; 709 | $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { 710 | listener = this[pair.value].bind(this); 711 | this._listeners[pair.key] = listener; 712 | if (!this.options.externalControlOnly) 713 | this.element.observe(pair.key, listener); 714 | if (this.options.externalControl) 715 | this.options.externalControl.observe(pair.key, listener); 716 | }.bind(this)); 717 | }, 718 | removeForm: function() { 719 | if (!this._form) return; 720 | this._form.remove(); 721 | this._form = null; 722 | this._controls = { }; 723 | }, 724 | showSaving: function() { 725 | this._oldInnerHTML = this.element.innerHTML; 726 | this.element.innerHTML = this.options.savingText; 727 | this.element.addClassName(this.options.savingClassName); 728 | this.element.style.backgroundColor = this._originalBackground; 729 | this.element.show(); 730 | }, 731 | triggerCallback: function(cbName, arg) { 732 | if ('function' == typeof this.options[cbName]) { 733 | this.options[cbName](this, arg); 734 | } 735 | }, 736 | unregisterListeners: function() { 737 | $H(this._listeners).each(function(pair) { 738 | if (!this.options.externalControlOnly) 739 | this.element.stopObserving(pair.key, pair.value); 740 | if (this.options.externalControl) 741 | this.options.externalControl.stopObserving(pair.key, pair.value); 742 | }.bind(this)); 743 | }, 744 | wrapUp: function(transport) { 745 | this.leaveEditMode(); 746 | // Can't use triggerCallback due to backward compatibility: requires 747 | // binding + direct element 748 | this._boundComplete(transport, this.element); 749 | } 750 | }); 751 | 752 | Object.extend(Ajax.InPlaceEditor.prototype, { 753 | dispose: Ajax.InPlaceEditor.prototype.destroy 754 | }); 755 | 756 | Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { 757 | initialize: function($super, element, url, options) { 758 | this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; 759 | $super(element, url, options); 760 | }, 761 | 762 | createEditField: function() { 763 | var list = document.createElement('select'); 764 | list.name = this.options.paramName; 765 | list.size = 1; 766 | this._controls.editor = list; 767 | this._collection = this.options.collection || []; 768 | if (this.options.loadCollectionURL) 769 | this.loadCollection(); 770 | else 771 | this.checkForExternalText(); 772 | this._form.appendChild(this._controls.editor); 773 | }, 774 | 775 | loadCollection: function() { 776 | this._form.addClassName(this.options.loadingClassName); 777 | this.showLoadingText(this.options.loadingCollectionText); 778 | var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 779 | Object.extend(options, { 780 | parameters: 'editorId=' + encodeURIComponent(this.element.id), 781 | onComplete: Prototype.emptyFunction, 782 | onSuccess: function(transport) { 783 | var js = transport.responseText.strip(); 784 | if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check 785 | throw('Server returned an invalid collection representation.'); 786 | this._collection = eval(js); 787 | this.checkForExternalText(); 788 | }.bind(this), 789 | onFailure: this.onFailure 790 | }); 791 | new Ajax.Request(this.options.loadCollectionURL, options); 792 | }, 793 | 794 | showLoadingText: function(text) { 795 | this._controls.editor.disabled = true; 796 | var tempOption = this._controls.editor.firstChild; 797 | if (!tempOption) { 798 | tempOption = document.createElement('option'); 799 | tempOption.value = ''; 800 | this._controls.editor.appendChild(tempOption); 801 | tempOption.selected = true; 802 | } 803 | tempOption.update((text || '').stripScripts().stripTags()); 804 | }, 805 | 806 | checkForExternalText: function() { 807 | this._text = this.getText(); 808 | if (this.options.loadTextURL) 809 | this.loadExternalText(); 810 | else 811 | this.buildOptionList(); 812 | }, 813 | 814 | loadExternalText: function() { 815 | this.showLoadingText(this.options.loadingText); 816 | var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); 817 | Object.extend(options, { 818 | parameters: 'editorId=' + encodeURIComponent(this.element.id), 819 | onComplete: Prototype.emptyFunction, 820 | onSuccess: function(transport) { 821 | this._text = transport.responseText.strip(); 822 | this.buildOptionList(); 823 | }.bind(this), 824 | onFailure: this.onFailure 825 | }); 826 | new Ajax.Request(this.options.loadTextURL, options); 827 | }, 828 | 829 | buildOptionList: function() { 830 | this._form.removeClassName(this.options.loadingClassName); 831 | this._collection = this._collection.map(function(entry) { 832 | return 2 === entry.length ? entry : [entry, entry].flatten(); 833 | }); 834 | var marker = ('value' in this.options) ? this.options.value : this._text; 835 | var textFound = this._collection.any(function(entry) { 836 | return entry[0] == marker; 837 | }.bind(this)); 838 | this._controls.editor.update(''); 839 | var option; 840 | this._collection.each(function(entry, index) { 841 | option = document.createElement('option'); 842 | option.value = entry[0]; 843 | option.selected = textFound ? entry[0] == marker : 0 == index; 844 | option.appendChild(document.createTextNode(entry[1])); 845 | this._controls.editor.appendChild(option); 846 | }.bind(this)); 847 | this._controls.editor.disabled = false; 848 | Field.scrollFreeActivate(this._controls.editor); 849 | } 850 | }); 851 | 852 | //**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** 853 | //**** This only exists for a while, in order to let **** 854 | //**** users adapt to the new API. Read up on the new **** 855 | //**** API and convert your code to it ASAP! **** 856 | 857 | Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { 858 | if (!options) return; 859 | function fallback(name, expr) { 860 | if (name in options || expr === undefined) return; 861 | options[name] = expr; 862 | }; 863 | fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : 864 | options.cancelLink == options.cancelButton == false ? false : undefined))); 865 | fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : 866 | options.okLink == options.okButton == false ? false : undefined))); 867 | fallback('highlightColor', options.highlightcolor); 868 | fallback('highlightEndColor', options.highlightendcolor); 869 | }; 870 | 871 | Object.extend(Ajax.InPlaceEditor, { 872 | DefaultOptions: { 873 | ajaxOptions: { }, 874 | autoRows: 3, // Use when multi-line w/ rows == 1 875 | cancelControl: 'link', // 'link'|'button'|false 876 | cancelText: 'cancel', 877 | clickToEditText: 'Click to edit', 878 | externalControl: null, // id|elt 879 | externalControlOnly: false, 880 | fieldPostCreation: 'activate', // 'activate'|'focus'|false 881 | formClassName: 'inplaceeditor-form', 882 | formId: null, // id|elt 883 | highlightColor: '#ffff99', 884 | highlightEndColor: '#ffffff', 885 | hoverClassName: '', 886 | htmlResponse: true, 887 | loadingClassName: 'inplaceeditor-loading', 888 | loadingText: 'Loading...', 889 | okControl: 'button', // 'link'|'button'|false 890 | okText: 'ok', 891 | paramName: 'value', 892 | rows: 1, // If 1 and multi-line, uses autoRows 893 | savingClassName: 'inplaceeditor-saving', 894 | savingText: 'Saving...', 895 | size: 0, 896 | stripLoadedTextTags: false, 897 | submitOnBlur: false, 898 | textAfterControls: '', 899 | textBeforeControls: '', 900 | textBetweenControls: '' 901 | }, 902 | DefaultCallbacks: { 903 | callback: function(form) { 904 | return Form.serialize(form); 905 | }, 906 | onComplete: function(transport, element) { 907 | // For backward compatibility, this one is bound to the IPE, and passes 908 | // the element directly. It was too often customized, so we don't break it. 909 | new Effect.Highlight(element, { 910 | startcolor: this.options.highlightColor, keepBackgroundImage: true }); 911 | }, 912 | onEnterEditMode: null, 913 | onEnterHover: function(ipe) { 914 | ipe.element.style.backgroundColor = ipe.options.highlightColor; 915 | if (ipe._effect) 916 | ipe._effect.cancel(); 917 | }, 918 | onFailure: function(transport, ipe) { 919 | alert('Error communication with the server: ' + transport.responseText.stripTags()); 920 | }, 921 | onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. 922 | onLeaveEditMode: null, 923 | onLeaveHover: function(ipe) { 924 | ipe._effect = new Effect.Highlight(ipe.element, { 925 | startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, 926 | restorecolor: ipe._originalBackground, keepBackgroundImage: true 927 | }); 928 | } 929 | }, 930 | Listeners: { 931 | click: 'enterEditMode', 932 | keydown: 'checkForEscapeOrReturn', 933 | mouseover: 'enterHover', 934 | mouseout: 'leaveHover' 935 | } 936 | }); 937 | 938 | Ajax.InPlaceCollectionEditor.DefaultOptions = { 939 | loadingCollectionText: 'Loading options...' 940 | }; 941 | 942 | // Delayed observer, like Form.Element.Observer, 943 | // but waits for delay after last key input 944 | // Ideal for live-search fields 945 | 946 | Form.Element.DelayedObserver = Class.create({ 947 | initialize: function(element, delay, callback) { 948 | this.delay = delay || 0.5; 949 | this.element = $(element); 950 | this.callback = callback; 951 | this.timer = null; 952 | this.lastValue = $F(this.element); 953 | Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); 954 | }, 955 | delayedListener: function(event) { 956 | if(this.lastValue == $F(this.element)) return; 957 | if(this.timer) clearTimeout(this.timer); 958 | this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); 959 | this.lastValue = $F(this.element); 960 | }, 961 | onTimerEvent: function() { 962 | this.timer = null; 963 | this.callback(this.element, $F(this.element)); 964 | } 965 | }); -------------------------------------------------------------------------------- /ui-server/public/javascripts/detect_client.js: -------------------------------------------------------------------------------- 1 | function DetectClient() { 2 | this.detect = function(image_url) { 3 | $("loader").style.visibility = "visible"; 4 | $('urlbox').value = image_url; 5 | var url = "/api/v2/detect/new?url=" + image_url; 6 | new Ajax.Request(url, { 7 | method:'get', 8 | onSuccess: function(transport){ 9 | var response = transport.responseText; 10 | result = new DetectResultParser(response); 11 | 12 | if (result.isError()) { 13 | e = result.getError(); 14 | draw_error(e); 15 | return; 16 | } 17 | if (!result.isCompleted()) { 18 | draw_not_completed(image_url); 19 | return; 20 | } 21 | 22 | draw_image(image_url, result); 23 | $("loader").style.visibility = "hidden"; 24 | }, 25 | onFailure: function(){ 26 | $("loader").style.visibility = "hidden"; 27 | e = new ApiError("connection_error", "can not connect to runner") 28 | draw_error(e); 29 | } 30 | }); 31 | }; 32 | 33 | function draw_error(e) { 34 | var canvas = $("detection"); 35 | var context = canvas.getContext("2d"); 36 | clearContext(context, canvas.width, canvas.height); 37 | canvas.height = 50; 38 | $("shout").innerHTML = e.description; 39 | $("loader").style.visibility = "hidden"; 40 | } 41 | 42 | function draw_not_completed(image_url) { 43 | setTimeout('api.detect(\'' + image_url + '\')', NOT_COMPLETED_TIMEOUT); 44 | } 45 | 46 | function draw_image(image_url, result) { 47 | var canvas = $("detection"); 48 | var context = canvas.getContext("2d"); 49 | clearContext(context, canvas.width, canvas.height); 50 | var img = new Image(); 51 | img.src = image_url; 52 | img.onload = function() { 53 | canvas.width = img.width; 54 | canvas.height = img.height; 55 | context.drawImage(img, 0, 0); 56 | context.strokeStyle = "#FFA500"; 57 | context.fillStyle = "#FFA500"; 58 | draw_results(context, result); 59 | } 60 | } 61 | 62 | function draw_results(context, result) { 63 | result.getRegions().each(function(item) { 64 | x = item.top_left_x; 65 | y = item.top_left_y; 66 | width = item.bottom_right_x - x; 67 | height = item.bottom_right_y - y; 68 | 69 | drawx = width / 5; 70 | drawy = height /4; 71 | 72 | //context.fillRect(x + drawx, y + drawy, drawy, drawy); 73 | //context.fillRect(x + drawx + drawx * 2, y + drawy, drawy, drawy); 74 | context.strokeRect(x, y, width, height); 75 | }); 76 | } 77 | 78 | function clearContext(context, width, height) { 79 | context.fillStyle = "#FFFFFF"; 80 | context.fillRect(0, 0, width, height); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /ui-server/public/javascripts/detect_result_parser.js: -------------------------------------------------------------------------------- 1 | function ApiError(code, description) { 2 | this.code = code; 3 | this.description = description; 4 | } 5 | 6 | function Region(tlx, tly, brx, bry) { 7 | this.top_left_x = tlx; 8 | this.top_left_y = tly; 9 | this.bottom_right_x = brx; 10 | this.bottom_right_y = bry; 11 | } 12 | 13 | function DetectResultParser(req) { 14 | this.req = req; 15 | 16 | this.getRegions = function() { 17 | var result = new Array(); 18 | var myregions = xmlDoc.getElementsByTagName("region"); 19 | for ( i = 0; i < myregions.length; i = i + 1) 20 | { 21 | region = myregions[i]; 22 | tlx = parseFloat(region.getAttribute("top_left_x")); 23 | tly = parseFloat(region.getAttribute("top_left_y")); 24 | brx = parseFloat(region.getAttribute("bottom_right_x")); 25 | bry = parseFloat(region.getAttribute("bottom_right_y")); 26 | result[i] = new Region(tlx, tly, brx, bry); 27 | } 28 | return result; 29 | }; 30 | 31 | this.isCompleted = function() { 32 | status = xmlDoc.getElementsByTagName("status")[0].childNodes[0].nodeValue; 33 | return status == "completed"; 34 | }; 35 | 36 | this.getError = function() { 37 | code = xmlDoc.getElementsByTagName("code")[0].childNodes[0].nodeValue; 38 | description = xmlDoc.getElementsByTagName("description")[0].childNodes[0].nodeValue; 39 | return new ApiError(code, description); 40 | } 41 | 42 | this.isError = function() { 43 | return xmlDoc.getElementsByTagName("error")[0] != null; 44 | }; 45 | 46 | if (window.DOMParser) 47 | { 48 | parser=new DOMParser(); 49 | xmlDoc=parser.parseFromString(req,"text/xml"); 50 | } 51 | else // Internet Explorer 52 | { 53 | xmlDoc=new ActiveXObject("Microsoft.XMLDOM"); 54 | xmlDoc.async="false"; 55 | xmlDoc.loadXML(req); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ui-server/public/javascripts/dragdrop.js: -------------------------------------------------------------------------------- 1 | // script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 2 | 3 | // Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 4 | // 5 | // script.aculo.us is freely distributable under the terms of an MIT-style license. 6 | // For details, see the script.aculo.us web site: http://script.aculo.us/ 7 | 8 | if(Object.isUndefined(Effect)) 9 | throw("dragdrop.js requires including script.aculo.us' effects.js library"); 10 | 11 | var Droppables = { 12 | drops: [], 13 | 14 | remove: function(element) { 15 | this.drops = this.drops.reject(function(d) { return d.element==$(element) }); 16 | }, 17 | 18 | add: function(element) { 19 | element = $(element); 20 | var options = Object.extend({ 21 | greedy: true, 22 | hoverclass: null, 23 | tree: false 24 | }, arguments[1] || { }); 25 | 26 | // cache containers 27 | if(options.containment) { 28 | options._containers = []; 29 | var containment = options.containment; 30 | if(Object.isArray(containment)) { 31 | containment.each( function(c) { options._containers.push($(c)) }); 32 | } else { 33 | options._containers.push($(containment)); 34 | } 35 | } 36 | 37 | if(options.accept) options.accept = [options.accept].flatten(); 38 | 39 | Element.makePositioned(element); // fix IE 40 | options.element = element; 41 | 42 | this.drops.push(options); 43 | }, 44 | 45 | findDeepestChild: function(drops) { 46 | deepest = drops[0]; 47 | 48 | for (i = 1; i < drops.length; ++i) 49 | if (Element.isParent(drops[i].element, deepest.element)) 50 | deepest = drops[i]; 51 | 52 | return deepest; 53 | }, 54 | 55 | isContained: function(element, drop) { 56 | var containmentNode; 57 | if(drop.tree) { 58 | containmentNode = element.treeNode; 59 | } else { 60 | containmentNode = element.parentNode; 61 | } 62 | return drop._containers.detect(function(c) { return containmentNode == c }); 63 | }, 64 | 65 | isAffected: function(point, element, drop) { 66 | return ( 67 | (drop.element!=element) && 68 | ((!drop._containers) || 69 | this.isContained(element, drop)) && 70 | ((!drop.accept) || 71 | (Element.classNames(element).detect( 72 | function(v) { return drop.accept.include(v) } ) )) && 73 | Position.within(drop.element, point[0], point[1]) ); 74 | }, 75 | 76 | deactivate: function(drop) { 77 | if(drop.hoverclass) 78 | Element.removeClassName(drop.element, drop.hoverclass); 79 | this.last_active = null; 80 | }, 81 | 82 | activate: function(drop) { 83 | if(drop.hoverclass) 84 | Element.addClassName(drop.element, drop.hoverclass); 85 | this.last_active = drop; 86 | }, 87 | 88 | show: function(point, element) { 89 | if(!this.drops.length) return; 90 | var drop, affected = []; 91 | 92 | this.drops.each( function(drop) { 93 | if(Droppables.isAffected(point, element, drop)) 94 | affected.push(drop); 95 | }); 96 | 97 | if(affected.length>0) 98 | drop = Droppables.findDeepestChild(affected); 99 | 100 | if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); 101 | if (drop) { 102 | Position.within(drop.element, point[0], point[1]); 103 | if(drop.onHover) 104 | drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 105 | 106 | if (drop != this.last_active) Droppables.activate(drop); 107 | } 108 | }, 109 | 110 | fire: function(event, element) { 111 | if(!this.last_active) return; 112 | Position.prepare(); 113 | 114 | if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) 115 | if (this.last_active.onDrop) { 116 | this.last_active.onDrop(element, this.last_active.element, event); 117 | return true; 118 | } 119 | }, 120 | 121 | reset: function() { 122 | if(this.last_active) 123 | this.deactivate(this.last_active); 124 | } 125 | }; 126 | 127 | var Draggables = { 128 | drags: [], 129 | observers: [], 130 | 131 | register: function(draggable) { 132 | if(this.drags.length == 0) { 133 | this.eventMouseUp = this.endDrag.bindAsEventListener(this); 134 | this.eventMouseMove = this.updateDrag.bindAsEventListener(this); 135 | this.eventKeypress = this.keyPress.bindAsEventListener(this); 136 | 137 | Event.observe(document, "mouseup", this.eventMouseUp); 138 | Event.observe(document, "mousemove", this.eventMouseMove); 139 | Event.observe(document, "keypress", this.eventKeypress); 140 | } 141 | this.drags.push(draggable); 142 | }, 143 | 144 | unregister: function(draggable) { 145 | this.drags = this.drags.reject(function(d) { return d==draggable }); 146 | if(this.drags.length == 0) { 147 | Event.stopObserving(document, "mouseup", this.eventMouseUp); 148 | Event.stopObserving(document, "mousemove", this.eventMouseMove); 149 | Event.stopObserving(document, "keypress", this.eventKeypress); 150 | } 151 | }, 152 | 153 | activate: function(draggable) { 154 | if(draggable.options.delay) { 155 | this._timeout = setTimeout(function() { 156 | Draggables._timeout = null; 157 | window.focus(); 158 | Draggables.activeDraggable = draggable; 159 | }.bind(this), draggable.options.delay); 160 | } else { 161 | window.focus(); // allows keypress events if window isn't currently focused, fails for Safari 162 | this.activeDraggable = draggable; 163 | } 164 | }, 165 | 166 | deactivate: function() { 167 | this.activeDraggable = null; 168 | }, 169 | 170 | updateDrag: function(event) { 171 | if(!this.activeDraggable) return; 172 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 173 | // Mozilla-based browsers fire successive mousemove events with 174 | // the same coordinates, prevent needless redrawing (moz bug?) 175 | if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; 176 | this._lastPointer = pointer; 177 | 178 | this.activeDraggable.updateDrag(event, pointer); 179 | }, 180 | 181 | endDrag: function(event) { 182 | if(this._timeout) { 183 | clearTimeout(this._timeout); 184 | this._timeout = null; 185 | } 186 | if(!this.activeDraggable) return; 187 | this._lastPointer = null; 188 | this.activeDraggable.endDrag(event); 189 | this.activeDraggable = null; 190 | }, 191 | 192 | keyPress: function(event) { 193 | if(this.activeDraggable) 194 | this.activeDraggable.keyPress(event); 195 | }, 196 | 197 | addObserver: function(observer) { 198 | this.observers.push(observer); 199 | this._cacheObserverCallbacks(); 200 | }, 201 | 202 | removeObserver: function(element) { // element instead of observer fixes mem leaks 203 | this.observers = this.observers.reject( function(o) { return o.element==element }); 204 | this._cacheObserverCallbacks(); 205 | }, 206 | 207 | notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' 208 | if(this[eventName+'Count'] > 0) 209 | this.observers.each( function(o) { 210 | if(o[eventName]) o[eventName](eventName, draggable, event); 211 | }); 212 | if(draggable.options[eventName]) draggable.options[eventName](draggable, event); 213 | }, 214 | 215 | _cacheObserverCallbacks: function() { 216 | ['onStart','onEnd','onDrag'].each( function(eventName) { 217 | Draggables[eventName+'Count'] = Draggables.observers.select( 218 | function(o) { return o[eventName]; } 219 | ).length; 220 | }); 221 | } 222 | }; 223 | 224 | /*--------------------------------------------------------------------------*/ 225 | 226 | var Draggable = Class.create({ 227 | initialize: function(element) { 228 | var defaults = { 229 | handle: false, 230 | reverteffect: function(element, top_offset, left_offset) { 231 | var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; 232 | new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, 233 | queue: {scope:'_draggable', position:'end'} 234 | }); 235 | }, 236 | endeffect: function(element) { 237 | var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; 238 | new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 239 | queue: {scope:'_draggable', position:'end'}, 240 | afterFinish: function(){ 241 | Draggable._dragging[element] = false 242 | } 243 | }); 244 | }, 245 | zindex: 1000, 246 | revert: false, 247 | quiet: false, 248 | scroll: false, 249 | scrollSensitivity: 20, 250 | scrollSpeed: 15, 251 | snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } 252 | delay: 0 253 | }; 254 | 255 | if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) 256 | Object.extend(defaults, { 257 | starteffect: function(element) { 258 | element._opacity = Element.getOpacity(element); 259 | Draggable._dragging[element] = true; 260 | new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 261 | } 262 | }); 263 | 264 | var options = Object.extend(defaults, arguments[1] || { }); 265 | 266 | this.element = $(element); 267 | 268 | if(options.handle && Object.isString(options.handle)) 269 | this.handle = this.element.down('.'+options.handle, 0); 270 | 271 | if(!this.handle) this.handle = $(options.handle); 272 | if(!this.handle) this.handle = this.element; 273 | 274 | if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { 275 | options.scroll = $(options.scroll); 276 | this._isScrollChild = Element.childOf(this.element, options.scroll); 277 | } 278 | 279 | Element.makePositioned(this.element); // fix IE 280 | 281 | this.options = options; 282 | this.dragging = false; 283 | 284 | this.eventMouseDown = this.initDrag.bindAsEventListener(this); 285 | Event.observe(this.handle, "mousedown", this.eventMouseDown); 286 | 287 | Draggables.register(this); 288 | }, 289 | 290 | destroy: function() { 291 | Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); 292 | Draggables.unregister(this); 293 | }, 294 | 295 | currentDelta: function() { 296 | return([ 297 | parseInt(Element.getStyle(this.element,'left') || '0'), 298 | parseInt(Element.getStyle(this.element,'top') || '0')]); 299 | }, 300 | 301 | initDrag: function(event) { 302 | if(!Object.isUndefined(Draggable._dragging[this.element]) && 303 | Draggable._dragging[this.element]) return; 304 | if(Event.isLeftClick(event)) { 305 | // abort on form elements, fixes a Firefox issue 306 | var src = Event.element(event); 307 | if((tag_name = src.tagName.toUpperCase()) && ( 308 | tag_name=='INPUT' || 309 | tag_name=='SELECT' || 310 | tag_name=='OPTION' || 311 | tag_name=='BUTTON' || 312 | tag_name=='TEXTAREA')) return; 313 | 314 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 315 | var pos = this.element.cumulativeOffset(); 316 | this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); 317 | 318 | Draggables.activate(this); 319 | Event.stop(event); 320 | } 321 | }, 322 | 323 | startDrag: function(event) { 324 | this.dragging = true; 325 | if(!this.delta) 326 | this.delta = this.currentDelta(); 327 | 328 | if(this.options.zindex) { 329 | this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); 330 | this.element.style.zIndex = this.options.zindex; 331 | } 332 | 333 | if(this.options.ghosting) { 334 | this._clone = this.element.cloneNode(true); 335 | this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); 336 | if (!this._originallyAbsolute) 337 | Position.absolutize(this.element); 338 | this.element.parentNode.insertBefore(this._clone, this.element); 339 | } 340 | 341 | if(this.options.scroll) { 342 | if (this.options.scroll == window) { 343 | var where = this._getWindowScroll(this.options.scroll); 344 | this.originalScrollLeft = where.left; 345 | this.originalScrollTop = where.top; 346 | } else { 347 | this.originalScrollLeft = this.options.scroll.scrollLeft; 348 | this.originalScrollTop = this.options.scroll.scrollTop; 349 | } 350 | } 351 | 352 | Draggables.notify('onStart', this, event); 353 | 354 | if(this.options.starteffect) this.options.starteffect(this.element); 355 | }, 356 | 357 | updateDrag: function(event, pointer) { 358 | if(!this.dragging) this.startDrag(event); 359 | 360 | if(!this.options.quiet){ 361 | Position.prepare(); 362 | Droppables.show(pointer, this.element); 363 | } 364 | 365 | Draggables.notify('onDrag', this, event); 366 | 367 | this.draw(pointer); 368 | if(this.options.change) this.options.change(this); 369 | 370 | if(this.options.scroll) { 371 | this.stopScrolling(); 372 | 373 | var p; 374 | if (this.options.scroll == window) { 375 | with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } 376 | } else { 377 | p = Position.page(this.options.scroll); 378 | p[0] += this.options.scroll.scrollLeft + Position.deltaX; 379 | p[1] += this.options.scroll.scrollTop + Position.deltaY; 380 | p.push(p[0]+this.options.scroll.offsetWidth); 381 | p.push(p[1]+this.options.scroll.offsetHeight); 382 | } 383 | var speed = [0,0]; 384 | if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); 385 | if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); 386 | if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); 387 | if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); 388 | this.startScrolling(speed); 389 | } 390 | 391 | // fix AppleWebKit rendering 392 | if(Prototype.Browser.WebKit) window.scrollBy(0,0); 393 | 394 | Event.stop(event); 395 | }, 396 | 397 | finishDrag: function(event, success) { 398 | this.dragging = false; 399 | 400 | if(this.options.quiet){ 401 | Position.prepare(); 402 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 403 | Droppables.show(pointer, this.element); 404 | } 405 | 406 | if(this.options.ghosting) { 407 | if (!this._originallyAbsolute) 408 | Position.relativize(this.element); 409 | delete this._originallyAbsolute; 410 | Element.remove(this._clone); 411 | this._clone = null; 412 | } 413 | 414 | var dropped = false; 415 | if(success) { 416 | dropped = Droppables.fire(event, this.element); 417 | if (!dropped) dropped = false; 418 | } 419 | if(dropped && this.options.onDropped) this.options.onDropped(this.element); 420 | Draggables.notify('onEnd', this, event); 421 | 422 | var revert = this.options.revert; 423 | if(revert && Object.isFunction(revert)) revert = revert(this.element); 424 | 425 | var d = this.currentDelta(); 426 | if(revert && this.options.reverteffect) { 427 | if (dropped == 0 || revert != 'failure') 428 | this.options.reverteffect(this.element, 429 | d[1]-this.delta[1], d[0]-this.delta[0]); 430 | } else { 431 | this.delta = d; 432 | } 433 | 434 | if(this.options.zindex) 435 | this.element.style.zIndex = this.originalZ; 436 | 437 | if(this.options.endeffect) 438 | this.options.endeffect(this.element); 439 | 440 | Draggables.deactivate(this); 441 | Droppables.reset(); 442 | }, 443 | 444 | keyPress: function(event) { 445 | if(event.keyCode!=Event.KEY_ESC) return; 446 | this.finishDrag(event, false); 447 | Event.stop(event); 448 | }, 449 | 450 | endDrag: function(event) { 451 | if(!this.dragging) return; 452 | this.stopScrolling(); 453 | this.finishDrag(event, true); 454 | Event.stop(event); 455 | }, 456 | 457 | draw: function(point) { 458 | var pos = this.element.cumulativeOffset(); 459 | if(this.options.ghosting) { 460 | var r = Position.realOffset(this.element); 461 | pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; 462 | } 463 | 464 | var d = this.currentDelta(); 465 | pos[0] -= d[0]; pos[1] -= d[1]; 466 | 467 | if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { 468 | pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; 469 | pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; 470 | } 471 | 472 | var p = [0,1].map(function(i){ 473 | return (point[i]-pos[i]-this.offset[i]) 474 | }.bind(this)); 475 | 476 | if(this.options.snap) { 477 | if(Object.isFunction(this.options.snap)) { 478 | p = this.options.snap(p[0],p[1],this); 479 | } else { 480 | if(Object.isArray(this.options.snap)) { 481 | p = p.map( function(v, i) { 482 | return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); 483 | } else { 484 | p = p.map( function(v) { 485 | return (v/this.options.snap).round()*this.options.snap }.bind(this)); 486 | } 487 | }} 488 | 489 | var style = this.element.style; 490 | if((!this.options.constraint) || (this.options.constraint=='horizontal')) 491 | style.left = p[0] + "px"; 492 | if((!this.options.constraint) || (this.options.constraint=='vertical')) 493 | style.top = p[1] + "px"; 494 | 495 | if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering 496 | }, 497 | 498 | stopScrolling: function() { 499 | if(this.scrollInterval) { 500 | clearInterval(this.scrollInterval); 501 | this.scrollInterval = null; 502 | Draggables._lastScrollPointer = null; 503 | } 504 | }, 505 | 506 | startScrolling: function(speed) { 507 | if(!(speed[0] || speed[1])) return; 508 | this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; 509 | this.lastScrolled = new Date(); 510 | this.scrollInterval = setInterval(this.scroll.bind(this), 10); 511 | }, 512 | 513 | scroll: function() { 514 | var current = new Date(); 515 | var delta = current - this.lastScrolled; 516 | this.lastScrolled = current; 517 | if(this.options.scroll == window) { 518 | with (this._getWindowScroll(this.options.scroll)) { 519 | if (this.scrollSpeed[0] || this.scrollSpeed[1]) { 520 | var d = delta / 1000; 521 | this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); 522 | } 523 | } 524 | } else { 525 | this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; 526 | this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; 527 | } 528 | 529 | Position.prepare(); 530 | Droppables.show(Draggables._lastPointer, this.element); 531 | Draggables.notify('onDrag', this); 532 | if (this._isScrollChild) { 533 | Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); 534 | Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; 535 | Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; 536 | if (Draggables._lastScrollPointer[0] < 0) 537 | Draggables._lastScrollPointer[0] = 0; 538 | if (Draggables._lastScrollPointer[1] < 0) 539 | Draggables._lastScrollPointer[1] = 0; 540 | this.draw(Draggables._lastScrollPointer); 541 | } 542 | 543 | if(this.options.change) this.options.change(this); 544 | }, 545 | 546 | _getWindowScroll: function(w) { 547 | var T, L, W, H; 548 | with (w.document) { 549 | if (w.document.documentElement && documentElement.scrollTop) { 550 | T = documentElement.scrollTop; 551 | L = documentElement.scrollLeft; 552 | } else if (w.document.body) { 553 | T = body.scrollTop; 554 | L = body.scrollLeft; 555 | } 556 | if (w.innerWidth) { 557 | W = w.innerWidth; 558 | H = w.innerHeight; 559 | } else if (w.document.documentElement && documentElement.clientWidth) { 560 | W = documentElement.clientWidth; 561 | H = documentElement.clientHeight; 562 | } else { 563 | W = body.offsetWidth; 564 | H = body.offsetHeight; 565 | } 566 | } 567 | return { top: T, left: L, width: W, height: H }; 568 | } 569 | }); 570 | 571 | Draggable._dragging = { }; 572 | 573 | /*--------------------------------------------------------------------------*/ 574 | 575 | var SortableObserver = Class.create({ 576 | initialize: function(element, observer) { 577 | this.element = $(element); 578 | this.observer = observer; 579 | this.lastValue = Sortable.serialize(this.element); 580 | }, 581 | 582 | onStart: function() { 583 | this.lastValue = Sortable.serialize(this.element); 584 | }, 585 | 586 | onEnd: function() { 587 | Sortable.unmark(); 588 | if(this.lastValue != Sortable.serialize(this.element)) 589 | this.observer(this.element) 590 | } 591 | }); 592 | 593 | var Sortable = { 594 | SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, 595 | 596 | sortables: { }, 597 | 598 | _findRootElement: function(element) { 599 | while (element.tagName.toUpperCase() != "BODY") { 600 | if(element.id && Sortable.sortables[element.id]) return element; 601 | element = element.parentNode; 602 | } 603 | }, 604 | 605 | options: function(element) { 606 | element = Sortable._findRootElement($(element)); 607 | if(!element) return; 608 | return Sortable.sortables[element.id]; 609 | }, 610 | 611 | destroy: function(element){ 612 | element = $(element); 613 | var s = Sortable.sortables[element.id]; 614 | 615 | if(s) { 616 | Draggables.removeObserver(s.element); 617 | s.droppables.each(function(d){ Droppables.remove(d) }); 618 | s.draggables.invoke('destroy'); 619 | 620 | delete Sortable.sortables[s.element.id]; 621 | } 622 | }, 623 | 624 | create: function(element) { 625 | element = $(element); 626 | var options = Object.extend({ 627 | element: element, 628 | tag: 'li', // assumes li children, override with tag: 'tagname' 629 | dropOnEmpty: false, 630 | tree: false, 631 | treeTag: 'ul', 632 | overlap: 'vertical', // one of 'vertical', 'horizontal' 633 | constraint: 'vertical', // one of 'vertical', 'horizontal', false 634 | containment: element, // also takes array of elements (or id's); or false 635 | handle: false, // or a CSS class 636 | only: false, 637 | delay: 0, 638 | hoverclass: null, 639 | ghosting: false, 640 | quiet: false, 641 | scroll: false, 642 | scrollSensitivity: 20, 643 | scrollSpeed: 15, 644 | format: this.SERIALIZE_RULE, 645 | 646 | // these take arrays of elements or ids and can be 647 | // used for better initialization performance 648 | elements: false, 649 | handles: false, 650 | 651 | onChange: Prototype.emptyFunction, 652 | onUpdate: Prototype.emptyFunction 653 | }, arguments[1] || { }); 654 | 655 | // clear any old sortable with same element 656 | this.destroy(element); 657 | 658 | // build options for the draggables 659 | var options_for_draggable = { 660 | revert: true, 661 | quiet: options.quiet, 662 | scroll: options.scroll, 663 | scrollSpeed: options.scrollSpeed, 664 | scrollSensitivity: options.scrollSensitivity, 665 | delay: options.delay, 666 | ghosting: options.ghosting, 667 | constraint: options.constraint, 668 | handle: options.handle }; 669 | 670 | if(options.starteffect) 671 | options_for_draggable.starteffect = options.starteffect; 672 | 673 | if(options.reverteffect) 674 | options_for_draggable.reverteffect = options.reverteffect; 675 | else 676 | if(options.ghosting) options_for_draggable.reverteffect = function(element) { 677 | element.style.top = 0; 678 | element.style.left = 0; 679 | }; 680 | 681 | if(options.endeffect) 682 | options_for_draggable.endeffect = options.endeffect; 683 | 684 | if(options.zindex) 685 | options_for_draggable.zindex = options.zindex; 686 | 687 | // build options for the droppables 688 | var options_for_droppable = { 689 | overlap: options.overlap, 690 | containment: options.containment, 691 | tree: options.tree, 692 | hoverclass: options.hoverclass, 693 | onHover: Sortable.onHover 694 | }; 695 | 696 | var options_for_tree = { 697 | onHover: Sortable.onEmptyHover, 698 | overlap: options.overlap, 699 | containment: options.containment, 700 | hoverclass: options.hoverclass 701 | }; 702 | 703 | // fix for gecko engine 704 | Element.cleanWhitespace(element); 705 | 706 | options.draggables = []; 707 | options.droppables = []; 708 | 709 | // drop on empty handling 710 | if(options.dropOnEmpty || options.tree) { 711 | Droppables.add(element, options_for_tree); 712 | options.droppables.push(element); 713 | } 714 | 715 | (options.elements || this.findElements(element, options) || []).each( function(e,i) { 716 | var handle = options.handles ? $(options.handles[i]) : 717 | (options.handle ? $(e).select('.' + options.handle)[0] : e); 718 | options.draggables.push( 719 | new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); 720 | Droppables.add(e, options_for_droppable); 721 | if(options.tree) e.treeNode = element; 722 | options.droppables.push(e); 723 | }); 724 | 725 | if(options.tree) { 726 | (Sortable.findTreeElements(element, options) || []).each( function(e) { 727 | Droppables.add(e, options_for_tree); 728 | e.treeNode = element; 729 | options.droppables.push(e); 730 | }); 731 | } 732 | 733 | // keep reference 734 | this.sortables[element.identify()] = options; 735 | 736 | // for onupdate 737 | Draggables.addObserver(new SortableObserver(element, options.onUpdate)); 738 | 739 | }, 740 | 741 | // return all suitable-for-sortable elements in a guaranteed order 742 | findElements: function(element, options) { 743 | return Element.findChildren( 744 | element, options.only, options.tree ? true : false, options.tag); 745 | }, 746 | 747 | findTreeElements: function(element, options) { 748 | return Element.findChildren( 749 | element, options.only, options.tree ? true : false, options.treeTag); 750 | }, 751 | 752 | onHover: function(element, dropon, overlap) { 753 | if(Element.isParent(dropon, element)) return; 754 | 755 | if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { 756 | return; 757 | } else if(overlap>0.5) { 758 | Sortable.mark(dropon, 'before'); 759 | if(dropon.previousSibling != element) { 760 | var oldParentNode = element.parentNode; 761 | element.style.visibility = "hidden"; // fix gecko rendering 762 | dropon.parentNode.insertBefore(element, dropon); 763 | if(dropon.parentNode!=oldParentNode) 764 | Sortable.options(oldParentNode).onChange(element); 765 | Sortable.options(dropon.parentNode).onChange(element); 766 | } 767 | } else { 768 | Sortable.mark(dropon, 'after'); 769 | var nextElement = dropon.nextSibling || null; 770 | if(nextElement != element) { 771 | var oldParentNode = element.parentNode; 772 | element.style.visibility = "hidden"; // fix gecko rendering 773 | dropon.parentNode.insertBefore(element, nextElement); 774 | if(dropon.parentNode!=oldParentNode) 775 | Sortable.options(oldParentNode).onChange(element); 776 | Sortable.options(dropon.parentNode).onChange(element); 777 | } 778 | } 779 | }, 780 | 781 | onEmptyHover: function(element, dropon, overlap) { 782 | var oldParentNode = element.parentNode; 783 | var droponOptions = Sortable.options(dropon); 784 | 785 | if(!Element.isParent(dropon, element)) { 786 | var index; 787 | 788 | var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); 789 | var child = null; 790 | 791 | if(children) { 792 | var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); 793 | 794 | for (index = 0; index < children.length; index += 1) { 795 | if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { 796 | offset -= Element.offsetSize (children[index], droponOptions.overlap); 797 | } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { 798 | child = index + 1 < children.length ? children[index + 1] : null; 799 | break; 800 | } else { 801 | child = children[index]; 802 | break; 803 | } 804 | } 805 | } 806 | 807 | dropon.insertBefore(element, child); 808 | 809 | Sortable.options(oldParentNode).onChange(element); 810 | droponOptions.onChange(element); 811 | } 812 | }, 813 | 814 | unmark: function() { 815 | if(Sortable._marker) Sortable._marker.hide(); 816 | }, 817 | 818 | mark: function(dropon, position) { 819 | // mark on ghosting only 820 | var sortable = Sortable.options(dropon.parentNode); 821 | if(sortable && !sortable.ghosting) return; 822 | 823 | if(!Sortable._marker) { 824 | Sortable._marker = 825 | ($('dropmarker') || Element.extend(document.createElement('DIV'))). 826 | hide().addClassName('dropmarker').setStyle({position:'absolute'}); 827 | document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); 828 | } 829 | var offsets = dropon.cumulativeOffset(); 830 | Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); 831 | 832 | if(position=='after') 833 | if(sortable.overlap == 'horizontal') 834 | Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); 835 | else 836 | Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); 837 | 838 | Sortable._marker.show(); 839 | }, 840 | 841 | _tree: function(element, options, parent) { 842 | var children = Sortable.findElements(element, options) || []; 843 | 844 | for (var i = 0; i < children.length; ++i) { 845 | var match = children[i].id.match(options.format); 846 | 847 | if (!match) continue; 848 | 849 | var child = { 850 | id: encodeURIComponent(match ? match[1] : null), 851 | element: element, 852 | parent: parent, 853 | children: [], 854 | position: parent.children.length, 855 | container: $(children[i]).down(options.treeTag) 856 | }; 857 | 858 | /* Get the element containing the children and recurse over it */ 859 | if (child.container) 860 | this._tree(child.container, options, child); 861 | 862 | parent.children.push (child); 863 | } 864 | 865 | return parent; 866 | }, 867 | 868 | tree: function(element) { 869 | element = $(element); 870 | var sortableOptions = this.options(element); 871 | var options = Object.extend({ 872 | tag: sortableOptions.tag, 873 | treeTag: sortableOptions.treeTag, 874 | only: sortableOptions.only, 875 | name: element.id, 876 | format: sortableOptions.format 877 | }, arguments[1] || { }); 878 | 879 | var root = { 880 | id: null, 881 | parent: null, 882 | children: [], 883 | container: element, 884 | position: 0 885 | }; 886 | 887 | return Sortable._tree(element, options, root); 888 | }, 889 | 890 | /* Construct a [i] index for a particular node */ 891 | _constructIndex: function(node) { 892 | var index = ''; 893 | do { 894 | if (node.id) index = '[' + node.position + ']' + index; 895 | } while ((node = node.parent) != null); 896 | return index; 897 | }, 898 | 899 | sequence: function(element) { 900 | element = $(element); 901 | var options = Object.extend(this.options(element), arguments[1] || { }); 902 | 903 | return $(this.findElements(element, options) || []).map( function(item) { 904 | return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; 905 | }); 906 | }, 907 | 908 | setSequence: function(element, new_sequence) { 909 | element = $(element); 910 | var options = Object.extend(this.options(element), arguments[2] || { }); 911 | 912 | var nodeMap = { }; 913 | this.findElements(element, options).each( function(n) { 914 | if (n.id.match(options.format)) 915 | nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; 916 | n.parentNode.removeChild(n); 917 | }); 918 | 919 | new_sequence.each(function(ident) { 920 | var n = nodeMap[ident]; 921 | if (n) { 922 | n[1].appendChild(n[0]); 923 | delete nodeMap[ident]; 924 | } 925 | }); 926 | }, 927 | 928 | serialize: function(element) { 929 | element = $(element); 930 | var options = Object.extend(Sortable.options(element), arguments[1] || { }); 931 | var name = encodeURIComponent( 932 | (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); 933 | 934 | if (options.tree) { 935 | return Sortable.tree(element, arguments[1]).children.map( function (item) { 936 | return [name + Sortable._constructIndex(item) + "[id]=" + 937 | encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); 938 | }).flatten().join('&'); 939 | } else { 940 | return Sortable.sequence(element, arguments[1]).map( function(item) { 941 | return name + "[]=" + encodeURIComponent(item); 942 | }).join('&'); 943 | } 944 | } 945 | }; 946 | 947 | // Returns true if child is contained within element 948 | Element.isParent = function(child, element) { 949 | if (!child.parentNode || child == element) return false; 950 | if (child.parentNode == element) return true; 951 | return Element.isParent(child.parentNode, element); 952 | }; 953 | 954 | Element.findChildren = function(element, only, recursive, tagName) { 955 | if(!element.hasChildNodes()) return null; 956 | tagName = tagName.toUpperCase(); 957 | if(only) only = [only].flatten(); 958 | var elements = []; 959 | $A(element.childNodes).each( function(e) { 960 | if(e.tagName && e.tagName.toUpperCase()==tagName && 961 | (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) 962 | elements.push(e); 963 | if(recursive) { 964 | var grandchildren = Element.findChildren(e, only, recursive, tagName); 965 | if(grandchildren) elements.push(grandchildren); 966 | } 967 | }); 968 | 969 | return (elements.length>0 ? elements.flatten() : []); 970 | }; 971 | 972 | Element.offsetSize = function (element, type) { 973 | return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; 974 | }; -------------------------------------------------------------------------------- /ui-server/public/javascripts/rails.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // Technique from Juriy Zaytsev 3 | // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ 4 | function isEventSupported(eventName) { 5 | var el = document.createElement('div'); 6 | eventName = 'on' + eventName; 7 | var isSupported = (eventName in el); 8 | if (!isSupported) { 9 | el.setAttribute(eventName, 'return;'); 10 | isSupported = typeof el[eventName] == 'function'; 11 | } 12 | el = null; 13 | return isSupported; 14 | } 15 | 16 | function isForm(element) { 17 | return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM' 18 | } 19 | 20 | function isInput(element) { 21 | if (Object.isElement(element)) { 22 | var name = element.nodeName.toUpperCase() 23 | return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA' 24 | } 25 | else return false 26 | } 27 | 28 | var submitBubbles = isEventSupported('submit'), 29 | changeBubbles = isEventSupported('change') 30 | 31 | if (!submitBubbles || !changeBubbles) { 32 | // augment the Event.Handler class to observe custom events when needed 33 | Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap( 34 | function(init, element, eventName, selector, callback) { 35 | init(element, eventName, selector, callback) 36 | // is the handler being attached to an element that doesn't support this event? 37 | if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) || 38 | (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) { 39 | // "submit" => "emulated:submit" 40 | this.eventName = 'emulated:' + this.eventName 41 | } 42 | } 43 | ) 44 | } 45 | 46 | if (!submitBubbles) { 47 | // discover forms on the page by observing focus events which always bubble 48 | document.on('focusin', 'form', function(focusEvent, form) { 49 | // special handler for the real "submit" event (one-time operation) 50 | if (!form.retrieve('emulated:submit')) { 51 | form.on('submit', function(submitEvent) { 52 | var emulated = form.fire('emulated:submit', submitEvent, true) 53 | // if custom event received preventDefault, cancel the real one too 54 | if (emulated.returnValue === false) submitEvent.preventDefault() 55 | }) 56 | form.store('emulated:submit', true) 57 | } 58 | }) 59 | } 60 | 61 | if (!changeBubbles) { 62 | // discover form inputs on the page 63 | document.on('focusin', 'input, select, texarea', function(focusEvent, input) { 64 | // special handler for real "change" events 65 | if (!input.retrieve('emulated:change')) { 66 | input.on('change', function(changeEvent) { 67 | input.fire('emulated:change', changeEvent, true) 68 | }) 69 | input.store('emulated:change', true) 70 | } 71 | }) 72 | } 73 | 74 | function handleRemote(element) { 75 | var method, url, params; 76 | 77 | var event = element.fire("ajax:before"); 78 | if (event.stopped) return false; 79 | 80 | if (element.tagName.toLowerCase() === 'form') { 81 | method = element.readAttribute('method') || 'post'; 82 | url = element.readAttribute('action'); 83 | params = element.serialize(); 84 | } else { 85 | method = element.readAttribute('data-method') || 'get'; 86 | url = element.readAttribute('href'); 87 | params = {}; 88 | } 89 | 90 | new Ajax.Request(url, { 91 | method: method, 92 | parameters: params, 93 | evalScripts: true, 94 | 95 | onComplete: function(request) { element.fire("ajax:complete", request); }, 96 | onSuccess: function(request) { element.fire("ajax:success", request); }, 97 | onFailure: function(request) { element.fire("ajax:failure", request); } 98 | }); 99 | 100 | element.fire("ajax:after"); 101 | } 102 | 103 | function handleMethod(element) { 104 | var method = element.readAttribute('data-method'), 105 | url = element.readAttribute('href'), 106 | csrf_param = $$('meta[name=csrf-param]')[0], 107 | csrf_token = $$('meta[name=csrf-token]')[0]; 108 | 109 | var form = new Element('form', { method: "POST", action: url, style: "display: none;" }); 110 | element.parentNode.insert(form); 111 | 112 | if (method !== 'post') { 113 | var field = new Element('input', { type: 'hidden', name: '_method', value: method }); 114 | form.insert(field); 115 | } 116 | 117 | if (csrf_param) { 118 | var param = csrf_param.readAttribute('content'), 119 | token = csrf_token.readAttribute('content'), 120 | field = new Element('input', { type: 'hidden', name: param, value: token }); 121 | form.insert(field); 122 | } 123 | 124 | form.submit(); 125 | } 126 | 127 | 128 | document.on("click", "*[data-confirm]", function(event, element) { 129 | var message = element.readAttribute('data-confirm'); 130 | if (!confirm(message)) event.stop(); 131 | }); 132 | 133 | document.on("click", "a[data-remote]", function(event, element) { 134 | if (event.stopped) return; 135 | handleRemote(element); 136 | event.stop(); 137 | }); 138 | 139 | document.on("click", "a[data-method]", function(event, element) { 140 | if (event.stopped) return; 141 | handleMethod(element); 142 | event.stop(); 143 | }); 144 | 145 | document.on("submit", function(event) { 146 | var element = event.findElement(), 147 | message = element.readAttribute('data-confirm'); 148 | if (message && !confirm(message)) { 149 | event.stop(); 150 | return false; 151 | } 152 | 153 | var inputs = element.select("input[type=submit][data-disable-with]"); 154 | inputs.each(function(input) { 155 | input.disabled = true; 156 | input.writeAttribute('data-original-value', input.value); 157 | input.value = input.readAttribute('data-disable-with'); 158 | }); 159 | 160 | var element = event.findElement("form[data-remote]"); 161 | if (element) { 162 | handleRemote(element); 163 | event.stop(); 164 | } 165 | }); 166 | 167 | document.on("ajax:after", "form", function(event, element) { 168 | var inputs = element.select("input[type=submit][disabled=true][data-disable-with]"); 169 | inputs.each(function(input) { 170 | input.value = input.readAttribute('data-original-value'); 171 | input.removeAttribute('data-original-value'); 172 | input.disabled = false; 173 | }); 174 | }); 175 | })(); 176 | -------------------------------------------------------------------------------- /ui-server/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /ui-server/public/stylesheets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/ui-server/public/stylesheets/.gitkeep -------------------------------------------------------------------------------- /ui-server/public/stylesheets/sinatra.css: -------------------------------------------------------------------------------- 1 | body { 2 | color:#000; 3 | font-family:helvetica, sans-serif; 4 | font-size:100%; 5 | line-height:1.25; 6 | background-color:#fff; 7 | margin:0; 8 | padding:0; 9 | } 10 | 11 | #head { 12 | text-align:center; 13 | } 14 | 15 | #head h1 a, #head h1 a:link, #head h1 a:visited, #head h1 a:hover { 16 | color:#100; 17 | text-decoration:none; 18 | } 19 | 20 | #head ul { 21 | font-size:1.1em; 22 | font-family:"lucida console", "monaco", "andale mono", "bitstream vera sans mono", 23 | "consolas", monospace; 24 | font-weight:normal; 25 | text-transform:uppercase; 26 | letter-spacing:2px; 27 | text-align:center; 28 | margin:0; 29 | padding:0 40px 0.25em 40px; 30 | } 31 | 32 | #head ul li { 33 | display:inline; 34 | list-style-type:none; 35 | padding:0 0.5em 0 0; 36 | } 37 | 38 | #head ul a, #head ul a:link, #head ul a:visited { 39 | color:#56534f; 40 | text-decoration:none; 41 | } 42 | 43 | #head ul a:hover { 44 | color:#000; 45 | text-decoration:underline 46 | } 47 | 48 | #content { 49 | color:#111; 50 | max-width:52em; 51 | min-width:27em; 52 | margin:7px auto; 53 | border-top:2px solid #837d7c; 54 | border-bottom:2px solid #837d7c; 55 | padding-top:10px; 56 | } 57 | 58 | #footer { 59 | color:#111; 60 | max-width:52em; 61 | min-width:27em; 62 | margin:7px auto; 63 | padding-top:10px; 64 | text-align: center; 65 | } 66 | 67 | .thumbnails {} 68 | 69 | .thumbnails img { 70 | margin: 10px; 71 | border: 3px solid #000000; 72 | height: 70px; 73 | width: auto; 74 | opacity:0.75; 75 | filter:alpha(opacity=75) 76 | } 77 | 78 | .thumbnails img:hover { 79 | opacity:1.0; 80 | filter:alpha(opacity=100) 81 | } 82 | 83 | h3 { 84 | color:#000; 85 | font-size:1.1em; 86 | letter-spacing:-1px; 87 | margin:1.8em 0 -0.25em 0; 88 | } 89 | 90 | h3 a { color:#000 } 91 | 92 | a { 93 | color:#000; 94 | text-decoration:underline; 95 | } 96 | 97 | a:hover { 98 | color:#910; 99 | text-decoration:underline; 100 | } 101 | 102 | a img { 103 | border:none; 104 | } 105 | 106 | code, pre, textarea, tt { 107 | font-family:"lucida console", "monaco", "andale mono", "bitstream vera sans mono", 108 | "consolas", monospace; 109 | } 110 | 111 | pre { 112 | font-size:0.85em; 113 | background:#f4f5f5; 114 | border:2px solid #d5d0d2; 115 | padding:0.5em; 116 | line-height:1.15; 117 | color:#111; 118 | overflow: auto; 119 | } 120 | 121 | p.warning { 122 | font-size:0.85em; 123 | background:#f4f5f5; 124 | border:2px solid #d5d0d2; 125 | padding:0.5em; 126 | line-height:1.15; 127 | color:#111; 128 | overflow: auto; 129 | } 130 | 131 | code, tt { 132 | font-size:0.85em; 133 | color:#222; 134 | } 135 | 136 | pre code { 137 | font-size:1em; 138 | color:#222; 139 | } 140 | 141 | input#urlbox { 142 | width: 90%; 143 | } 144 | 145 | .div_highlight { 146 | background:#f4f5f5; 147 | border:2px solid #d5d0d2; 148 | line-height:1.15; 149 | padding: 50px; 150 | text-align: center; 151 | } 152 | 153 | .blurb { 154 | width: 780px; 155 | height: 300px; 156 | margin: 0 32px; 157 | position: relative; 158 | } 159 | 160 | .leftcolumn { 161 | position:absolute; 162 | left: 0px; 163 | width: 230px; 164 | text-align: justify; 165 | } 166 | 167 | .middlecolumn { 168 | position:absolute; 169 | left: 275px; 170 | width: 230px; 171 | text-align: justify; 172 | } 173 | 174 | .rightcolumn { 175 | position:absolute; 176 | left: 550px; 177 | width: 230px; 178 | text-align: justify; 179 | } 180 | 181 | #detection { 182 | border: 3px solid #000000; 183 | } 184 | -------------------------------------------------------------------------------- /ui-server/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /ui-server/test/factories/image_factory.rb: -------------------------------------------------------------------------------- 1 | Factory.define :image, :class => Image do |u| 2 | u.url 'http://sphotos.ak.fbcdn.net/photos-ak-snc1/v272/12/29/1352704123/n1352704123_30009292_5638.jpg' 3 | end 4 | -------------------------------------------------------------------------------- /ui-server/test/factories/region_factory.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | f.sequence(:id) { |n| n } 3 | factory :region do 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /ui-server/test/factories/runner_factory.rb: -------------------------------------------------------------------------------- 1 | # This will guess the User class 2 | #FactoryGirl.define do 3 | # factory :user do 4 | # first_name 'John' 5 | # last_name 'Doe' 6 | # admin false 7 | # end 8 | # 9 | # # This will use the User class (Admin would have been guessed) 10 | # factory :admin, :class => User do 11 | # first_name 'Admin' 12 | # last_name 'User' 13 | # admin true 14 | # end 15 | # 16 | # # The same, but using a string instead of class constant 17 | # factory :admin, :class => 'user' do 18 | # first_name 'Admin' 19 | # last_name 'User' 20 | # admin true 21 | # end 22 | #end 23 | -------------------------------------------------------------------------------- /ui-server/test/fixtures/failures.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html 2 | 3 | # This model initially had no columns defined. If you add columns to the 4 | # model remove the '{}' from the fixture names and add the columns immediately 5 | # below each fixture, per the syntax in the comments below 6 | # 7 | one: {} 8 | # column: value 9 | # 10 | two: {} 11 | # column: value 12 | -------------------------------------------------------------------------------- /ui-server/test/fixtures/images.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html 2 | 3 | # This model initially had no columns defined. If you add columns to the 4 | # model remove the '{}' from the fixture names and add the columns immediately 5 | # below each fixture, per the syntax in the comments below 6 | # 7 | one: {} 8 | # column: value 9 | # 10 | two: {} 11 | # column: value 12 | -------------------------------------------------------------------------------- /ui-server/test/fixtures/regions.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html 2 | 3 | # This model initially had no columns defined. If you add columns to the 4 | # model remove the '{}' from the fixture names and add the columns immediately 5 | # below each fixture, per the syntax in the comments below 6 | # 7 | one: {} 8 | # column: value 9 | # 10 | two: {} 11 | # column: value 12 | -------------------------------------------------------------------------------- /ui-server/test/fixtures/runners.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html 2 | 3 | # This model initially had no columns defined. If you add columns to the 4 | # model remove the '{}' from the fixture names and add the columns immediately 5 | # below each fixture, per the syntax in the comments below 6 | # 7 | one: {} 8 | # column: value 9 | # 10 | two: {} 11 | # column: value 12 | -------------------------------------------------------------------------------- /ui-server/test/functional/api/v2/detect_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::V2::DetectControllerTest < ActionController::TestCase 4 | test "should get new" do 5 | get :new 6 | assert_response :success 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /ui-server/test/functional/backend/detect_result_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Backend::DetectResultControllerTest < ActionController::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /ui-server/test/functional/backend/scheduler_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Backend::SchedulerControllerTest < ActionController::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /ui-server/test/functional/web_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class WebControllerTest < ActionController::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /ui-server/test/performance/browsing_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rails/performance_test_help' 3 | 4 | # Profiling results for each test method are written to tmp/performance. 5 | class BrowsingTest < ActionDispatch::PerformanceTest 6 | def test_homepage 7 | get '/' 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /ui-server/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. 7 | # 8 | # Note: You'll currently still have to declare fixtures explicitly in integration tests 9 | # -- they do not yet inherit this setting 10 | fixtures :all 11 | 12 | # Add more helper methods to be used by all tests here... 13 | end 14 | -------------------------------------------------------------------------------- /ui-server/test/unit/failure_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FailureTest < ActiveSupport::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /ui-server/test/unit/helpers/api/v2/detect_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::V2::DetectHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /ui-server/test/unit/helpers/backend/detect_result_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Backend::DetectResultHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /ui-server/test/unit/helpers/backend/scheduler_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Backend::SchedulerHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /ui-server/test/unit/helpers/web_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class WebHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /ui-server/test/unit/image_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ImageTest < ActiveSupport::TestCase 4 | test "that only valid urls are saved" do 5 | img = Factory.new(:image) 6 | assert img.valid? 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /ui-server/test/unit/region_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RegionTest < ActiveSupport::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /ui-server/test/unit/runner_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RunnerTest < ActiveSupport::TestCase 4 | # Replace this with your real tests. 5 | test "the truth" do 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /ui-server/vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mess110/detection/88d230b36eb05673e531c1d3e4d7de5cba6b6f11/ui-server/vendor/plugins/.gitkeep --------------------------------------------------------------------------------