via.html
HTML document, ASCII text, with very long lines (738)
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4<meta charset="UTF-8"> 5<!-- 6VGG Image Annotator (via) 7www.robots.ox.ac.uk/~vgg/software/via/ 8 9Copyright (c) 2016-2018, Abhishek Dutta, Visual Geometry Group, Oxford University and VIA Contributors.. 10All rights reserved. 11 12Redistribution and use in source and binary forms, with or without 13modification, are permitted provided that the following conditions are met: 14 15Redistributions of source code must retain the above copyright notice, this 16list of conditions and the following disclaimer. 17Redistributions in binary form must reproduce the above copyright notice, 18this list of conditions and the following disclaimer in the documentation 19and/or other materials provided with the distribution. 20THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30POSSIBILITY OF SUCH DAMAGE. 31--> 32<title>VGG Image Annotator</title> 33<meta name="author" content="Abhishek Dutta"> 34<meta name="description" content="VIA is a standalone image annotator application packaged as a single HTML file (< 400 KB) that runs on most modern web browsers."> 35 36<!-- 37Development of VIA is supported by the EPSRC programme grant 38Seebibyte: Visual Search for the Era of Big Data (EP/M013774/1). 39Using Google Analytics, we record the usage of this application. 40This data is used in reporting of this programme grant. 41 42If you do not wish to share this data, you can safely remove the 43javascript code below. 44--> 45<script type="text/javascript"> 46(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 47(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 48m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 49})(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 50ga('create', 'UA-20555581-2', 'auto'); 51ga('set', 'page', 'via-2.0.0.html'); 52ga('send', 'pageview'); 53</script> 54 55<!-- START: Contents of file: via.css--> 56<style> 57/* 58CSS style definitions for VIA 2.x.y 59http://www.robots.ox.ac.uk/~vgg/software/via/ 60Author: Abhishek Dutta <adutta@robots.ox.ac.uk> 61Date: moved from index.html to via.css on 13 June 2019 62*/ 63 64body { min-width:200px; padding:0; margin:0; font-family:sans-serif; } 65* { box-sizing: border-box; } 66input[type=text] { border:1px solid #cccccc; margin:0.6rem 0; padding:0.2rem 0.4rem; } 67a { text-decoration:none; } 68textarea { border:1px solid #cccccc; margin:0.6rem 0; padding:0.2rem 0.4rem; } 69 70/* Top panel : #navbar, #toolbar */ 71.top_panel { font-size:0.9rem; display:block; background-color:#212121; color:#ffffff; z-index:1001; margin-bottom:1rem;} 72 73/* Navigation menu bar that appear at the top */ 74.menubar { display:inline-block; height:1.8rem; } /* height needed to avoid extra bottom border */ 75.menubar a:link { color:#eeeeee; text-decoration:none; } 76.menubar a:visited { color:#eeeeee; text-decoration:none; } 77.menubar ul { display:block; padding:0; margin:0; } 78.menubar li { display:inline; float:left; padding:0.45rem 1rem; } 79.menubar li:hover { background-color:#616161; cursor:default; } 80 81.menubar li ul { display:none; background-color:#212121; border:1px solid #616161; min-width:10rem; position:absolute; z-index:100; margin:0.4rem -1rem;} 82.menubar li ul li { display:block; float:none; color:#eeeeee; margin:0; padding:0.6rem 1rem; } 83.menubar li ul li:hover { cursor:pointer; } 84.menubar li ul li.submenu_divider { margin:0 0.4rem; padding:0; height:1px; border-bottom:1px solid #424242; } 85.menubar li:hover ul { display:block; } 86 87/* toolbar containing small icons for tools */ 88.toolbar { display:inline-block; margin-left:1rem; } 89.toolbar svg { fill:white; margin: 0.2rem 0.1rem; height:1.2rem; -moz-user-select:none; -webkit-user-select:none; -ms-user-select:none;} 90.toolbar svg:hover { fill:yellow; cursor:pointer; } 91.toolbar svg:active { fill:white; } 92.toolbar ul { display:inline-block; padding:0.2rem; margin:0; } 93.toolbar li { display:inline; float:left; padding:0rem 0.3rem; border:1px solid white;} /* main menu items */ 94.toolbar li:hover { color:red; cursor:pointer; } 95 96/* Middle panel: containing #image_panel, #leftsidebar */ 97.middle_panel { display:table; table-layout:fixed; width:100%; z-index:1; padding:0;} 98#leftsidebar { display:none; z-index:10; vertical-align:top;} 99#display_area { display:table-cell; width:100%; z-index:1; margin:0; padding-left:1em; vertical-align:top; } 100/* layers of canvas */ 101#image_panel { position:relative; outline:none; } 102#image_panel img { visibility:hidden; opacity:0; position:absolute; top:0px; left:0px; width:100%; height:100%; outline:none; } 103#image_panel canvas { position:absolute; top:0px; left:0px; outline:none;} 104#image_panel .visible { visibility:visible !important; opacity:1 !important; } 105#image_panel label>img { visibility:visible; opacity:1; position:relative; width:auto; height:4em; outline:none; } 106 107/* image buffer 108#image_panel .fadein { visibility:visible; opacity:1; transition: visibility 0s linear 0s, opacity 300ms; } 109#image_panel .fadeout { visibility:hidden; opacity:0; transition: visibility 0s linear 300ms, opacity 300ms; } 110*/ 111 112/* image grid view */ 113#image_grid_panel { position:relative; margin:0; padding:0; width:100%; } 114#image_grid_panel #image_grid_toolbar { display:block; font-size:small; padding:0.5rem 0;} 115#image_grid_panel #image_grid_toolbar select { font-size:small; } 116#image_grid_panel #image_grid_toolbar .tool { display:inline; margin:0 0.5rem;} 117#image_grid_panel #image_grid_group_panel { font-size:small; } 118#image_grid_panel #image_grid_group_panel select { font-size:small; } 119#image_grid_panel #image_grid_group_panel .image_grid_group_toolbar { display:inline; margin-left: 2rem;} 120#image_grid_panel #image_grid_group_panel .image_grid_group_toolbar select { margin:0 0.2rem; padding:0; font-size:small;} 121 122#image_grid_panel #image_grid_nav { display:inline; font-size:small; padding-left:0.5rem; margin-top:0.2rem; } 123#image_grid_panel #image_grid_nav span { margin: 0 0.2rem; } 124#image_grid_panel #image_grid_content { position:relative; overflow:hidden; margin:0; padding:0; outline:none; } 125#image_grid_panel #image_grid_content #image_grid_content_img img { margin:0.3em; padding:0; border:0.2em solid #ffffff; outline:0.1em solid #0066ff;} 126#image_grid_panel #image_grid_content #image_grid_content_img .not_sel { opacity:0.6; outline:none; } 127#image_grid_panel #image_grid_content #image_grid_content_img { position:absolute; top:0; left:0; width:100%; height:100%; } 128#image_grid_panel #image_grid_content #image_grid_content_rshape { position:absolute; top:0; left:0; width:100%; height:100%; pointer-events:none; } 129#image_grid_panel #image_grid_content img { float:left; margin:0; } 130 131#leftsidebar_collapse_panel { display:none; position:relative; z-index:10; vertical-align:top; } 132#leftsidebar_show_button { font-size:large; margin-left:0.1rem; } 133#leftsidebar_show_button:hover { color:red; cursor: pointer; } 134 135/* Left sidebar accordion */ 136button.leftsidebar_accordion { font-size:large; background-color:#f2f2f2; cursor:pointer; padding:0.5em 0.5em; width:100%; text-align:left; border:0; outline:none; } 137button.leftsidebar_accordion:focus { outline: none; } 138button.leftsidebar_accordion.active, button.leftsidebar_accordion:hover { background-color: #e6e6e6; } 139button.leftsidebar_accordion:after { content:'\02795'; color:#4d4d4d; float:right; } 140button.leftsidebar_accordion.active:after { content: '\2796'; } 141.leftsidebar_accordion_panel { display:none; padding:0 0.5em; font-size:small; border-right:2px solid #f2f2f2; border-bottom:2px solid #f2f2f2; } 142.leftsidebar_accordion_panel.show { display:block; } 143 144/* Keyboard shortcut panel */ 145.leftsidebar_accordion_panel table { border-collapse:collapse; } 146.leftsidebar_accordion_panel td { border:1px solid #f2f2f2; padding:0.2rem 0.4rem; } 147 148/* buttons */ 149.button_panel { display:inline-block; width:100%; margin:0.2rem 0; } 150.button_panel .text_button, .text_button { color: #0000ff; padding: 0.2rem 0.2rem; -moz-user-select:none; -webkit-user-select:none; -ms-user-select:none; } 151.button_panel .flush_right { float:right; } 152.button_panel .text_button:hover, .text_button:hover { cursor:pointer; } 153.button_panel .text_button:active, .text_button:active { color: #000000; } 154.button_panel .active { border-bottom:1px solid black; } 155.button_panel .button { display:inline-block; padding:0.35rem 0.5rem; margin:0 0.05rem; cursor:pointer; background-color:#cccccc; border-radius:0.2rem; -moz-user-select:none; -webkit-user-select:none; -ms-user-select:none; } 156.button_panel .button:hover { background-color:black; color:white; } 157 158/* Attributes properties: name, description, type, ... */ 159#attribute_properties { display:table; width:100%; border-collapse:collapse; margin:1rem 0; border:1px solid #cccccc; } 160#attribute_properties .property { display:table-row;} 161#attribute_properties .property span { display:table-cell; padding: 0.2rem 0.4rem; } 162#attribute_properties .property span input { width: 100%; border:1px solid #cccccc; margin: 0;} 163#attribute_properties .property span input:focus { border:1px solid black; } 164#attribute_properties .property span select { width: 100%; border:1px solid #cccccc; margin: 0;} 165 166/* Attributes options: options for attribute type={checkbox,radio,...} */ 167#attribute_options { display:table; width:100%; border-collapse:collapse; margin:1rem 0; border:1px solid #cccccc; table-layout:fixed; } 168#attribute_options .new_option_id_entry { display:inline-block; padding:1rem 0.2rem; } 169#attribute_options .new_option_id_entry input {border:none; border-bottom:1px solid #cccccc; margin: 0; font-size: 0.8rem;} 170#attribute_options .property { display:table-row;} 171#attribute_options .property span { display:table-cell; padding: 0.2rem 0.2rem; font-weight:bold; } 172#attribute_options .property input { display:table-cell; width:94%; border:none; border-bottom:1px solid #cccccc; margin: 0; font-size: 0.8rem;} 173#attribute_options .property input:focus { border-bottom:1px solid #000000; background-color:#f2f2f2; color:#000000; } 174#attribute_options .property span input[type=checkbox] { vertical-align:middle; } 175#attribute_options .property span input[type=radio] { vertical-align:middle; } 176 177/* overlay panel used to gather user inputs before invoking a function using invoke_with_user_inputs() */ 178#user_input_panel { position:fixed; display:none; width:100%; height:100%; top:0; left:0; right:0; bottom:0; background-color: rgba(0,0,0,0.6); z-index:1002; } 179#user_input_panel .content { position:fixed; background-color:white; top:50%; left:50%; transform:translate(-50%,-50%); -webkit-transform: translate(-50%, -50%); -moz-transform: translate(-50%, -50%); -o-transform: translate(-50%, -50%); -ms-transform: translate(-50%, -50%); padding:2rem 4rem;} 180#user_input_panel .content .title { font-size:large; font-weight:bold; } 181#user_input_panel .content .user_inputs { display:table; width:100%; border-collapse:collapse;} 182#user_input_panel .content .user_inputs .row { display:table-row; } 183#user_input_panel .content .user_inputs .cell { display:table-cell; padding:1rem 0.5rem; vertical-align:middle; border:1px solid #f2f2f2; } 184#user_input_panel .content .user_confirm { display:table; width:100%; text-align:center; margin:2rem 0;} 185#user_input_panel .content .user_confirm .ok { display:table-cell; width:48%; } 186#user_input_panel .content .user_confirm .cancel { display:table-cell; width:48%; } 187#user_input_panel .content .warning { color:red; } 188 189/* Attribute editor */ 190#annotation_editor_panel { position:fixed; display:none; width:100%; left:0; bottom:0; background-color:white; border-top:2px solid #cccccc; padding:0.2em 1em; overflow:auto; z-index:1001; box-shadow: 0 0 1em #cccccc;} 191#annotation_editor { display:table; margin-bottom:3em; border-collapse:collapse; font-size:inherit; position: absolute; background-color:white; } 192#annotation_editor .row { display:table-row; } 193#annotation_editor .highlight .col { background-color:#e6e6e6;} 194 195#annotation_editor .col { display:table-cell; padding:0.4em 0.6em; border:1px solid #000000; vertical-align:middle; font-size:inherit; } 196#annotation_editor .header { font-weight:bold; } 197#annotation_editor .id { font-weight:bold; } 198#annotation_editor .col input[type=checkbox] { vertical-align:middle; } 199#annotation_editor .col input[type=radio] { vertical-align:middle; font-size:inherit; } 200#annotation_editor .col label { vertical-align:middle; font-size:inherit; } 201#annotation_editor .col textarea { border:0.1em solid #cccccc; padding:0; margin:0; font-size:inherit; background-color:#ffffff; } 202#annotation_editor .col textarea:focus { border:0.1em dashed #cccccc; } 203#annotation_editor .col span { display:block; } 204#annotation_editor .col horizontal_container { display:inline-block; } 205 206#annotation_editor .col .img_options { display:inline; } 207#annotation_editor .col .img_options .imrow { display:block; } 208#annotation_editor .col .img_options span { display:inline-block; margin: 0.2rem 0.4rem;} 209#annotation_editor .col .img_options span img { height:4em; } 210#annotation_editor .col .img_options p { margin:0; padding:0; font-size:inherit; } 211#annotation_editor .col .img_options input[type=radio] { display:none; } 212#annotation_editor .col .img_options input[type=radio] + label { display:block; cursor:pointer; text-align:center;} 213#annotation_editor .col .img_options input[type=radio]:checked + label { border: 0.1em solid black; background-color:#cccccc; cursor:default; font-size:inherit; } 214 215#project_info_panel { display:table; border-collapse:collapse; font-size:0.8rem; } 216#project_info_panel .row { display:table-row; } 217#project_info_panel .col { display:table-cell; padding:0.4rem 0.1rem; border:none; } 218#project_info_panel .col input[type=text] { font-size:0.8rem; border:none; border-bottom:1px solid #cccccc; margin: 0; width:100%;} 219#project_info_panel .col input:focus { border-bottom:1px solid #000000; background-color:#f2f2f2; color:#000000; } 220 221/* Region shape selection panel inside leftsidebar */ 222ul.region_shape { font-size:xx-large; list-style-type:none; overflow:hidden; padding:0.4em 0; margin:0; } 223ul.region_shape li { float:left; padding:0 0.2em; fill:#ffffff; stroke:#000000; } 224ul.region_shape li:hover { cursor:pointer; fill:#ffffff; stroke:#ff0000; } 225ul.region_shape .selected { fill:#ffffff; stroke:#ff0000; } 226 227/* cursor coordinates inside region shape selection panel in leftsidebar */ 228#region_info { font-size:0.8em; margin-bottom:0.4em; } 229 230/* Loaded image list shown in leftsidebar panel */ 231#img_fn_list { display:none; font-size:small; overflow:scroll; min-height:10rem; max-height:25rem; } 232#img_fn_list ul { position:relative; line-height:1.3em; margin:0; padding:0; list-style-type:none;} 233#img_fn_list li { white-space:nowrap; display:block; padding:0 0.4rem; } 234#img_fn_list li:hover { background-color:#d5e5ff; cursor:pointer; } 235#img_fn_list .error { color:red; } 236#img_fn_list .sel { border-left:0.2rem solid black !important; font-weight:bold; } 237#img_fn_list .buffered { border-left:0.2rem solid #cccccc; } 238 239#message_panel { display:block; width:100%; position:fixed; bottom:0px; z-index:9999; text-align:center; } 240#message_panel .content { display:inline; margin:auto; background-color:#000000; color:#ffff00; font-size:small; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; line-height:2rem; padding: 0.5rem 2rem;} 241 242.text_panel { display:none; margin:auto; font-size:medium; line-height:1.3em; margin: 0; max-width:700px; } 243.text_panel li { margin:1em 0; text-align:left; } 244.text_panel p { text-align:left; } 245 246.svg_button:hover { cursor:pointer; } 247 248/* Loading spinbar */ 249.loading_spinbox { display:inline-block; border:0.4em solid #cccccc; border-radius:50%; border-top:0.4em solid black; border-bottom:0.4em solid black;-webkit-animation:spin 2s linear infinite; animation:spin 2s linear infinite; } 250@-webkit-keyframes spin { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); } } 251@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } 252 253#invisible_file_input { width:0.1px; height:0.1px; opacity:0; overflow:hidden; position:absolute; z-index:-1; } 254 255.display_none { display:none !important; } 256.display_block { display:block !important; } 257 258/* project settings */ 259#settings_panel { font-size:1rem; border-collapse:collapse; width:95%; } 260#settings_panel a { border: 1px solid #f2f2f2; } 261#settings_panel .row { display:table-row; border-bottom:1px solid #f2f2f2; } 262#settings_panel .variable { display:table-cell; width:60%; padding:0.5rem 0.5rem; } 263#settings_panel .variable div { display:block; } 264#settings_panel .variable .name { } 265#settings_panel .variable .desc { font-size:0.7em; color:#808080; padding:0.2rem 0rem; } 266#settings_panel .value { display:table-cell; vertical-align:middle; padding-left:1rem; } 267 268/* page {about, help, file not found, etc.} */ 269.display_area_content { } /* this class is used to clear the display area content */ 270.narrow_page_content li { font-size:0.9rem; margin: 0.4rem 0; } 271.narrow_page_content { width:60%; } 272 273.force_small_font { font-size:small !important; } 274.key { font-family:monospace; padding:1px 6px; background:linear-gradient(to bottom,#f0f0f0,#fcfcfc);; border:1px solid #e0e0e0; white-space:nowrap; color:#303030; border-bottom-width:2px; border-radius:3px; font-size:1.2em; } 275</style> 276<!-- END: Contents of file: via.css--> 277</head> 278 279<body onload="_via_init()" onresize="_via_update_ui_components()"> 280<!-- 281SVG icon set definitions 282Material icons downloaded from https://material.io/icons 283--> 284<svg style="display:none;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> 285<defs> 286<symbol id="via_logo"> 287<!-- Logo designed by Abhishek Dutta <adutta@robots.ox.ac.uk>, May 2018 --> 288<title>VGG Image Annotator Logo</title> 289<rect width="400" height="160" x="0" y="0" fill="#212121"></rect> 290 291<text x="56" y="130" font-family="Serif" font-size="100" fill="white">V</text> 292<text x="180" y="130" font-family="Serif" font-size="100" fill="white">I</text> 293<text x="270" y="130" font-family="Serif" font-size="100" fill="white">A</text> 294 295<rect width="80" height="100" x="52" y="40" stroke="yellow" fill="none" stroke-width="2"></rect> 296<text x="72" y="30" font-family="'Comic Sans MS', cursive, sans-serif" font-size="18" fill="yellow">VGG</text> 297 298<rect width="50" height="100" x="175" y="45" stroke="yellow" fill="none" stroke-width="2"></rect> 299<text x="175" y="35" font-family="'Comic Sans MS', cursive, sans-serif" font-size="18" fill="yellow">Image</text> 300 301<rect width="80" height="100" x="265" y="40" stroke="yellow" fill="none" stroke-width="2"></rect> 302<text x="265" y="30" font-family="'Comic Sans MS', cursive, sans-serif" font-size="18" fill="yellow">Annotator</text> 303</symbol> 304<symbol id="shape_rectangle"> 305<title>Rectangular region shape</title> 306<rect width="20" height="12" x="6" y="10" stroke-width="2"></rect> 307</symbol> 308<symbol id="shape_circle"> 309<title>Circular region shape</title> 310<circle r="10" cx="16" cy="16" stroke-width="2"></circle> 311</symbol> 312<symbol id="shape_ellipse"> 313<title>Elliptical region shape</title> 314<ellipse rx="12" ry="8" cx="16" cy="16" stroke-width="2"></ellipse> 315</symbol> 316<symbol id="shape_polygon"> 317<title>Polygon region shape</title> 318<path d="M 15.25,2.2372 3.625,11.6122 6,29.9872 l 20.75,-9.625 2.375,-14.75 z" stroke-width="2"></path> 319</symbol> 320<symbol id="shape_point"> 321<title>Point region shape</title> 322<circle r="3" cx="16" cy="16" stroke-width="2"></circle> 323</symbol> 324<symbol id="shape_polyline"> 325<title>Polyline region shape</title> 326<path d="M 2,12 10,24 18,12 24,18" stroke-width="2"></path> 327<circle r="1" cx="2" cy="12" stroke-width="2"></circle> 328<circle r="1" cx="10" cy="24" stroke-width="2"></circle> 329<circle r="1" cx="18" cy="12" stroke-width="2"></circle> 330<circle r="1" cx="24" cy="18" stroke-width="2"></circle> 331</symbol> 332 333<!-- Material icons downloaded from https://material.io/icons --> 334<symbol id="icon_settings"> 335<path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"></path> 336</symbol> 337<symbol id="icon_save"> 338<path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"></path> 339</symbol> 340<symbol id="icon_open"> 341<path d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10z"></path> 342</symbol> 343<symbol id="icon_gridon"> 344<path d="M20 2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM8 20H4v-4h4v4zm0-6H4v-4h4v4zm0-6H4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4z"></path> 345</symbol> 346<symbol id="icon_gridoff"> 347<path d="M8 4v1.45l2 2V4h4v4h-3.45l2 2H14v1.45l2 2V10h4v4h-3.45l2 2H20v1.45l2 2V4c0-1.1-.9-2-2-2H4.55l2 2H8zm8 0h4v4h-4V4zM1.27 1.27L0 2.55l2 2V20c0 1.1.9 2 2 2h15.46l2 2 1.27-1.27L1.27 1.27zM10 12.55L11.45 14H10v-1.45zm-6-6L5.45 8H4V6.55zM8 20H4v-4h4v4zm0-6H4v-4h3.45l.55.55V14zm6 6h-4v-4h3.45l.55.54V20zm2 0v-1.46L17.46 20H16z"></path> 348</symbol> 349<symbol id="icon_next"> 350<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path> 351</symbol> 352<symbol id="icon_prev"> 353<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path> 354</symbol> 355<symbol id="icon_list"> 356<path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"></path> 357</symbol> 358<symbol id="icon_zoomin"> 359<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"></path> 360<path d="M12 10h-2v2H9v-2H7V9h2V7h1v2h2v1z"/> 361</symbol> 362<symbol id="icon_zoomout"> 363<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14zM7 9h5v1H7z"></path> 364</symbol> 365<symbol id="icon_copy"> 366<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"></path> 367</symbol> 368<symbol id="icon_paste"> 369<path d="M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"></path> 370</symbol> 371<symbol id="icon_pasten"> 372<path d="M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"></path> 373<text x="8" y="18">n</text> 374</symbol> 375<symbol id="icon_pasteundo"> 376<path d="M19 2h-4.18C14.4.84 13.3 0 12 0c-1.3 0-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"></path> 377<text x="8" y="18">x</text> 378</symbol> 379<symbol id="icon_selectall"> 380<path d="M3 5h2V3c-1.1 0-2 .9-2 2zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2c0-1.1-.9-2-2-2zM5 21v-2H3c0 1.1.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2zM7 17h10V7H7v10zm2-8h6v6H9V9z"></path> 381</symbol> 382<symbol id="icon_close"> 383<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path> 384</symbol> 385<symbol id="icon_insertcomment"> 386<path d="M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"></path> 387</symbol> 388<symbol id="icon_checkbox"> 389<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path> 390</symbol> 391<symbol id="icon_fileupload"> 392<path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"></path> 393</symbol> 394<symbol id="icon_filedownload"> 395<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"></path> 396</symbol> 397</defs> 398</svg> 399 400<!-- used by invoke_with_user_inputs() to gather user inputs --> 401<div id="user_input_panel"></div> 402 403<!-- to show status messages --> 404<div id="message_panel"> 405<div id="message_panel_content" class="content"></div> 406</div> 407 408<!-- spreadsheet like editor for annotations --> 409<div id="annotation_editor_panel"> 410<div class="button_panel"> 411<span class="text_button" onclick="edit_region_metadata_in_annotation_editor()" id="button_edit_region_metadata" title="Manual annotations of regions">Region Annotations</span> 412<span class="text_button" onclick="edit_file_metadata_in_annotation_editor()" id="button_edit_file_metadata" title="Manual annotations of a file">File Annotations</span> 413 414<span class="button" style="float:right;margin-right:0.2rem;" onclick="annotation_editor_toggle_all_regions_editor()" title="Close this window of annotation editor">×</span> 415<span class="button" style="float:right;margin-right:0.2rem;" onclick="annotation_editor_increase_panel_height()" title="Increase the height of this panel">↑</span> 416<span class="button" style="float:right;margin-right:0.2rem;" onclick="annotation_editor_decrease_panel_height()" title="Decrease the height of this panel">↓</span> 417<span class="button" style="float:right;margin-right:0.2rem;" onclick="annotation_editor_increase_content_size()" title="Increase size of contents in annotation editor">+</span> 418<span class="button" style="float:right;margin-right:0.2rem;" onclick="annotation_editor_decrease_content_size()" title="Decrease size of contents in annotation editor">−</span> 419</div> 420<!-- here, a child div with id="annotation_editor" is added by javascript --> 421</div> 422 423<div class="top_panel" id="ui_top_panel"> 424<!-- menu bar --> 425<div class="menubar"> 426<ul> 427<li onclick="show_home_panel()" style="cursor:pointer;">Home</li> 428<li>Project 429<ul> 430<li onclick="project_open_select_project_file()" title="Load a VIA project (from a JSON file)">Load</li> 431<li onclick="project_save_with_confirm()" title="Save this VIA project (as a JSON file)">Save</li> 432<li onclick="settings_panel_toggle()" title="Show/edit project settings">Settings</li> 433<li class="submenu_divider"></li> 434<li onclick="sel_local_images()" title="Add images locally stored in this computer">Add local files</li> 435<li onclick="project_file_add_url_with_input()" title="Add images from a web URL (e.g. http://www.robots.ox.ac.uk/~vgg/software/via/images/swan.jpg)">Add files from URL</li> 436<li onclick="project_file_add_abs_path_with_input()" title="Add images using absolute path of file (e.g. /home/abhishek/image1.jpg)">Add file using absolute path</li> 437<li onclick="sel_local_data_file('files_url')" title="Add images from a list of web url or absolute path stored in a text file (one url or path per line)">Add url or path from text file</li> 438<li onclick="project_file_remove_with_confirm()" title="Remove selected file (i.e. file currently being shown)">Remove file</li> 439<li class="submenu_divider"></li> 440<li onclick="sel_local_data_file('attributes')" title="Import region and file attributes from a JSON file">Import region/file attributes</li> 441<li onclick="project_save_attributes()" title="Export region and file attributes to a JSON file">Export region/file attributes</li> 442</ul> 443</li> 444 445<li>Annotation 446<ul> 447<li onclick="download_all_region_data('csv')" title="Export annotations to a CSV file">Export Annotations (as csv)</li> 448<li onclick="download_all_region_data('json')" title="Export annotaitons to a JSON file">Export Annotations (as json)</li> 449<li onclick="download_all_region_data('coco', 'json')" title="Export annotaitons to COCO (http://cocodataset.org) format">Export Annotations (COCO format)</li> 450<li onclick="" class="submenu_divider"></li> 451<li onclick="sel_local_data_file('annotations')" title="Import annotations from a CSV file">Import Annotations (from csv)</li> 452<li onclick="sel_local_data_file('annotations')" title="Import annotations from a JSON file">Import Annotations (from json)</li> 453<li onclick="sel_local_data_file('annotations_coco')" title="Import annotations from a COCO (http://cocodataset.org) formatted JSON file">Import Annotations (COCO format)</li> 454 455<li class="submenu_divider"></li> 456<li onclick="show_annotation_data()" title="Show a preview of annotations (opens in a new browser windows)">Preview Annotations</li> 457<li onclick="download_as_image()" title="Download an image containing the annotations">Download as Image</li> 458</ul> 459</li> 460 461<li>View 462<ul> 463<li onclick="image_grid_toggle()" title="Toggle between single image view and image grid view">Toggle image grid view</li> 464<li onclick="leftsidebar_toggle()" title="Show or hide the sidebar shown in left hand side">Toggle left sidebar</li> 465<li onclick="toggle_img_fn_list_visibility()" title="Show or hide a panel to update annotations corresponding to file and region">Toggle image filename list</li> 466<li onclick="toggle_message_visibility()" title="Show or hide status messages that appears at the bottom of the page">Toggle status message visibility</li> 467<li class="submenu_divider"></li> 468<li onclick="toggle_attributes_editor()" title="Show or hide a panel to update file and region attributes">Toggle attributes editor</li> 469<li onclick="annotation_editor_toggle_all_regions_editor()" title="Show or hide a panel to update annotations corresponding to file and region">Toggle annotation editor (Space)</li> 470<li class="submenu_divider"></li> 471<li onclick="toggle_region_boundary_visibility()" title="Show or hide the region boundaries">Show/hide region boundaries (b)</li> 472<li onclick="toggle_region_id_visibility()" title="Show or hide the region id labels">Show/hide region labels (l)</li> 473<li onclick="toggle_region_info_visibility()" title="Show or hide the image coordinates">Show/hide region info.</li> 474</ul> 475</li> 476 477<li>Help 478<ul> 479<li onclick="set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_GETTING_STARTED)" title="Show a guide to get started with this application">Getting Started</li> 480<li title="Visit the project page for this application"><a href="http://www.robots.ox.ac.uk/~vgg/software/via/" target="_blank">VGG Project Page</a></li> 481<li onclick="" title="Report an issue to the developers of this application (requires an account at gitlab.com)"><a href="https://gitlab.com/vgg/via/issues" target="_blank">Report issues</a></li> 482<li class="submenu_divider"></li> 483<li><a target="_blank" href="https://gitlab.com/vgg/via/blob/master/Contributors.md" title="List of people who have contributed towards the development of VIA">Contributors</a></li> 484<li onclick="set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_LICENSE)" title="View license of this application">License</li> 485<li onclick="set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_ABOUT)" title="Show more details about this application">About VIA</li> 486</ul> 487</li> 488</ul> 489</div> <!-- end of menubar --> 490 491<!-- Shortcut toolbar --> 492<div class="toolbar"> 493<svg onclick="project_open_select_project_file()" viewbox="0 0 24 24"><use xlink:href="#icon_open"></use><title>Open Project</title></svg> 494<svg onclick="project_save_with_confirm()" viewbox="0 0 24 24"><use xlink:href="#icon_save"></use><title>Save Project</title></svg> 495<svg onclick="settings_panel_toggle()" viewbox="0 0 24 24"><use xlink:href="#icon_settings"></use><title>Update Project Settings</title></svg> 496<!-- 497<svg onclick="" viewbox="0 0 24 24"><use xlink:href="#icon_checkbox"></use><title>Locate Files</title></svg> 498--> 499 500<svg onclick="sel_local_data_file('annotations')" style="margin-left:1rem;" viewbox="0 0 24 24"><use xlink:href="#icon_fileupload"></use><title>Import Annotations from CSV</title></svg> 501<svg onclick="download_all_region_data('csv')" viewbox="0 0 24 24"><use xlink:href="#icon_filedownload"></use><title>Download Annotations as CSV</title></svg> 502 503<svg onclick="image_grid_toggle()" id="toolbar_image_grid_toggle" style="margin-left:1rem;" viewbox="0 0 24 24"><use xlink:href="#icon_gridon"></use><title>Switch to Image Grid View</title></svg> 504<svg onclick="annotation_editor_toggle_all_regions_editor()" viewbox="0 0 24 24"><use xlink:href="#icon_insertcomment"></use><title>Toggle Annotation Editor</title></svg> 505 506<svg onclick="move_to_prev_image()" style="margin-left:1rem;" viewbox="0 0 24 24"><use xlink:href="#icon_prev"></use><title>Previous</title></svg> 507<svg onclick="toggle_img_fn_list_visibility()" viewbox="0 0 24 24"><use xlink:href="#icon_list"></use><title>Toggle Filename List</title></svg> 508<svg onclick="move_to_next_image()" viewbox="0 0 24 24"><use xlink:href="#icon_next"></use><title>Next</title></svg> 509 510<svg onclick="zoom_in()" style="margin-left:1rem;" viewbox="0 0 24 24"><use xlink:href="#icon_zoomin"></use><title>Zoom In</title></svg> 511<svg onclick="zoom_out()" viewbox="0 0 24 24"><use xlink:href="#icon_zoomout"></use><title>Zoom Out</title></svg> 512 513<svg onclick="sel_all_regions()" viewbox="0 0 24 24" style="margin-left:1rem;"><use xlink:href="#icon_selectall"></use><title>Select All Regions</title></svg> 514<svg onclick="copy_sel_regions()" viewbox="0 0 24 24"><use xlink:href="#icon_copy"></use><title>Copy Regions</title></svg> 515<svg onclick="paste_sel_regions_in_current_image()" viewbox="0 0 24 24"><use xlink:href="#icon_paste"></use><title>Paste Regions</title></svg> 516<svg onclick="paste_to_multiple_images_with_confirm()" viewbox="0 0 24 24"><use xlink:href="#icon_pasten"></use><title>Paste Region in Multiple Images</title></svg> 517<svg onclick="del_sel_regions_with_confirm()" viewbox="0 0 24 24"><use xlink:href="#icon_pasteundo"></use><title>Undo Regions Pasted in Multiple Images</title></svg> 518<svg onclick="del_sel_regions()" viewbox="0 0 24 24"><use xlink:href="#icon_close"></use><title>Delete Region</title></svg> 519</div> 520<!-- end of shortcut toolbar --> 521<input type="file" id="invisible_file_input" name="files[]" style="display:none"> 522</div> <!-- endof #top_panel --> 523 524<!-- Middle Panel contains a left-sidebar and image display areas --> 525<div class="middle_panel"> 526<!-- this panel contains a button to shows the left side bar --> 527<div id="leftsidebar_collapse_panel"> 528<span class="text_button" onclick="leftsidebar_toggle()" title="Show left sidebar">▸</span> 529</div> 530 531<div id="leftsidebar"> 532<div class="leftsidebar_accordion_panel show" style="float:right; border:2px solid #f2f2f2;"> 533<span class="text_button" onclick="leftsidebar_decrease_width()" title="Reduce width of this toolbar panel">←</span> 534<span class="text_button" onclick="leftsidebar_increase_width()" title="Increase width of this toolbar panel">→</span> 535<span class="text_button" onclick="leftsidebar_toggle()" title="Show/hide this toolbar panel">◂</span> 536</div> 537 538<button class="leftsidebar_accordion active">Region Shape</button> 539<div class="leftsidebar_accordion_panel show"> 540<ul class="region_shape"> 541<li id="region_shape_rect" class="selected" onclick="select_region_shape('rect')" title="Rectangle (Shortcut key 1)"><svg height="32" viewbox="0 0 32 32"><use xlink:href="#shape_rectangle"></use></svg></li> 542<li id="region_shape_circle" onclick="select_region_shape('circle')" title="Circle (Shortcut key 2)"><svg height="32" viewbox="0 0 32 32"><use xlink:href="#shape_circle"></use></svg></li> 543<li id="region_shape_ellipse" onclick="select_region_shape('ellipse')" title="Ellipse (Shortcut key 3)"><svg height="32" viewbox="0 0 32 32"><use xlink:href="#shape_ellipse"></use></svg></li> 544<li id="region_shape_polygon" onclick="select_region_shape('polygon')" title="Polygon (Shortcut key 4)"><svg height="32" viewbox="0 0 32 32"><use xlink:href="#shape_polygon"></use></svg></li> 545<li id="region_shape_point" onclick="select_region_shape('point')" title="Point (Shortcut key 5)"><svg height="32" viewbox="0 0 32 32"><use xlink:href="#shape_point"></use></svg></li> 546<li id="region_shape_polyline" onclick="select_region_shape('polyline')" title="Polyline (Shortcut key 6)"><svg height="32" viewbox="0 0 32 32"><use xlink:href="#shape_polyline"></use></svg></li> 547</ul> 548<div id="region_info" class="display_none"> </div> 549</div> 550 551<!-- Project --> 552<button class="leftsidebar_accordion active" id="project_panel_title">Project</button> 553<div class="leftsidebar_accordion_panel show" id="img_fn_list_panel"> 554<div id="project_info_panel"> 555<div class="row"> 556<span class="col"><label for="project_name">Name: </label></span> 557<span class="col"><input type="text" value="" onchange="project_on_name_update(this)" id="project_name" title="VIA project name"></span> 558</div> 559</div> 560<div id="project_tools_panel"> 561<div class="button_panel" style="margin:0.1rem 0;" > 562<select style="width:48%" id="filelist_preset_filters_list" onchange="img_fn_list_onpresetfilter_select()" title="Filter file list using predefined filters"> 563<option value="all">All files</option> 564<option value="files_without_region">Show files without regions</option> 565<option value="files_missing_region_annotations">Show files missing region annotations</option> 566<option value="files_missing_file_annotations">Show files missing file annotations</option> 567<option value="files_error_loading">Files that could not be loaded</option> 568<option value="regex">Regular Expression</option> 569<option value="files_region_attribute">Filter by Region Attribute</option> 570</select> 571<input style="width:50%" type="text" placeholder="regular expression" oninput="img_fn_list_onregex()" id="img_fn_list_regex" title="Filter using regular expression"> 572</div> 573</div> 574<div id="img_fn_list"></div> 575<p> 576<div class="button_panel"> 577<span class="button" onclick="sel_local_images()" title="Add new file from local disk">Add Files</span> 578<span class="button" onclick="project_file_add_url_with_input()" title="Add new file using URL">Add URL</span> 579<span class="button" onclick="project_file_remove_with_confirm()" title="Remove selected file (i.e. file currently being shown) from project">Remove</span> 580</div> 581</p> 582</div> 583 584<!-- Attributes --> 585<button class="leftsidebar_accordion" id="attributes_editor_panel_title">Attributes</button> 586<div class="leftsidebar_accordion_panel" id="attributes_editor_panel"> 587<div class="button_panel" style="padding:1rem 0;"> 588<span class="text_button" onclick="show_region_attributes_update_panel()" id="button_show_region_attributes" title="Show region attributes">Region Attributes</span> 589<span class="text_button" onclick="show_file_attributes_update_panel()" id="button_show_file_attributes" title="Show file attributes">File Attributes</span> 590</div> 591<div id="attributes_update_panel"> 592<div class="button_panel"> 593<input style="width:70%" type="text" placeholder="attribute name" id="user_input_attribute_id" value=""> 594<span id="button_add_new_attribute" class="button" onclick="add_new_attribute_from_user_input()" title="Add new attribute">+</span> 595<span id="button_del_attribute" class="button" onclick="delete_existing_attribute_with_confirm()" title="Delete existing attribute">−</span> 596</div> 597<div class="button_panel" style="margin:0.1rem 0;" > 598<select style="width:100%" id="attributes_name_list" onchange="update_current_attribute_id(this)" title="List of existing attributes"></select> 599</div> 600<div id="attribute_properties"></div> 601<div id="attribute_options"></div> 602<p style="text-align:center"> 603<span class="text_button" title="Show a spreadsheet like editor for all manual annotations" onclick="annotation_editor_toggle_all_regions_editor()">Toggle Annotation Editor</span> 604</p> 605</div> 606</div> 607 608<button class="leftsidebar_accordion">Keyboard Shortcuts</button> 609<div class="leftsidebar_accordion_panel"> 610<div style="display:block; text-align:center; padding:1rem;">Available only on image focus</div> 611<table> 612<tr> 613<td style="width:8em;"><span class="key">←</span> <span class="key">↑</span> <span class="key">→</span> <span class="key">↓</span></td> 614<td>Move selected region by 1 px (Shift to jump)</td> 615</tr> 616<tr> 617<td><span class="key">a</span></td> 618<td>Select all regions</td> 619</tr> 620 621<tr> 622<td><span class="key">c</span></td> 623<td>Copy selected regions</td> 624</tr> 625<tr> 626<td><span class="key">v</span></td> 627<td>Paste selected regions</td> 628</tr> 629<tr> 630<td><span class="key">d</span></td> 631<td>Delete selected regions</td> 632</tr> 633<tr> 634<td><span class="key">Ctrl</span> + Wheel</td> 635<td>Zoom in/out (mouse cursor is over image)</td> 636</tr> 637<tr> 638<td><span class="key">l</span></td> 639<td>Toggle region label</td> 640</tr> 641<tr> 642<td><span class="key">b</span></td> 643<td>Toggle region boundary</td> 644</tr> 645<tr> 646<td><span class="key">Enter</span></td> 647<td>Finish drawing polyshape</td> 648</tr> 649<tr> 650<td><span class="key">Backspace</span></td> 651<td>Delete last polyshape vertex</td> 652</tr> 653</table> 654 655<div style="display:block; text-align:center; padding:1rem;">Always Available</div> 656<table> 657<tr> 658<td style="width:8em;"><span class="key">←</span> <span class="key">→</span></td> 659<td>Move to next/previous image</td> 660</tr> 661<tr> 662<td><span class="key">+</span> <span class="key">-</span> <span class="key">=</span></td> 663<td>Zoom in/out/reset</td> 664</tr> 665<tr> 666<td><span class="key">↑</span></td> 667<td>Update region label</td> 668</tr> 669<tr> 670<td><span class="key">↓</span></td> 671<td>Update region colour</td> 672</tr> 673<tr> 674<td><span class="key">Spacebar</span></td> 675<td>Toggle annotation editor (Ctrl to toggle on image editor)</td> 676</tr> 677<tr> 678<td><span class="key">Home</span> / <span class="key">h</span></td> 679<td>Jump to first image</td> 680</tr> 681<tr> 682<td><span class="key">End</span> / <span class="key">e</span></td> 683<td>Jump to last image</td> 684</tr> 685<tr> 686<td><span class="key">PgUp</span> / <span class="key">u</span></td> 687<td>Jump several images</td> 688</tr> 689<tr> 690<td><span class="key">PgDown</span> / <span class="key">d</span></td> 691<td>Jump several images</td> 692</tr> 693 694<tr> 695<td><span class="key">Esc</span></td> 696<td>Cancel ongoing task</td> 697</tr> 698</table> 699</div> 700 701</div> <!-- end of leftsidebar --> 702 703<!-- Main display area: contains image canvas, ... --> 704<div id="display_area"> 705<div id="image_panel" class="display_area_content display_none"> 706<!-- buffer images using <img> element will be added here --> 707 708<!-- @todo: in future versions, this canvas will be replaced by a <svg> element --> 709<canvas id="region_canvas" width="1" height="1" tabindex="1">Sorry, your browser does not support HTML5 Canvas functionality which is required for this application.</canvas> 710<!-- here, a child div with id="annotation_editor" is added by javascript --> 711</div> 712<div id="image_grid_panel" class="display_area_content display_none"> 713 714<div id="image_grid_group_panel"> 715<span class="tool">Group by <select id="image_grid_toolbar_group_by_select" onchange="image_grid_toolbar_onchange_group_by_select(this)"></select></span> 716</div> 717 718<div id="image_grid_toolbar"> 719<span>Selected</span> 720<span id="image_grid_group_by_sel_img_count">0</span> 721<span>of</span> 722<span id="image_grid_group_by_img_count">0</span> 723<span>images in current group, show</span> 724 725<span> 726<select id="image_grid_show_image_policy" onchange="image_grid_onchange_show_image_policy(this)"> 727<option value="all">all images (paginated)</option> 728<option value="first_mid_last">only first, middle and last image</option> 729<option value="even_indexed">even indexed images (i.e. 0,2,4,...)</option> 730<option value="odd_indexed">odd indexed images (i.e. 1,3,5,...)</option> 731<option value="gap5">images 1, 5, 10, 15,...</option> 732<option value="gap25">images 1, 25, 50, 75, ...</option> 733<option value="gap50">images 1, 50, 100, 150, ...</option> 734</select> 735</span> 736 737<div id="image_grid_nav"></div> 738</div> 739 740<div id="image_grid_content"> 741<div id="image_grid_content_img"></div> 742<svg xmlns:xlink="http://www.w3.org/2000/svg" id="image_grid_content_rshape"></svg> 743</div> 744 745<div id="image_grid_info"> 746</div> 747</div> <!-- end of image grid panel --> 748 749<div id="settings_panel" class="display_area_content display_none"> 750<h2>Settings</h2> 751<div class="row"> 752<div class="variable"> 753<div class="name">Project Name</div> 754</div> 755 756<div class="value"> 757<input type="text" id="_via_settings.project.name"/> 758</div> 759</div> 760 761<div class="row"> 762<div class="variable"> 763<div class="name">Default Path</div> 764<div class="desc">If all images in your project are saved in a single folder, set the default path to the location of this folder. The VIA application will load images from this folder by default. Note: a default path of <code>"./"</code> indicates that the folder containing <code>via.html</code> application file also contains the images in this project. For example: <code>/datasets/VOC2012/JPEGImages/</code> or <code>C:\Documents\data\</code> <strong>(note the trailing <code>/</code> and <code>\</code></strong>)</div> 765</div> 766 767<div class="value"> 768<input type="text" id="_via_settings.core.default_filepath" placeholder="/datasets/pascal/voc2012/VOCdevkit/VOC2012/JPEGImages/"/> 769</div> 770</div> 771 772<div class="row"> 773<div class="variable"> 774<div class="name">Search Path List</div> 775<div class="desc">If you define multiple paths, all these folders will be searched to find images in this project. We do not recommend this approach as it is computationally expensive to search for images in multiple folders. <ol id="_via_settings.core.filepath"></ol></div> 776</div> 777 778<div class="value"> 779<input type="text" id="settings_input_new_filepath" placeholder="/datasets/pascal/voc2012/VOCdevkit/VOC2012/JPEGImages"/> 780</div> 781</div> 782 783<div class="row"> 784<div class="variable"> 785<div class="name">Region Label</div> 786<div class="desc">By default, each region in an image is labelled using the region-id. Here, you can select a more descriptive labelling of regions.</div> 787</div> 788 789<div class="value"> 790<select id="_via_settings.ui.image.region_label"></select> 791</div> 792</div> 793 794<div class="row"> 795<div class="variable"> 796<div class="name">Region Colour</div> 797<div class="desc">By default, each region is drawn using a single colour. Using this setting, you can assign a unique colour to regions grouped according to a region attribute.</div> 798</div> 799 800<div class="value"> 801<select id="_via_settings.ui.image.region_color"></select> 802</div> 803</div> 804 805<div class="row"> 806<div class="variable"> 807<div class="name">Region Label Font</div> 808<div class="desc">Font size and font family for showing region labels.</div> 809</div> 810 811<div class="value"> 812<input id="_via_settings.ui.image.region_label_font" placeholder="12px Arial"/> 813</div> 814</div> 815 816<div class="row"> 817<div class="variable"> 818<div class="name">Preload Buffer Size</div> 819<div class="desc">Images are preloaded in buffer to allow smoother navigation of next/prev images. A large buffer size may slow down the overall browser performance. To disable preloading, set buffer size to 0.</div> 820</div> 821<div class="value"> 822<input type="text" id="_via_settings.core.buffer_size" /> 823</div> 824</div> 825 826<div class="row"> 827<div class="variable"> 828<div class="name">On-image Annotation Editor</div> 829<div class="desc">When a single region is selected, the on-image annotation editor is gets activated which the user to update annotations of this region. By default, this on-image annotation editor is placed near the selected region.</div> 830</div> 831 832<div class="value"> 833<select id="_via_settings.ui.image.on_image_annotation_editor_placement"> 834<option value="NEAR_REGION">close to selected region</option> 835<option value="IMAGE_BOTTOM">at the bottom of image being annotated</option> 836<option value="DISABLE">DISABLE on-image annotation editor</option> 837</select> 838</div> 839</div> 840 841<div class="row" style="border:none;"> 842<button onclick="settings_save()" value="save_settings" style="margin-top:2rem">Save</button> 843<button onclick="settings_panel_toggle()" value="cancel_settings" style="margin-left:2rem;">Cancel</button> 844</div> 845</div> <!-- end of settings panel --> 846 847<div id="page_404" class="display_area_content display_none narrow_page_content"> 848<h2>File Not Found</h2> 849<p>Filename: <span style="font-family:Mono;" id="page_404_filename"></span></p> 850 851<p>We recommend that you update the default path in <span class="text_button" title="Show Project Settings" onclick="settings_panel_toggle()">project settings</span> to the folder which contains this image.</p> 852 853<p>A temporary fix is to use <span class="text_button" title="Load or Add Images" onclick="sel_local_images()">browser's file selector</span> to manually locate and add this file. We do not recommend this approach because it requires you to repeat this process every time your load this project in the VIA application.</p> 854</div> <!-- end of file not found panel --> 855 856<div id="page_start_info" class="display_area_content display_none narrow_page_content"> 857<ul> 858<li>To start annotation, <span class="text_button" title="Load or Add Images" onclick="sel_local_images()">select images</span> (or, add images from <span class="text_button" title="Add images from a web URL (e.g. http://www.robots.ox.ac.uk/~vgg/software/via/images/swan.jpg)" onclick="project_file_add_url_with_input()">URL</span> or <span class="text_button" title="Add images using absolute path of file (e.g. /home/abhishek/image1.jpg)" onclick="project_file_add_abs_path_with_input()">absolute path</span>) and draw regions</li> 859<li>Use <span class="text_button" title="Toggle attributes editor panel" onclick="toggle_attributes_editor()">attribute editor</span> to define attributes (e.g. name) and <span class="text_button" title="Toggle annotations editor panel" onclick="annotation_editor_toggle_all_regions_editor()">annotation editor</span> to describe each region (e.g. cat) using these attributes.</li> 860<li>Remember to <span class="text_button" onclick="project_save_with_confirm()">save</span> your project before closing this application so that you can <span class="text_button" onclick="project_open_select_project_file()">load</span> it later to continue annotation.</li> 861<li>For help, see the <span class="text_button" onclick="set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_GETTING_STARTED)">Getting Started</span> page and pre-loaded demo: <a href="http://www.robots.ox.ac.uk/~vgg/software/via/via_demo.html">image annotation</a> and <a href="http://www.robots.ox.ac.uk/~vgg/software/via/via_face_demo.html">face annotation</a>.</li> 862</ul> 863 864</div> 865 866<div id="page_getting_started" class="display_area_content display_none narrow_page_content"> 867<p>A more detailed user guide (with screenshots and descriptions) is <a href="http://www.robots.ox.ac.uk/~vgg/software/via/docs/user_guide.html">available here</a>.</p> 868<ol> 869<li><strong>Load Images</strong>: The first step is to load all the images that you wish to annotate. There are multiple ways to add images to a VIA project. Choose the method that suits your use case. 870<ul> 871<li>Method 1: Selecting local files using browser's file selector 872<ol> 873<li>Click <span class="text_button" title="Load or Add Images" onclick="sel_local_images()"><code>Project → Add local files</code></span></li> 874<li>Select desired images and click <code>Open</code></li> 875</ol> 876</li> 877<li>Method 2: Adding files from URL or absolute path 878<ol> 879<li>Click <span class="text_button" title="Add images from a web URL (e.g. http://www.robots.ox.ac.uk/~vgg/software/via/images/swan.jpg)" onclick="project_file_add_url_with_input()"><code>Project → Add files from URL</code></span></li> 880<li>Enter URL and click <code>OK</code></li> 881</ol> 882</li> 883<li>Method 3: Adding files from list of url or absolute path stored in text file 884<ol> 885<li>Create a text file containing URL and absolute path (one per line)</li> 886<li>Click <span class="text_button" title="Add images from a list of web url or absolute path stored in a text file (one url or path per line)" onclick="sel_local_data_file('files_url')"><code>Project → Add url or path from text file</code></span></li> 887<li>Select the text file and click <code>Open</code></li> 888</ol> 889</li> 890</ul> 891</li> 892<li><strong>Draw Regions</strong>: Select a region shape (<span class="text_button" onclick="select_region_shape('rect')">rectangle</span>, <span class="text_button" onclick="select_region_shape('circle')">circle</span>, <span class="text_button" onclick="select_region_shape('ellipse')">ellipse</span>, <span class="text_button" onclick="select_region_shape('polygon')">polygon</span>, <span class="text_button" onclick="select_region_shape('point')">point</span>, <span class="text_button" onclick="select_region_shape('polyline')">polyline</span>) from the left sidebar and draw regions as follows: 893 894<ul> 895<li>Rectangle, Circle and Ellipse 896<ul> 897<li>Press left mouse button, drag mouse cursor and release mouse button.</li> 898<li>To define a point inside an existing region, click inside the region to select it (if not already selected), now press left mouse button, drag and release to draw region inside existing region.</li> 899<li>To select, click inside the region. If the click point contains multiple regions, then clicking multiple times at that location shuffles selection through those regions.</li> 900</ul> 901</li> 902</ul> 903 904<ul> 905<li>Point 906<ul> 907<li>Click to define points.</li> 908<li>To draw a region inside existing region, click inside the region to select it (if not already selected), now click again to define the point.</li> 909<li>To select, click on (or near) the existing point.</li> 910</ul> 911</li> 912</ul> 913 914<ul> 915<li>Polygon and Polyline 916<ul> 917<li>Click to define vertices.</li> 918<li>Press <strong>[Enter]</strong> to finish drawing the region or press [Esc] to cancel.</li> 919<li>If the first vertex needs to be defined inside an existing region, click inside the region to select it (if not already selected), now click again to define the vertex.</li> 920<li>To select, click inside the region. If the click point contains multiple regions, then clicking multiple times at that location shuffles selection through those regions.</li> 921</ul> 922</li> 923</ul> 924</li> 925 926<li><strong>Create Annotations</strong>: For a more detailed description of this step, see <a href="http://www.robots.ox.ac.uk/~vgg/software/via/docs/creating_annotations.html">Creating Annotations : VIA User Guide</a>. Click the <span class="text_button" onclick="annotation_editor_toggle_all_regions_editor()"><code>View → Toggle attributes editor</code></span> to show attributes editor panel in left sidebar and add the desired file or region attributes (e.g. name). Now click <span class="text_button" onclick="annotation_editor_toggle_all_regions_editor()"><code>View → Toggle annotations editor</code></span> to show the annotation editor panel in the bottom side. Update the annotations for each region.</li> 927<li><strong>Export Annotations</strong>: To export the annotations in json or csv format, click <span class="text_button" onclick="download_all_region_data('csv')"><code>Annotation → Export annotations</code></span> in top menubar.</li> 928<li><strong>Save Project</strong>: To save the project, click <span class="text_button" onclick="project_save_with_confirm()"><code>Project → Save</code></span> in top menubar.</li> 929</ol> 930</div> 931 932<div id="page_load_ongoing" class="display_area_content narrow_page_content"> 933<div style="text-align:center"> 934<a href="http://www.robots.ox.ac.uk/~vgg/software/via/"> 935<svg height="160" viewbox="0 0 400 160" style="background-color:#212121;"> 936<use xlink:href="#via_logo"></use> 937</svg> 938</a> 939<div style="margin-top:4rem">Loading ...</div> 940</div> 941</div> 942 943<div id="page_about" class="display_area_content display_none" style="width:40rem !important"> 944<div style="text-align:center"> 945<a href="http://www.robots.ox.ac.uk/~vgg/software/via/"> 946<svg height="160" viewbox="0 0 400 160" style="background-color:#212121;"> 947<use xlink:href="#via_logo"></use> 948</svg> 949</a> 950</div> 951 952<p style="font-family:mono; font-size:0.8em;text-align:center;"><a href="https://gitlab.com/vgg/via/blob/master/CHANGELOG">Version 2.0.12</a></p> 953<p>VGG Image Annotator (VIA) is an image annotation tool that can be used to define regions in an image and create textual descriptions of those regions. VIA is an <a href="https://gitlab.com/vgg/via/">open source project</a> developed at the <a href="http://www.robots.ox.ac.uk/~vgg/">Visual Geometry Group</a> and released under the BSD-2 clause <a href="https://gitlab.com/vgg/via/blob/master/LICENSE">license</a>.</p> 954<p>Here is a list of some salient features of VIA: 955<ul> 956<li>based solely on HTML, CSS and Javascript (no external javascript libraries)</li> 957<li>can be used off-line (full application in a single html file of size < 400KB)</li> 958<li>requires nothing more than a modern web browser (tested on Firefox, Chrome and Safari)</li> 959<li>supported region shapes: rectangle, circle, ellipse, polygon, point and polyline</li> 960<li>import/export of region data in csv and json file format</li> 961</ul> 962</p> 963<p>For more details, visit <a href="http://www.robots.ox.ac.uk/~vgg/software/via/">http://www.robots.ox.ac.uk/~vgg/software/via/</a>.</p> 964<p> </p> 965<p>Copyright © 2016-2021, <a href="mailto:adutta-removeme@robots.ox.ac.uk">Abhishek Dutta</a>,Visual Geometry Group, Oxford University and <a target="_blank" href="https://gitlab.com/vgg/via/blob/master/Contributors.md">VIA Contributors</a>.</p> 966</div> <!-- end of page_about --> 967 968<div id="page_license" class="display_area_content display_none narrow_page_content"> 969<pre> 970Copyright (c) 2016-2022, Abhishek Dutta, Visual Geometry Group, Oxford University and VIA Contributors. 971All rights reserved. 972 973Redistribution and use in source and binary forms, with or without 974modification, are permitted provided that the following conditions are met: 975 976Redistributions of source code must retain the above copyright notice, this 977list of conditions and the following disclaimer. 978Redistributions in binary form must reproduce the above copyright notice, 979this list of conditions and the following disclaimer in the documentation 980and/or other materials provided with the distribution. 981THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 982AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 983IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 984ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 985LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 986CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 987SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 988INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 989CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 990ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 991POSSIBILITY OF SUCH DAMAGE. 992</pre> 993</div> 994</div> <!-- end of display_area --> 995</div> <!-- end of middle_panel --> 996 997<!-- this vertical spacer is needed to allow scrollbar to show 998items like Keyboard Shortcut hidden under the attributes panel --> 999<div style="width: 100%;" id="vertical_space"></div> 1000 1001<!-- DEMO SCRIPT AUTOMATICALLY INSERTED BY VIA PACKER SCRIPT --> 1002<!-- START: Contents of file: via.js--> 1003<script> 1004/* 1005VGG Image Annotator (via) 1006www.robots.ox.ac.uk/~vgg/software/via/ 1007 1008Copyright (c) 2016-2019, Abhishek Dutta, Visual Geometry Group, Oxford University and VIA Contributors. 1009All rights reserved. 1010 1011Redistribution and use in source and binary forms, with or without 1012modification, are permitted provided that the following conditions are met: 1013 1014Redistributions of source code must retain the above copyright notice, this 1015list of conditions and the following disclaimer. 1016Redistributions in binary form must reproduce the above copyright notice, 1017this list of conditions and the following disclaimer in the documentation 1018and/or other materials provided with the distribution. 1019THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 1020AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1021IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1022ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 1023LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 1024CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 1025SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 1026INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 1027CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 1028ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 1029POSSIBILITY OF SUCH DAMAGE. 1030*/ 1031 1032/* 1033Links: 1034- https://gitlab.com/vgg/via/blob/master/Contributors.md : list of developers who have contributed code to the VIA project. 1035- https://gitlab.com/vgg/via/blob/master/CodeDoc.md : source code documentation 1036- https://gitlab.com/vgg/via/blob/master/CONTRIBUTING.md : guide for contributors 1037 1038This source code can be grouped into the following categories: 1039- Data structure for annotations 1040- Initialization routine 1041- Handlers for top navigation bar 1042- Local file uploaders 1043- Data Importer 1044- Data Exporter 1045- Maintainers of user interface 1046- Image click handlers 1047- Canvas update routines 1048- Region collision routines 1049- Shortcut key handlers 1050- Persistence of annotation data in browser cache (i.e. localStorage) 1051- Handlers for attributes input panel (spreadsheet like user input panel) 1052*/ 1053 1054"use strict"; 1055 1056var VIA_VERSION = '2.0.12'; 1057var VIA_NAME = 'VGG Image Annotator'; 1058var VIA_SHORT_NAME = 'VIA'; 1059var VIA_REGION_SHAPE = { RECT:'rect', 1060CIRCLE:'circle', 1061ELLIPSE:'ellipse', 1062POLYGON:'polygon', 1063POINT:'point', 1064POLYLINE:'polyline' 1065}; 1066 1067var VIA_ATTRIBUTE_TYPE = { TEXT:'text', 1068CHECKBOX:'checkbox', 1069RADIO:'radio', 1070IMAGE:'image', 1071DROPDOWN:'dropdown' 1072}; 1073 1074var VIA_DISPLAY_AREA_CONTENT_NAME = {IMAGE:'image_panel', 1075IMAGE_GRID:'image_grid_panel', 1076SETTINGS:'settings_panel', 1077PAGE_404:'page_404', 1078PAGE_GETTING_STARTED:'page_getting_started', 1079PAGE_ABOUT:'page_about', 1080PAGE_START_INFO:'page_start_info', 1081PAGE_LICENSE:'page_license' 1082}; 1083 1084var VIA_ANNOTATION_EDITOR_MODE = {SINGLE_REGION:'single_region', 1085ALL_REGIONS:'all_regions'}; 1086var VIA_ANNOTATION_EDITOR_PLACEMENT = {NEAR_REGION:'NEAR_REGION', 1087IMAGE_BOTTOM:'IMAGE_BOTTOM', 1088DISABLE:'DISABLE'}; 1089 1090var VIA_REGION_EDGE_TOL = 5; // pixel 1091var VIA_REGION_CONTROL_POINT_SIZE = 2; 1092var VIA_POLYGON_VERTEX_MATCH_TOL = 10; 1093var VIA_REGION_MIN_DIM = 3; 1094var VIA_MOUSE_CLICK_TOL = 2; 1095var VIA_ELLIPSE_EDGE_TOL = 0.2; // euclidean distance 1096var VIA_THETA_TOL = Math.PI/18; // 10 degrees 1097var VIA_POLYGON_RESIZE_VERTEX_OFFSET = 100; 1098var VIA_CANVAS_DEFAULT_ZOOM_LEVEL_INDEX = 3; 1099var VIA_CANVAS_ZOOM_LEVELS = [0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 2.5, 3.0, 4, 5, 6, 7, 8, 9, 10]; 1100var VIA_REGION_COLOR_LIST = ["#E69F00", "#56B4E9", "#009E73", "#D55E00", "#CC79A7", "#F0E442", "#ffffff"]; 1101// radius of control points in all shapes 1102var VIA_REGION_SHAPES_POINTS_RADIUS = 3; 1103// radius of control points in a point 1104var VIA_REGION_POINT_RADIUS = 3; 1105var VIA_REGION_POINT_RADIUS_DEFAULT = 3; 1106 1107var VIA_THEME_REGION_BOUNDARY_WIDTH = 3; 1108var VIA_THEME_BOUNDARY_LINE_COLOR = "black"; 1109// var VIA_THEME_BOUNDARY_FILL_COLOR = "yellow"; 1110var VIA_THEME_BOUNDARY_FILL_COLOR = "magenta"; 1111var VIA_THEME_SEL_REGION_FILL_COLOR = "#808080"; 1112var VIA_THEME_SEL_REGION_FILL_BOUNDARY_COLOR = "yellow"; 1113var VIA_THEME_SEL_REGION_OPACITY = 0.5; 1114var VIA_THEME_MESSAGE_TIMEOUT_MS = 6000; 1115var VIA_THEME_CONTROL_POINT_COLOR = '#ff0000'; 1116 1117var VIA_CSV_SEP = ','; 1118var VIA_CSV_QUOTE_CHAR = '"'; 1119var VIA_CSV_KEYVAL_SEP = ':'; 1120 1121var _via_img_metadata = {}; // data structure to store loaded images metadata 1122var _via_img_src = {}; // image content {abs. path, url, base64 data, etc} 1123var _via_img_fileref = {}; // reference to local images selected by using browser file selector 1124var _via_img_count = 0; // count of the loaded images 1125var _via_canvas_regions = []; // image regions spec. in canvas space 1126var _via_canvas_scale = 1.0;// current scale of canvas image 1127 1128var _via_image_id = ''; // id={filename+length} of current image 1129var _via_image_index = -1; // index 1130 1131var _via_current_image_filename; 1132var _via_current_image; 1133var _via_current_image_width; 1134var _via_current_image_height; 1135 1136// a record of image statistics (e.g. width, height) 1137var _via_img_stat = {}; 1138var _via_is_all_img_stat_read_ongoing = false; 1139var _via_img_stat_current_img_index = false; 1140 1141// image canvas 1142var _via_display_area = document.getElementById('display_area'); 1143var _via_img_panel = document.getElementById('image_panel'); 1144var _via_reg_canvas = document.getElementById('region_canvas'); 1145var _via_reg_ctx; // initialized in _via_init() 1146var _via_canvas_width, _via_canvas_height; 1147 1148// canvas zoom 1149var _via_canvas_zoom_level_index = VIA_CANVAS_DEFAULT_ZOOM_LEVEL_INDEX; // 1.0 1150var _via_canvas_scale_without_zoom = 1.0; 1151 1152// state of the application 1153var _via_is_user_drawing_region = false; 1154var _via_current_image_loaded = false; 1155var _via_is_window_resized = false; 1156var _via_is_user_resizing_region = false; 1157var _via_is_user_moving_region = false; 1158var _via_is_user_drawing_polygon = false; 1159var _via_is_region_selected = false; 1160var _via_is_all_region_selected = false; 1161var _via_is_loaded_img_list_visible = false; 1162var _via_is_attributes_panel_visible = false; 1163var _via_is_reg_attr_panel_visible = false; 1164var _via_is_file_attr_panel_visible = false; 1165var _via_is_canvas_zoomed = false; 1166var _via_is_loading_current_image = false; 1167var _via_is_region_id_visible = true; 1168var _via_is_region_boundary_visible = true; 1169var _via_is_region_info_visible = false; 1170var _via_is_ctrl_pressed = false; 1171var _via_is_debug_mode = false; 1172var _via_is_message_visible = true; 1173 1174// region 1175var _via_current_shape = VIA_REGION_SHAPE.RECT; 1176var _via_current_polygon_region_id = -1; 1177var _via_user_sel_region_id = -1; 1178var _via_click_x0 = 0; var _via_click_y0 = 0; 1179var _via_click_x1 = 0; var _via_click_y1 = 0; 1180var _via_region_click_x, _via_region_click_y; 1181var _via_region_edge = [-1, -1]; 1182var _via_current_x = 0; var _via_current_y = 0; 1183 1184// region copy/paste 1185var _via_region_selected_flag = []; // region select flag for current image 1186var _via_copied_image_regions = []; 1187var _via_paste_to_multiple_images_input; 1188 1189// message 1190var _via_message_clear_timer; 1191 1192// attributes 1193var _via_attribute_being_updated = 'region'; // {region, file} 1194var _via_attributes = { 'region':{}, 'file':{} }; 1195var _via_current_attribute_id = ''; 1196 1197// region group color 1198var _via_canvas_regions_group_color = {}; // color of each region 1199 1200// invoke a method after receiving user input 1201var _via_user_input_ok_handler = null; 1202var _via_user_input_cancel_handler = null; 1203var _via_user_input_data = {}; 1204 1205// annotation editor 1206var _via_annotaion_editor_panel = document.getElementById('annotation_editor_panel'); 1207var _via_metadata_being_updated = 'region'; // {region, file} 1208var _via_annotation_editor_mode = VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION; 1209 1210// persistence to local storage 1211var _via_is_local_storage_available = false; 1212var _via_is_save_ongoing = false; 1213 1214// all the image_id and image_filename of images added by the user is 1215// stored in _via_image_id_list and _via_image_filename_list 1216// 1217// Image filename list (img_fn_list) contains a filtered list of images 1218// currently accessible by the user. The img_fn_list is visible in the 1219// left side toolbar. image_grid, next/prev, etc operations depend on 1220// the contents of _via_img_fn_list_img_index_list. 1221var _via_image_id_list = []; // array of all image id (in order they were added by user) 1222var _via_image_filename_list = []; // array of all image filename 1223var _via_image_load_error = []; // {true, false} 1224var _via_image_filepath_resolved = []; // {true, false} 1225var _via_image_filepath_id_list = []; // path for each file 1226 1227var _via_reload_img_fn_list_table = true; 1228var _via_img_fn_list_img_index_list = []; // image index list of images show in img_fn_list 1229var _via_img_fn_list_html = []; // html representation of image filename list 1230 1231// image grid 1232var image_grid_panel = document.getElementById('image_grid_panel'); 1233var _via_display_area_content_name = ''; // describes what is currently shown in display area 1234var _via_display_area_content_name_prev = ''; 1235var _via_image_grid_requires_update = false; 1236var _via_image_grid_content_overflow = false; 1237var _via_image_grid_load_ongoing = false; 1238var _via_image_grid_page_first_index = 0; // array index in _via_img_fn_list_img_index_list[] 1239var _via_image_grid_page_last_index = -1; 1240var _via_image_grid_selected_img_index_list = []; 1241var _via_image_grid_page_img_index_list = []; // list of all image index in current page of image grid 1242var _via_image_grid_visible_img_index_list = []; // list of images currently visible in grid 1243var _via_image_grid_mousedown_img_index = -1; 1244var _via_image_grid_mouseup_img_index = -1; 1245var _via_image_grid_img_index_list = []; // list of all image index in the image grid 1246var _via_image_grid_region_index_list = []; // list of all image index in the image grid 1247var _via_image_grid_group = {}; // {'value':[image_index_list]} 1248var _via_image_grid_group_var = []; // {type, name, value} 1249var _via_image_grid_group_show_all = false; 1250var _via_image_grid_stack_prev_page = []; // stack of first img index of every page navigated so far 1251 1252// image buffer 1253var VIA_IMG_PRELOAD_INDICES = [1, -1, 2, 3, -2, 4]; // for any image, preload previous 2 and next 4 images 1254var VIA_IMG_PRELOAD_COUNT = 4; 1255var _via_buffer_preload_img_index = -1; 1256var _via_buffer_img_index_list = []; 1257var _via_buffer_img_shown_timestamp = []; 1258var _via_preload_img_promise_list = []; 1259 1260// via settings 1261var _via_settings = {}; 1262_via_settings.ui = {}; 1263_via_settings.ui.annotation_editor_height = 25; // in percent of the height of browser window 1264_via_settings.ui.annotation_editor_fontsize = 0.8;// in rem 1265_via_settings.ui.leftsidebar_width = 18; // in rem 1266 1267_via_settings.ui.image_grid = {}; 1268_via_settings.ui.image_grid.img_height = 80; // in pixel 1269_via_settings.ui.image_grid.rshape_fill = 'none'; 1270_via_settings.ui.image_grid.rshape_fill_opacity = 0.3; 1271_via_settings.ui.image_grid.rshape_stroke = 'yellow'; 1272_via_settings.ui.image_grid.rshape_stroke_width = 2; 1273_via_settings.ui.image_grid.show_region_shape = true; 1274_via_settings.ui.image_grid.show_image_policy = 'all'; 1275 1276_via_settings.ui.image = {}; 1277_via_settings.ui.image.region_label = '__via_region_id__'; // default: region_id 1278_via_settings.ui.image.region_color = '__via_default_region_color__'; // default color: yellow 1279_via_settings.ui.image.region_label_font = '10px Sans'; 1280_via_settings.ui.image.on_image_annotation_editor_placement = VIA_ANNOTATION_EDITOR_PLACEMENT.NEAR_REGION; 1281 1282_via_settings.core = {}; 1283_via_settings.core.buffer_size = 4*VIA_IMG_PRELOAD_COUNT + 2; 1284_via_settings.core.filepath = {}; 1285_via_settings.core.default_filepath = ''; 1286 1287// UI html elements 1288var invisible_file_input = document.getElementById("invisible_file_input"); 1289var display_area = document.getElementById("display_area"); 1290var ui_top_panel = document.getElementById("ui_top_panel"); 1291var image_panel = document.getElementById("image_panel"); 1292var img_buffer_now = document.getElementById("img_buffer_now"); 1293 1294var annotation_list_snippet = document.getElementById("annotation_list_snippet"); 1295var annotation_textarea = document.getElementById("annotation_textarea"); 1296 1297var img_fn_list_panel = document.getElementById('img_fn_list_panel'); 1298var img_fn_list = document.getElementById('img_fn_list'); 1299var attributes_panel = document.getElementById('attributes_panel'); 1300var leftsidebar = document.getElementById('leftsidebar'); 1301 1302var BBOX_LINE_WIDTH = 4; 1303var BBOX_SELECTED_OPACITY = 0.3; 1304var BBOX_BOUNDARY_FILL_COLOR_ANNOTATED = "#f2f2f2"; 1305var BBOX_BOUNDARY_FILL_COLOR_NEW = "#aaeeff"; 1306var BBOX_BOUNDARY_LINE_COLOR = "#1a1a1a"; 1307var BBOX_SELECTED_FILL_COLOR = "#ffffff"; 1308 1309var VIA_ANNOTATION_EDITOR_HEIGHT_CHANGE = 5; // in percent 1310var VIA_ANNOTATION_EDITOR_FONTSIZE_CHANGE = 0.1; // in rem 1311var VIA_IMAGE_GRID_IMG_HEIGHT_CHANGE = 20; // in percent 1312var VIA_LEFTSIDEBAR_WIDTH_CHANGE = 1; // in rem 1313var VIA_POLYGON_SEGMENT_SUBTENDED_ANGLE = 5; // in degree (used to approximate shapes using polygon) 1314var VIA_FLOAT_PRECISION = 3; // number of decimal places to include in float values 1315 1316// COCO Export 1317var VIA_COCO_EXPORT_RSHAPE = ['rect', 'circle', 'ellipse', 'polygon', 'point']; 1318var VIA_COCO_EXPORT_ATTRIBUTE_TYPE = [VIA_ATTRIBUTE_TYPE.DROPDOWN, 1319VIA_ATTRIBUTE_TYPE.RADIO]; 1320// 1321// Data structure to store metadata about file and regions 1322// 1323function file_metadata(filename, size) { 1324this.filename = filename; 1325this.size = size; // file size in bytes 1326this.regions = []; // array of file_region() 1327this.file_attributes = {}; // image attributes 1328} 1329 1330function file_region() { 1331this.shape_attributes = {}; // region shape attributes 1332this.region_attributes = {}; // region attributes 1333} 1334 1335// 1336// Initialization routine 1337// 1338function _via_init() { 1339console.log(VIA_NAME); 1340show_message(VIA_NAME + ' (' + VIA_SHORT_NAME + ') version ' + VIA_VERSION + 1341'. Ready !', 2*VIA_THEME_MESSAGE_TIMEOUT_MS); 1342 1343if ( _via_is_debug_mode ) { 1344document.getElementById('ui_top_panel').innerHTML += '<span>DEBUG MODE</span>'; 1345} 1346 1347document.getElementById('img_fn_list').style.display = 'block'; 1348document.getElementById('leftsidebar').style.display = 'table-cell'; 1349 1350// initialize default project 1351project_init_default_project(); 1352 1353// initialize region canvas 2D context 1354_via_init_reg_canvas_context(); 1355 1356// initialize user input handlers (for both window and via_reg_canvas) 1357// handles drawing of regions by user over the image 1358_via_init_keyboard_handlers(); 1359_via_init_mouse_handlers(); 1360 1361// initialize image grid 1362image_grid_init(); 1363 1364show_single_image_view(); 1365init_leftsidebar_accordion(); 1366attribute_update_panel_set_active_button(); 1367annotation_editor_set_active_button(); 1368init_message_panel(); 1369 1370// run attached sub-modules (if any) 1371// e.g. demo modules 1372if (typeof _via_load_submodules === 'function') { 1373console.log('Loading VIA submodule'); 1374setTimeout( async function() { 1375await _via_load_submodules(); 1376}, 100); 1377} 1378 1379} 1380 1381function _via_init_reg_canvas_context() { 1382_via_reg_ctx = _via_reg_canvas.getContext('2d'); 1383} 1384 1385function _via_init_keyboard_handlers() { 1386window.addEventListener('keydown', _via_window_keydown_handler, false); 1387_via_reg_canvas.addEventListener('keydown', _via_reg_canvas_keydown_handler, false); 1388_via_reg_canvas.addEventListener('keyup', _via_reg_canvas_keyup_handler, false); 1389} 1390 1391// handles drawing of regions over image by the user 1392function _via_init_mouse_handlers() { 1393_via_reg_canvas.addEventListener('dblclick', _via_reg_canvas_dblclick_handler, false); 1394_via_reg_canvas.addEventListener('mousedown', _via_reg_canvas_mousedown_handler, false); 1395_via_reg_canvas.addEventListener('mouseup', _via_reg_canvas_mouseup_handler, false); 1396_via_reg_canvas.addEventListener('mouseover', _via_reg_canvas_mouseover_handler, false); 1397_via_reg_canvas.addEventListener('mousemove', _via_reg_canvas_mousemove_handler, false); 1398_via_reg_canvas.addEventListener('wheel', _via_reg_canvas_mouse_wheel_listener, false); 1399// touch screen event handlers 1400// @todo: adapt for mobile users 1401_via_reg_canvas.addEventListener('touchstart', _via_reg_canvas_mousedown_handler, false); 1402_via_reg_canvas.addEventListener('touchend', _via_reg_canvas_mouseup_handler, false); 1403_via_reg_canvas.addEventListener('touchmove', _via_reg_canvas_mousemove_handler, false); 1404} 1405 1406// 1407// Download image with annotations 1408// 1409 1410function download_as_image() { 1411if ( _via_display_area_content_name !== VIA_DISPLAY_AREA_CONTENT_NAME['IMAGE'] ) { 1412show_message('This functionality is only available in single image view mode'); 1413return; 1414} else { 1415var c = document.createElement('canvas'); 1416 1417// ensures that downloaded image is scaled at current zoom level 1418c.width = _via_reg_canvas.width; 1419c.height = _via_reg_canvas.height; 1420 1421var ct = c.getContext('2d'); 1422// draw current image 1423ct.drawImage(_via_current_image, 0, 0, _via_reg_canvas.width, _via_reg_canvas.height); 1424// draw current regions 1425ct.drawImage(_via_reg_canvas, 0, 0); 1426 1427var cur_img_mime = 'image/jpeg'; 1428if ( _via_current_image.src.startsWith('data:') ) { 1429var c1 = _via_current_image.src.indexOf(':', 0); 1430var c2 = _via_current_image.src.indexOf(';', c1); 1431cur_img_mime = _via_current_image.src.substring(c1 + 1, c2); 1432} 1433 1434// extract image data from canvas 1435var saved_img = c.toDataURL(cur_img_mime); 1436saved_img.replace(cur_img_mime, "image/octet-stream"); 1437 1438// simulate user click to trigger download of image 1439var a = document.createElement('a'); 1440a.href = saved_img; 1441a.target = '_blank'; 1442a.download = _via_current_image_filename; 1443 1444// simulate a mouse click event 1445var event = new MouseEvent('click', { 1446view: window, 1447bubbles: true, 1448cancelable: true 1449}); 1450 1451a.dispatchEvent(event); 1452} 1453} 1454 1455// 1456// Display area content 1457// 1458function clear_display_area() { 1459var panels = document.getElementsByClassName('display_area_content'); 1460var i; 1461for ( i = 0; i < panels.length; ++i ) { 1462panels[i].classList.add('display_none'); 1463} 1464} 1465 1466function is_content_name_valid(content_name) { 1467var e; 1468for ( e in VIA_DISPLAY_AREA_CONTENT_NAME ) { 1469if ( VIA_DISPLAY_AREA_CONTENT_NAME[e] === content_name ) { 1470return true; 1471} 1472} 1473return false; 1474} 1475 1476function show_home_panel() { 1477show_single_image_view(); 1478} 1479 1480function set_display_area_content(content_name) { 1481if ( is_content_name_valid(content_name) ) { 1482_via_display_area_content_name_prev = _via_display_area_content_name; 1483clear_display_area(); 1484var p = document.getElementById(content_name); 1485p.classList.remove('display_none'); 1486_via_display_area_content_name = content_name; 1487} 1488} 1489 1490function show_single_image_view() { 1491if (_via_current_image_loaded) { 1492img_fn_list_clear_all_style(); 1493_via_show_img(_via_image_index); 1494set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE); 1495annotation_editor_update_content(); 1496 1497var p = document.getElementById('toolbar_image_grid_toggle'); 1498p.firstChild.setAttribute('xlink:href', '#icon_gridon'); 1499p.childNodes[1].innerHTML = 'Switch to Image Grid View'; 1500} else { 1501set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_START_INFO); 1502} 1503} 1504 1505function show_image_grid_view() { 1506if (_via_current_image_loaded) { 1507img_fn_list_clear_all_style(); 1508set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID); 1509image_grid_toolbar_update_group_by_select(); 1510 1511if ( _via_image_grid_group_var.length === 0 ) { 1512image_grid_show_all_project_images(); 1513} 1514annotation_editor_update_content(); 1515 1516var p = document.getElementById('toolbar_image_grid_toggle'); 1517p.firstChild.setAttribute('xlink:href', '#icon_gridoff'); 1518p.childNodes[1].innerHTML = 'Switch to Single Image View'; 1519 1520//edit_file_metadata_in_annotation_editor(); 1521} else { 1522set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_START_INFO); 1523} 1524} 1525 1526// 1527// Handlers for top navigation bar 1528// 1529function sel_local_images() { 1530// source: https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications 1531if (invisible_file_input) { 1532invisible_file_input.setAttribute('multiple', 'multiple'); 1533invisible_file_input.accept = '.jpg,.jpeg,.png,.bmp'; 1534invisible_file_input.onchange = project_file_add_local; 1535invisible_file_input.click(); 1536} 1537} 1538 1539// invoked by menu-item buttons in HTML UI 1540function download_all_region_data(type, file_extension) { 1541if ( typeof(file_extension) === 'undefined' ) { 1542file_extension = type; 1543} 1544// Javascript strings (DOMString) is automatically converted to utf-8 1545// see: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob 1546pack_via_metadata(type).then( function(data) { 1547var blob_attr = {type: 'text/'+file_extension+';charset=utf-8'}; 1548var all_region_data_blob = new Blob(data, blob_attr); 1549 1550var filename = 'via_export'; 1551if(typeof(_via_settings) !== 'undefined' && 1552_via_settings.hasOwnProperty('project') && 1553_via_settings['project']['name'] !== '') { 1554filename = _via_settings['project']['name']; 1555} 1556if ( file_extension !== 'csv' || file_extension !== 'json' ) { 1557filename += '_' + type + '.' + file_extension; 1558} 1559save_data_to_local_file(all_region_data_blob, filename); 1560}.bind(this), function(err) { 1561show_message('Failed to download data: [' + err + ']'); 1562}.bind(this)); 1563} 1564 1565function sel_local_data_file(type) { 1566if (invisible_file_input) { 1567switch(type) { 1568case 'annotations': 1569invisible_file_input.accept='.csv,.json'; 1570invisible_file_input.onchange = import_annotations_from_file; 1571break; 1572 1573case 'annotations_coco': 1574invisible_file_input.accept='.json'; 1575invisible_file_input.onchange = load_coco_annotations_json_file; 1576break; 1577 1578case 'files_url': 1579invisible_file_input.accept=''; 1580invisible_file_input.onchange = import_files_url_from_file; 1581break; 1582 1583case 'attributes': 1584invisible_file_input.accept='json'; 1585invisible_file_input.onchange = project_import_attributes_from_file; 1586break; 1587 1588default: 1589console.log('sel_local_data_file() : unknown type ' + type); 1590return; 1591} 1592invisible_file_input.removeAttribute('multiple'); 1593invisible_file_input.click(); 1594} 1595} 1596 1597// 1598// Data Importer 1599// 1600function import_files_url_from_file(event) { 1601var selected_files = event.target.files; 1602var i, file; 1603for ( i = 0; i < selected_files.length; ++i ) { 1604file = selected_files[i]; 1605load_text_file(file, import_files_url_from_csv); 1606} 1607} 1608 1609function import_annotations_from_file(event) { 1610var selected_files = event.target.files; 1611var i, file; 1612for ( i = 0; i < selected_files.length; ++i ) { 1613file = selected_files[i]; 1614switch ( file.type ) { 1615case '': // Fall-through // Windows 10: Firefox and Chrome do not report filetype 1616show_message('File type for ' + file.name + ' cannot be determined! Assuming text/plain.'); 1617case 'text/plain': // Fall-through 1618case 'application/vnd.ms-excel': // Fall-through // @todo: filetype of VIA csv annotations in Windows 10 , fix this (reported by @Eli Walker) 1619case 'text/csv': 1620load_text_file(file, import_annotations_from_csv); 1621break; 1622 1623case 'text/json': // Fall-through 1624case 'application/json': 1625load_text_file(file, import_annotations_from_json); 1626break; 1627 1628default: 1629show_message('Annotations cannot be imported from file of type ' + file.type); 1630break; 1631} 1632} 1633} 1634 1635function load_coco_annotations_json_file(event) { 1636load_text_file(event.target.files[0], import_coco_annotations_from_json); 1637} 1638 1639function import_annotations_from_csv(data) { 1640return new Promise( function(ok_callback, err_callback) { 1641if ( data === '' || typeof(data) === 'undefined') { 1642err_callback(); 1643} 1644 1645var region_import_count = 0; 1646var malformed_csv_lines_count = 0; 1647var file_added_count = 0; 1648 1649var line_split_regex = new RegExp('\n|\r|\r\n', 'g'); 1650var csvdata = data.split(line_split_regex); 1651 1652var parsed_header = parse_csv_header_line(csvdata[0]); 1653if ( ! parsed_header.is_header ) { 1654show_message('Header line missing in the CSV file'); 1655err_callback(); 1656return; 1657} 1658 1659var n = csvdata.length; 1660var i; 1661var first_img_id = ''; 1662for ( i = 1; i < n; ++i ) { 1663// ignore blank lines 1664if (csvdata[i].charAt(0) === '\n' || csvdata[i].charAt(0) === '') { 1665continue; 1666} 1667 1668var d = parse_csv_line(csvdata[i]); 1669 1670// check if csv line was malformed 1671if ( d.length !== parsed_header.csv_column_count ) { 1672malformed_csv_lines_count += 1; 1673continue; 1674} 1675 1676var filename = d[parsed_header.filename_index]; 1677var size = d[parsed_header.size_index]; 1678var img_id = _via_get_image_id(filename, size); 1679 1680// check if file is already present in this project 1681if ( ! _via_img_metadata.hasOwnProperty(img_id) ) { 1682img_id = project_add_new_file(filename, size); 1683if ( _via_settings.core.default_filepath === '' ) { 1684_via_img_src[img_id] = filename; 1685} else { 1686_via_file_resolve_file_to_default_filepath(img_id); 1687} 1688file_added_count += 1; 1689 1690if ( first_img_id === '' ) { 1691first_img_id = img_id; 1692} 1693} 1694 1695// copy file attributes 1696if ( d[parsed_header.file_attr_index] !== '"{}"') { 1697var fattr = d[parsed_header.file_attr_index]; 1698fattr = remove_prefix_suffix_quotes( fattr ); 1699fattr = unescape_from_csv( fattr ); 1700 1701var m = json_str_to_map( fattr ); 1702for( var key in m ) { 1703_via_img_metadata[img_id].file_attributes[key] = m[key]; 1704 1705// add this file attribute to _via_attributes 1706if ( ! _via_attributes['file'].hasOwnProperty(key) ) { 1707_via_attributes['file'][key] = { 'type':'text' }; 1708} 1709} 1710} 1711 1712var region_i = new file_region(); 1713// copy regions shape attributes 1714if ( d[parsed_header.region_shape_attr_index] !== '"{}"' ) { 1715var sattr = d[parsed_header.region_shape_attr_index]; 1716sattr = remove_prefix_suffix_quotes( sattr ); 1717sattr = unescape_from_csv( sattr ); 1718 1719var m = json_str_to_map( sattr ); 1720for ( var key in m ) { 1721region_i.shape_attributes[key] = m[key]; 1722} 1723} 1724 1725// copy region attributes 1726if ( d[parsed_header.region_attr_index] !== '"{}"' ) { 1727var rattr = d[parsed_header.region_attr_index]; 1728rattr = remove_prefix_suffix_quotes( rattr ); 1729rattr = unescape_from_csv( rattr ); 1730 1731var m = json_str_to_map( rattr ); 1732for ( var key in m ) { 1733region_i.region_attributes[key] = m[key]; 1734 1735// add this region attribute to _via_attributes 1736if ( ! _via_attributes['region'].hasOwnProperty(key) ) { 1737_via_attributes['region'][key] = { 'type':'text' }; 1738} 1739} 1740} 1741 1742// add regions only if they are present 1743if (Object.keys(region_i.shape_attributes).length > 0 || 1744Object.keys(region_i.region_attributes).length > 0 ) { 1745_via_img_metadata[img_id].regions.push(region_i); 1746region_import_count += 1; 1747} 1748} 1749show_message('Import Summary : [' + file_added_count + '] new files, ' + 1750'[' + region_import_count + '] regions, ' + 1751'[' + malformed_csv_lines_count + '] malformed csv lines.'); 1752 1753if ( file_added_count ) { 1754update_img_fn_list(); 1755} 1756 1757if ( _via_current_image_loaded ) { 1758if ( region_import_count ) { 1759update_attributes_update_panel(); 1760annotation_editor_update_content(); 1761_via_load_canvas_regions(); // image to canvas space transform 1762_via_redraw_reg_canvas(); 1763_via_reg_canvas.focus(); 1764} 1765} else { 1766if ( file_added_count ) { 1767var first_img_index = _via_image_id_list.indexOf(first_img_id); 1768_via_show_img( first_img_index ); 1769} 1770} 1771ok_callback([file_added_count, region_import_count, malformed_csv_lines_count]); 1772}); 1773} 1774 1775function parse_csv_header_line(line) { 1776var header_via_10x = '#filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes'; // VIA versions 1.0.x 1777var header_via_11x = 'filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes'; // VIA version 1.1.x 1778 1779if ( line === header_via_10x || line === header_via_11x ) { 1780return { 'is_header':true, 1781'filename_index': 0, 1782'size_index': 1, 1783'file_attr_index': 2, 1784'region_shape_attr_index': 5, 1785'region_attr_index': 6, 1786'csv_column_count': 7 1787} 1788} else { 1789return { 'is_header':false }; 1790} 1791} 1792 1793// see http://cocodataset.org/#format-data 1794function import_coco_annotations_from_json(data_str) { 1795return new Promise( function(ok_callback, err_callback) { 1796if (data_str === '' || typeof(data_str) === 'undefined') { 1797show_message('Empty file'); 1798return; 1799} 1800var coco = JSON.parse(data_str); 1801if( !coco.hasOwnProperty('info') || 1802!coco.hasOwnProperty('categories') || 1803!coco.hasOwnProperty('annotations') || 1804!coco.hasOwnProperty('images') ) { 1805show_message('File does not contain valid annotations in COCO format.'); 1806return; 1807} 1808 1809// create _via_attributes from coco['categories'] 1810var category_id_to_attribute_name = {}; 1811for( var i in coco['categories'] ) { 1812var sc = coco['categories'][i]['supercategory']; 1813var cid = coco['categories'][i]['id']; 1814var cname = coco['categories'][i]['name']; 1815if( !_via_attributes['region'].hasOwnProperty(sc)) { 1816_via_attributes['region'][sc] = {'type':VIA_ATTRIBUTE_TYPE.RADIO, 1817'description':'coco["categories"][' + i + ']=' + JSON.stringify(coco['categories'][i]), 1818'options':{}, 1819'default_options':{} 1820}; 1821} 1822_via_attributes['region'][sc]['options'][cid] = cname; 1823category_id_to_attribute_name[cid] = sc; 1824} 1825// if more than 5 options, convert the attribute type to DROPDOWN 1826for( var attr_name in _via_attributes['region'] ) { 1827if( Object.keys(_via_attributes['region'][attr_name]['options']).length > 5 ) { 1828_via_attributes['region'][attr_name]['type'] = VIA_ATTRIBUTE_TYPE.DROPDOWN; 1829} 1830} 1831 1832// create an map of image_id and their annotations 1833var image_id_to_annotation_index = {}; 1834for ( var annotation_index in coco['annotations'] ) { 1835var coco_image_id = coco.annotations[annotation_index]['image_id']; 1836if ( !image_id_to_annotation_index.hasOwnProperty(coco_image_id) ) { 1837image_id_to_annotation_index[coco_image_id] = []; 1838} 1839image_id_to_annotation_index[coco_image_id].push( annotation_index ); 1840} 1841 1842// add all files and annotations 1843_via_img_metadata = {}; 1844_via_image_id_list = []; 1845_via_image_filename_list = []; 1846_via_img_count = 0; 1847var imported_file_count = 0; 1848var imported_region_count = 0; 1849for ( var coco_img_index in coco['images'] ) { 1850var coco_img_id = coco['images'][coco_img_index]['id']; 1851var filename; 1852if ( coco.images[coco_img_index].hasOwnProperty('coco_url') && 1853coco.images[coco_img_index]['coco_url'] !== "") { 1854filename = coco.images[coco_img_index]['coco_url']; 1855} else { 1856filename = coco.images[coco_img_index]['file_name']; 1857} 1858_via_img_metadata[coco_img_id] = { 'filename':filename, 1859'size' :-1, 1860'regions' :[], 1861'file_attributes': { 1862'width' :coco.images[coco_img_index]['width'], 1863'height':coco.images[coco_img_index]['height'] 1864}, 1865}; 1866_via_image_id_list.push(coco_img_id); 1867_via_image_filename_list.push(filename); 1868_via_img_count = _via_img_count + 1; 1869 1870// add all annotations associated with this file 1871if ( image_id_to_annotation_index.hasOwnProperty(coco_img_id) ) { 1872for ( var i in image_id_to_annotation_index[coco_img_id] ) { 1873var annotation_i = coco['annotations'][ image_id_to_annotation_index[coco_img_id][i] ]; 1874var bbox_from_polygon = polygon_to_bbox(annotation_i['segmentation'][0]); 1875 1876// ensure rectangles get imported as rectangle (and not as polygon) 1877var is_rectangle = true; 1878for (var j = 0; j < annotation_i['bbox'].length; ++j) { 1879if (annotation_i['bbox'][j] !== bbox_from_polygon[j]) { 1880is_rectangle = false; 1881break; 1882} 1883} 1884 1885var region_i = { 'shape_attributes': {}, 'region_attributes': {} }; 1886var attribute_name = category_id_to_attribute_name[ annotation_i['category_id'] ]; 1887var attribute_value = annotation_i['category_id'].toString(); 1888region_i['region_attributes'][attribute_name] = attribute_value; 1889 1890if ( annotation_i['segmentation'][0].length === 8 && is_rectangle ) { 1891region_i['shape_attributes'] = { 'name':'rect', 1892'x': annotation_i['bbox'][0], 1893'y': annotation_i['bbox'][1], 1894'width': annotation_i['bbox'][2], 1895'height': annotation_i['bbox'][3]}; 1896} else { 1897region_i['shape_attributes'] = { 'name':'polygon', 1898'all_points_x':[], 1899'all_points_y':[]}; 1900for ( var j = 0; j < annotation_i['segmentation'][0].length; j = j + 2 ) { 1901region_i['shape_attributes']['all_points_x'].push( annotation_i['segmentation'][0][j] ); 1902region_i['shape_attributes']['all_points_y'].push( annotation_i['segmentation'][0][j+1] ); 1903} 1904} 1905_via_img_metadata[coco_img_id]['regions'].push(region_i); 1906imported_region_count = imported_region_count + 1; 1907} 1908} 1909} 1910show_message('Import Summary : [' + _via_img_count + '] new files, ' + 1911'[' + imported_region_count + '] regions.'); 1912 1913if(_via_img_count) { 1914update_img_fn_list(); 1915} 1916 1917if(_via_current_image_loaded) { 1918if(imported_region_count) { 1919update_attributes_update_panel(); 1920annotation_editor_update_content(); 1921_via_load_canvas_regions(); // image to canvas space transform 1922_via_redraw_reg_canvas(); 1923_via_reg_canvas.focus(); 1924} 1925} else { 1926if(_via_img_count) { 1927_via_show_img(0); 1928} 1929} 1930ok_callback([_via_img_count, imported_region_count, 0]); 1931}); 1932} 1933 1934function import_annotations_from_json(data_str) { 1935return new Promise( function(ok_callback, err_callback) { 1936if (data_str === '' || typeof(data_str) === 'undefined') { 1937return; 1938} 1939 1940var d = JSON.parse(data_str); 1941var region_import_count = 0; 1942var file_added_count = 0; 1943var malformed_entries_count = 0; 1944for (var img_id in d) { 1945if ( ! _via_img_metadata.hasOwnProperty(img_id) ) { 1946project_add_new_file(d[img_id].filename, d[img_id].size, img_id); 1947if ( _via_settings.core.default_filepath === '' ) { 1948_via_img_src[img_id] = d[img_id].filename; 1949} else { 1950_via_file_resolve_file_to_default_filepath(img_id); 1951} 1952file_added_count += 1; 1953} 1954 1955// copy file attributes 1956for ( var key in d[img_id].file_attributes ) { 1957if ( key === '' ) { 1958continue; 1959} 1960 1961_via_img_metadata[img_id].file_attributes[key] = d[img_id].file_attributes[key]; 1962 1963// add this file attribute to _via_attributes 1964if ( ! _via_attributes['file'].hasOwnProperty(key) ) { 1965_via_attributes['file'][key] = { 'type':'text' }; 1966} 1967} 1968 1969// copy regions 1970var regions = d[img_id].regions; 1971for ( var i in regions ) { 1972var region_i = new file_region(); 1973for ( var sid in regions[i].shape_attributes ) { 1974region_i.shape_attributes[sid] = regions[i].shape_attributes[sid]; 1975} 1976for ( var rid in regions[i].region_attributes ) { 1977if ( rid === '' ) { 1978continue; 1979} 1980 1981region_i.region_attributes[rid] = regions[i].region_attributes[rid]; 1982 1983// add this region attribute to _via_attributes 1984if ( ! _via_attributes['region'].hasOwnProperty(rid) ) { 1985_via_attributes['region'][rid] = { 'type':'text' }; 1986} 1987} 1988 1989// add regions only if they are present 1990if ( Object.keys(region_i.shape_attributes).length > 0 || 1991Object.keys(region_i.region_attributes).length > 0 ) { 1992_via_img_metadata[img_id].regions.push(region_i); 1993region_import_count += 1; 1994} 1995} 1996} 1997show_message('Import Summary : [' + file_added_count + '] new files, ' + 1998'[' + region_import_count + '] regions, ' + 1999'[' + malformed_entries_count + '] malformed entries.'); 2000 2001if ( file_added_count ) { 2002update_img_fn_list(); 2003} 2004 2005if ( _via_current_image_loaded ) { 2006if ( region_import_count ) { 2007update_attributes_update_panel(); 2008annotation_editor_update_content(); 2009_via_load_canvas_regions(); // image to canvas space transform 2010_via_redraw_reg_canvas(); 2011_via_reg_canvas.focus(); 2012} 2013} else { 2014if ( file_added_count ) { 2015_via_show_img(0); 2016} 2017} 2018 2019ok_callback([file_added_count, region_import_count, malformed_entries_count]); 2020}); 2021} 2022 2023// assumes that csv line follows the RFC 4180 standard 2024// see: https://en.wikipedia.org/wiki/Comma-separated_values 2025function parse_csv_line(s, field_separator) { 2026if (typeof(s) === 'undefined' || s.length === 0 ) { 2027return []; 2028} 2029 2030if (typeof(field_separator) === 'undefined') { 2031field_separator = ','; 2032} 2033var double_quote_seen = false; 2034var start = 0; 2035var d = []; 2036 2037var i = 0; 2038while ( i < s.length) { 2039if (s.charAt(i) === field_separator) { 2040if (double_quote_seen) { 2041// field separator inside double quote is ignored 2042i = i + 1; 2043} else { 2044//var part = s.substr(start, i - start); 2045d.push( s.substr(start, i - start) ); 2046start = i + 1; 2047i = i + 1; 2048} 2049} else { 2050if (s.charAt(i) === '"') { 2051if (double_quote_seen) { 2052if (s.charAt(i+1) === '"') { 2053// ignore escaped double quotes 2054i = i + 2; 2055} else { 2056// closing of double quote 2057double_quote_seen = false; 2058i = i + 1; 2059} 2060} else { 2061double_quote_seen = true; 2062start = i; 2063i = i + 1; 2064} 2065} else { 2066i = i + 1; 2067} 2068} 2069 2070} 2071// extract the last field (csv rows have no trailing comma) 2072d.push( s.substr(start) ); 2073return d; 2074} 2075 2076// s = '{"name":"rect","x":188,"y":90,"width":243,"height":233}' 2077function json_str_to_map(s) { 2078if (typeof(s) === 'undefined' || s.length === 0 ) { 2079return {}; 2080} 2081 2082return JSON.parse(s); 2083} 2084 2085// ensure the exported json string conforms to RFC 4180 2086// see: https://en.wikipedia.org/wiki/Comma-separated_values 2087function map_to_json(m) { 2088var s = []; 2089for ( var key in m ) { 2090var v = m[key]; 2091var si = JSON.stringify(key); 2092si += VIA_CSV_KEYVAL_SEP; 2093si += JSON.stringify(v); 2094s.push( si ); 2095} 2096return '{' + s.join(VIA_CSV_SEP) + '}'; 2097} 2098 2099function escape_for_csv(s) { 2100return s.replace(/["]/g, '""'); 2101} 2102 2103function unescape_from_csv(s) { 2104return s.replace(/""/g, '"'); 2105} 2106 2107function remove_prefix_suffix_quotes(s) { 2108if ( s.charAt(0) === '"' && s.charAt(s.length-1) === '"' ) { 2109return s.substr(1, s.length-2); 2110} else { 2111return s; 2112} 2113} 2114 2115function clone_image_region(r0) { 2116var r1 = new file_region(); 2117 2118// copy shape attributes 2119for ( var key in r0.shape_attributes ) { 2120r1.shape_attributes[key] = clone_value(r0.shape_attributes[key]); 2121} 2122 2123// copy region attributes 2124for ( var key in r0.region_attributes ) { 2125r1.region_attributes[key] = clone_value(r0.region_attributes[key]); 2126} 2127return r1; 2128} 2129 2130function clone_value(value) { 2131if ( typeof(value) === 'object' ) { 2132if ( Array.isArray(value) ) { 2133return value.slice(0); 2134} else { 2135var copy = {}; 2136for ( var p in value ) { 2137if ( value.hasOwnProperty(p) ) { 2138copy[p] = clone_value(value[p]); 2139} 2140} 2141return copy; 2142} 2143} 2144return value; 2145} 2146 2147function _via_get_image_id(filename, size) { 2148if ( typeof(size) === 'undefined' ) { 2149return filename; 2150} else { 2151return filename + size; 2152} 2153} 2154 2155function load_text_file(text_file, callback_function) { 2156if (text_file) { 2157var text_reader = new FileReader(); 2158text_reader.addEventListener( 'progress', function(e) { 2159show_message('Loading data from file : ' + text_file.name + ' ... '); 2160}, false); 2161 2162text_reader.addEventListener( 'error', function() { 2163show_message('Error loading data text file : ' + text_file.name + ' !'); 2164callback_function(''); 2165}, false); 2166 2167text_reader.addEventListener( 'load', function() { 2168callback_function(text_reader.result); 2169}, false); 2170text_reader.readAsText(text_file, 'utf-8'); 2171} 2172} 2173 2174function import_files_url_from_csv(data) { 2175return new Promise( function(ok_callback, err_callback) { 2176if ( data === '' || typeof(data) === 'undefined') { 2177err_callback(); 2178} 2179 2180var malformed_url_count = 0; 2181var url_added_count = 0; 2182 2183var line_split_regex = new RegExp('\n|\r|\r\n', 'g'); 2184var csvdata = data.split(line_split_regex); 2185 2186var percent_completed = 0; 2187var n = csvdata.length; 2188var i; 2189var img_id; 2190var first_img_id = ''; 2191for ( i=0; i < n; ++i ) { 2192// ignore blank lines 2193if (csvdata[i].charAt(0) === '\n' || csvdata[i].charAt(0) === '') { 2194malformed_url_count += 1; 2195continue; 2196} else { 2197img_id = project_file_add_url(csvdata[i]); 2198if ( first_img_id === '' ) { 2199first_img_id = img_id; 2200} 2201url_added_count += 1; 2202} 2203} 2204show_message('Added ' + url_added_count + ' files to project'); 2205if ( url_added_count ) { 2206var first_img_index = _via_image_id_list.indexOf(first_img_id); 2207_via_show_img(first_img_index); 2208update_img_fn_list(); 2209} 2210}); 2211} 2212 2213// 2214// Data Exporter 2215// 2216function pack_via_metadata(return_type) { 2217return new Promise( function(ok_callback, err_callback) { 2218if( return_type === 'csv' ) { 2219var csvdata = []; 2220var csvheader = 'filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes'; 2221csvdata.push(csvheader); 2222 2223for ( var image_id in _via_img_metadata ) { 2224var fattr = map_to_json( _via_img_metadata[image_id].file_attributes ); 2225fattr = escape_for_csv( fattr ); 2226 2227var prefix = '\n' + _via_img_metadata[image_id].filename; 2228prefix += ',' + _via_img_metadata[image_id].size; 2229prefix += ',"' + fattr + '"'; 2230 2231var r = _via_img_metadata[image_id].regions; 2232 2233if ( r.length !==0 ) { 2234for ( var i = 0; i < r.length; ++i ) { 2235var csvline = []; 2236csvline.push(prefix); 2237csvline.push(r.length); 2238csvline.push(i); 2239 2240var sattr = map_to_json( r[i].shape_attributes ); 2241sattr = '"' + escape_for_csv( sattr ) + '"'; 2242csvline.push(sattr); 2243 2244var rattr = map_to_json( r[i].region_attributes ); 2245rattr = '"' + escape_for_csv( rattr ) + '"'; 2246csvline.push(rattr); 2247csvdata.push( csvline.join(VIA_CSV_SEP) ); 2248} 2249} else { 2250// @todo: reconsider this practice of adding an empty entry 2251csvdata.push(prefix + ',0,0,"{}","{}"'); 2252} 2253} 2254ok_callback(csvdata); 2255} 2256 2257// see http://cocodataset.org/#format-data 2258if( return_type === 'coco' ) { 2259img_stat_set_all().then( function(ok) { 2260var coco = export_project_to_coco_format(); 2261ok_callback( [ coco ] ); 2262}.bind(this), function(err) { 2263err_callback(err); 2264}.bind(this)); 2265} else { 2266// default format is JSON 2267ok_callback( [ JSON.stringify(_via_img_metadata) ] ); 2268} 2269}.bind(this)); 2270} 2271 2272function export_project_to_coco_format() { 2273var coco = { 'info':{}, 'images':[], 'annotations':[], 'licenses':[], 'categories':[] }; 2274coco['info'] = { 'year': new Date().getFullYear(), 2275'version': '1.0', 2276'description': 'VIA project exported to COCO format using VGG Image Annotator (http://www.robots.ox.ac.uk/~vgg/software/via/)', 2277'contributor': '', 2278'url': 'http://www.robots.ox.ac.uk/~vgg/software/via/', 2279'date_created': new Date().toString(), 2280}; 2281coco['licenses'] = [ {'id':0, 'name':'Unknown License', 'url':''} ]; // indicates that license is unknown 2282 2283var skipped_annotation_count = 0; 2284// We want to ensure that a COCO project imported in VIA and then exported again back to 2285// COCO format using VIA retains the image_id and category_id present in the original COCO project. 2286// A VIA project that has been created by importing annotations from a COCO project contains 2287// unique image_id of type integer and contains all unique option id. If we detect this, we reuse 2288// the existing image_id and category_id, otherwise we assign a new unique id sequentially. 2289// Currently, it is not possible to preserve the annotation_id 2290var assign_unique_id = false; 2291for(var img_id in _via_img_metadata) { 2292if(Number.isNaN(parseInt(img_id))) { 2293assign_unique_id = true; // since COCO only supports image_id of type integer, we cannot reuse the VIA's image-id 2294break; 2295} 2296} 2297if(assign_unique_id) { 2298// check if all the options have unique id 2299var attribute_option_id_list = []; 2300for(var attr_name in _via_attributes) { 2301if( !VIA_COCO_EXPORT_ATTRIBUTE_TYPE.includes(_via_attributes[attr_name]['type']) ) { 2302continue; // skip this attribute as it will not be included in COCO export 2303} 2304 2305for(var attr_option_id in _via_attributes[attr_name]['options']) { 2306if(attribute_option_id_list.includes(attr_option_id) || 2307Number.isNaN(parseInt(attr_option_id)) ) { 2308assign_unique_id = true; 2309break; 2310} else { 2311attribute_option_id_list.push(assign_unique_id); 2312} 2313} 2314} 2315} 2316 2317// add categories 2318var attr_option_id_to_category_id = {}; 2319var unique_category_id = 1; 2320for(var attr_name in _via_attributes['region']) { 2321if( VIA_COCO_EXPORT_ATTRIBUTE_TYPE.includes(_via_attributes['region'][attr_name]['type']) ) { 2322for(var attr_option_id in _via_attributes['region'][attr_name]['options']) { 2323var category_id; 2324if(assign_unique_id) { 2325category_id = unique_category_id; 2326unique_category_id = unique_category_id + 1; 2327} else { 2328category_id = parseInt(attr_option_id); 2329} 2330coco['categories'].push({ 2331'supercategory':attr_name, 2332'id':category_id, 2333'name':_via_attributes['region'][attr_name]['options'][attr_option_id] 2334}); 2335attr_option_id_to_category_id[attr_option_id] = category_id; 2336} 2337} 2338} 2339 2340// add files and all their associated annotations 2341var annotation_id = 1; 2342var unique_img_id = 1; 2343for( var img_index in _via_image_id_list ) { 2344var img_id = _via_image_id_list[img_index]; 2345var file_src = _via_settings['core']['default_filepath'] + _via_img_metadata[img_id].filename; 2346if ( _via_img_fileref[img_id] instanceof File ) { 2347file_src = _via_img_fileref[img_id].filename; 2348} 2349 2350var coco_img_id; 2351if(assign_unique_id) { 2352coco_img_id = unique_img_id; 2353unique_img_id = unique_img_id + 1; 2354} else { 2355coco_img_id = parseInt(img_id); 2356} 2357 2358coco['images'].push( { 2359'id':coco_img_id, 2360'width':_via_img_stat[img_index][0], 2361'height':_via_img_stat[img_index][1], 2362'file_name':_via_img_metadata[img_id].filename, 2363'license':0, 2364'flickr_url':file_src, 2365'coco_url':file_src, 2366'date_captured':'', 2367} ); 2368 2369// add all annotations associated with this file 2370for( var rindex in _via_img_metadata[img_id].regions ) { 2371var region = _via_img_metadata[img_id].regions[rindex]; 2372if( !VIA_COCO_EXPORT_RSHAPE.includes(region.shape_attributes['name']) ) { 2373skipped_annotation_count = skipped_annotation_count + 1; 2374continue; // skip this region as COCO does not allow it 2375} 2376 2377var coco_annotation = via_region_shape_to_coco_annotation(region.shape_attributes); 2378coco_annotation['id'] = annotation_id; 2379coco_annotation['image_id'] = coco_img_id; 2380 2381var region_aid_list = Object.keys(region['region_attributes']); 2382for(var region_attribute_id in region['region_attributes']) { 2383var region_attribute_value = region['region_attributes'][region_attribute_id]; 2384if(attr_option_id_to_category_id.hasOwnProperty(region_attribute_value)) { 2385coco_annotation['category_id'] = attr_option_id_to_category_id[region_attribute_value]; 2386coco['annotations'].push(coco_annotation); 2387annotation_id = annotation_id + 1; 2388} else { 2389skipped_annotation_count = skipped_annotation_count + 1; 2390continue; // skip attribute value not supported by COCO format 2391} 2392} 2393} 2394} 2395 2396show_message('Skipped ' + skipped_annotation_count + ' annotations. COCO format only supports the following attribute types: ' + JSON.stringify(VIA_COCO_EXPORT_ATTRIBUTE_TYPE) + ' and region shapes: ' + JSON.stringify(VIA_COCO_EXPORT_RSHAPE)); 2397return [ JSON.stringify(coco) ]; 2398} 2399 2400function via_region_shape_to_coco_annotation(shape_attributes) { 2401var annotation = { 'segmentation':[[]], 'area':[], 'bbox':[], 'iscrowd':0 }; 2402 2403switch(shape_attributes['name']) { 2404case 'rect': 2405var x0 = shape_attributes['x']; 2406var y0 = shape_attributes['y']; 2407var w = parseInt(shape_attributes['width']); 2408var h = parseInt(shape_attributes['height']); 2409var x1 = x0 + w; 2410var y1 = y0 + h; 2411annotation['segmentation'][0] = [x0, y0, x1, y0, x1, y1, x0, y1]; 2412annotation['area'] = w * h ; 2413 2414annotation['bbox'] = [x0, y0, w, h]; 2415break; 2416 2417case 'point': 2418var cx = shape_attributes['cx']; 2419var cy = shape_attributes['cy']; 2420// 2 is for visibility - currently set to always inside segmentation. 2421// see Keypoint Detection: http://cocodataset.org/#format-data 2422annotation['keypoints'] = [cx, cy, 2]; 2423annotation['num_keypoints'] = 1; 2424break; 2425 2426case 'circle': 2427var a,b; 2428a = shape_attributes['r']; 2429b = shape_attributes['r']; 2430var theta_to_radian = Math.PI/180; 2431 2432for ( var theta = 0; theta < 360; theta = theta + VIA_POLYGON_SEGMENT_SUBTENDED_ANGLE ) { 2433var theta_radian = theta * theta_to_radian; 2434var x = shape_attributes['cx'] + a * Math.cos(theta_radian); 2435var y = shape_attributes['cy'] + b * Math.sin(theta_radian); 2436annotation['segmentation'][0].push( fixfloat(x), fixfloat(y) ); 2437} 2438annotation['bbox'] = polygon_to_bbox(annotation['segmentation'][0]); 2439annotation['area'] = annotation['bbox'][2] * annotation['bbox'][3]; 2440break; 2441 2442case 'ellipse': 2443var a,b; 2444a = shape_attributes['rx']; 2445b = shape_attributes['ry']; 2446var rotation = 0; 2447// older version of VIA2 did not support rotated ellipse and hence 'theta' attribute may not be available 2448if( shape_attributes.hasOwnProperty('theta') ) { 2449rotation = shape_attributes['theta']; 2450} 2451 2452var theta_to_radian = Math.PI/180; 2453 2454for ( var theta = 0; theta < 360; theta = theta + VIA_POLYGON_SEGMENT_SUBTENDED_ANGLE ) { 2455var theta_radian = theta * theta_to_radian; 2456var x = shape_attributes['cx'] + 2457( a * Math.cos(theta_radian) * Math.cos(rotation) ) - 2458( b * Math.sin(theta_radian) * Math.sin(rotation) ); 2459var y = shape_attributes['cy'] + 2460( a * Math.cos(theta_radian) * Math.sin(rotation) ) + 2461( b * Math.sin(theta_radian) * Math.cos(rotation) ); 2462annotation['segmentation'][0].push( fixfloat(x), fixfloat(y) ); 2463} 2464annotation['bbox'] = polygon_to_bbox(annotation['segmentation'][0]); 2465annotation['area'] = annotation['bbox'][2] * annotation['bbox'][3]; 2466break; 2467 2468case 'polygon': 2469annotation['segmentation'][0] = []; 2470var x0 = +Infinity; 2471var y0 = +Infinity; 2472var x1 = -Infinity; 2473var y1 = -Infinity; 2474for ( var i in shape_attributes['all_points_x'] ) { 2475annotation['segmentation'][0].push( shape_attributes['all_points_x'][i] ); 2476annotation['segmentation'][0].push( shape_attributes['all_points_y'][i] ); 2477if ( shape_attributes['all_points_x'][i] < x0 ) { 2478x0 = shape_attributes['all_points_x'][i]; 2479} 2480if ( shape_attributes['all_points_y'][i] < y0 ) { 2481y0 = shape_attributes['all_points_y'][i]; 2482} 2483if ( shape_attributes['all_points_x'][i] > x1 ) { 2484x1 = shape_attributes['all_points_x'][i]; 2485} 2486if ( shape_attributes['all_points_y'][i] > y1 ) { 2487y1 = shape_attributes['all_points_y'][i]; 2488} 2489} 2490var w = x1 - x0; 2491var h = y1 - y0; 2492annotation['bbox'] = [x0, y0, w, h]; 2493annotation['area'] = w * h; // approximate area 2494} 2495return annotation; 2496} 2497 2498function save_data_to_local_file(data, filename) { 2499var a = document.createElement('a'); 2500a.href = URL.createObjectURL(data); 2501a.download = filename; 2502 2503// simulate a mouse click event 2504var event = new MouseEvent('click', { 2505view: window, 2506bubbles: true, 2507cancelable: true 2508}); 2509a.dispatchEvent(event); 2510 2511// @todo: replace a.dispatchEvent() with a.click() 2512// a.click() based trigger is supported in Chrome 70 and Safari 11/12 but **not** in Firefox 63 2513//a.click(); 2514} 2515 2516// 2517// Maintainers of user interface 2518// 2519 2520function init_message_panel() { 2521var p = document.getElementById('message_panel'); 2522p.addEventListener('mousedown', function() { 2523this.style.display = 'none'; 2524}, false); 2525p.addEventListener('mouseover', function() { 2526clearTimeout(_via_message_clear_timer); // stop any previous timeouts 2527}, false); 2528} 2529 2530function toggle_message_visibility() { 2531if(_via_is_message_visible) { 2532show_message('Disabled status messages'); 2533_via_is_message_visible = false; 2534} else { 2535_via_is_message_visible = true; 2536show_message('Status messages are now visible'); 2537} 2538} 2539 2540function show_message(msg, t) { 2541if ( _via_message_clear_timer ) { 2542clearTimeout(_via_message_clear_timer); // stop any previous timeouts 2543} 2544if ( !_via_is_message_visible ) { 2545return; 2546} 2547 2548var timeout = t; 2549if ( typeof t === 'undefined' ) { 2550timeout = VIA_THEME_MESSAGE_TIMEOUT_MS; 2551} 2552document.getElementById('message_panel_content').innerHTML = msg; 2553document.getElementById('message_panel').style.display = 'block'; 2554 2555_via_message_clear_timer = setTimeout( function() { 2556document.getElementById('message_panel').style.display = 'none'; 2557}, timeout); 2558} 2559 2560function _via_regions_group_color_init() { 2561_via_canvas_regions_group_color = {}; 2562var aid = _via_settings.ui.image.region_color; 2563if ( aid !== '__via_default_region_color__' ) { 2564var avalue; 2565for ( var i = 0; i < _via_img_metadata[_via_image_id].regions.length; ++i ) { 2566avalue = _via_img_metadata[_via_image_id].regions[i].region_attributes[aid]; 2567_via_canvas_regions_group_color[avalue] = 1; 2568} 2569var color_index = 0; 2570for ( avalue in _via_canvas_regions_group_color ) { 2571_via_canvas_regions_group_color[avalue] = VIA_REGION_COLOR_LIST[ color_index % VIA_REGION_COLOR_LIST.length ]; 2572color_index = color_index + 1; 2573} 2574} 2575} 2576 2577// transform regions in image space to canvas space 2578function _via_load_canvas_regions() { 2579_via_regions_group_color_init(); 2580 2581// load all existing annotations into _via_canvas_regions 2582var regions = _via_img_metadata[_via_image_id].regions; 2583_via_canvas_regions = []; 2584for ( var i = 0; i < regions.length; ++i ) { 2585var region_i = new file_region(); 2586for ( var key in regions[i].shape_attributes ) { 2587region_i.shape_attributes[key] = regions[i].shape_attributes[key]; 2588} 2589_via_canvas_regions.push(region_i); 2590 2591switch(_via_canvas_regions[i].shape_attributes['name']) { 2592case VIA_REGION_SHAPE.RECT: 2593var x = regions[i].shape_attributes['x'] / _via_canvas_scale; 2594var y = regions[i].shape_attributes['y'] / _via_canvas_scale; 2595var width = regions[i].shape_attributes['width'] / _via_canvas_scale; 2596var height = regions[i].shape_attributes['height'] / _via_canvas_scale; 2597 2598_via_canvas_regions[i].shape_attributes['x'] = Math.round(x); 2599_via_canvas_regions[i].shape_attributes['y'] = Math.round(y); 2600_via_canvas_regions[i].shape_attributes['width'] = Math.round(width); 2601_via_canvas_regions[i].shape_attributes['height'] = Math.round(height); 2602break; 2603 2604case VIA_REGION_SHAPE.CIRCLE: 2605var cx = regions[i].shape_attributes['cx'] / _via_canvas_scale; 2606var cy = regions[i].shape_attributes['cy'] / _via_canvas_scale; 2607var r = regions[i].shape_attributes['r'] / _via_canvas_scale; 2608_via_canvas_regions[i].shape_attributes['cx'] = Math.round(cx); 2609_via_canvas_regions[i].shape_attributes['cy'] = Math.round(cy); 2610_via_canvas_regions[i].shape_attributes['r'] = Math.round(r); 2611break; 2612 2613case VIA_REGION_SHAPE.ELLIPSE: 2614var cx = regions[i].shape_attributes['cx'] / _via_canvas_scale; 2615var cy = regions[i].shape_attributes['cy'] / _via_canvas_scale; 2616var rx = regions[i].shape_attributes['rx'] / _via_canvas_scale; 2617var ry = regions[i].shape_attributes['ry'] / _via_canvas_scale; 2618// rotation in radians 2619var theta = regions[i].shape_attributes['theta']; 2620_via_canvas_regions[i].shape_attributes['cx'] = Math.round(cx); 2621_via_canvas_regions[i].shape_attributes['cy'] = Math.round(cy); 2622_via_canvas_regions[i].shape_attributes['rx'] = Math.round(rx); 2623_via_canvas_regions[i].shape_attributes['ry'] = Math.round(ry); 2624_via_canvas_regions[i].shape_attributes['theta'] = theta; 2625break; 2626 2627case VIA_REGION_SHAPE.POLYLINE: // handled by polygon 2628case VIA_REGION_SHAPE.POLYGON: 2629var all_points_x = regions[i].shape_attributes['all_points_x'].slice(0); 2630var all_points_y = regions[i].shape_attributes['all_points_y'].slice(0); 2631for (var j=0; j<all_points_x.length; ++j) { 2632all_points_x[j] = Math.round(all_points_x[j] / _via_canvas_scale); 2633all_points_y[j] = Math.round(all_points_y[j] / _via_canvas_scale); 2634} 2635_via_canvas_regions[i].shape_attributes['all_points_x'] = all_points_x; 2636_via_canvas_regions[i].shape_attributes['all_points_y'] = all_points_y; 2637break; 2638 2639case VIA_REGION_SHAPE.POINT: 2640var cx = regions[i].shape_attributes['cx'] / _via_canvas_scale; 2641var cy = regions[i].shape_attributes['cy'] / _via_canvas_scale; 2642 2643_via_canvas_regions[i].shape_attributes['cx'] = Math.round(cx); 2644_via_canvas_regions[i].shape_attributes['cy'] = Math.round(cy); 2645break; 2646} 2647} 2648} 2649 2650// updates currently selected region shape 2651function select_region_shape(sel_shape_name) { 2652for ( var shape_name in VIA_REGION_SHAPE ) { 2653var ui_element = document.getElementById('region_shape_' + VIA_REGION_SHAPE[shape_name]); 2654ui_element.classList.remove('selected'); 2655} 2656 2657_via_current_shape = sel_shape_name; 2658var ui_element = document.getElementById('region_shape_' + _via_current_shape); 2659ui_element.classList.add('selected'); 2660 2661switch(_via_current_shape) { 2662case VIA_REGION_SHAPE.RECT: // Fall-through 2663case VIA_REGION_SHAPE.CIRCLE: // Fall-through 2664case VIA_REGION_SHAPE.ELLIPSE: 2665show_message('Press single click and drag mouse to draw ' + 2666_via_current_shape + ' region'); 2667break; 2668 2669case VIA_REGION_SHAPE.POLYLINE: 2670case VIA_REGION_SHAPE.POLYGON: 2671_via_is_user_drawing_polygon = false; 2672_via_current_polygon_region_id = -1; 2673 2674show_message('[Single Click] to define polygon/polyline vertices, ' + 2675'[Backspace] to delete last vertex, [Enter] to finish, [Esc] to cancel drawing.' ); 2676break; 2677 2678case VIA_REGION_SHAPE.POINT: 2679show_message('Press single click to define points (or landmarks)'); 2680break; 2681 2682default: 2683show_message('Unknown shape selected!'); 2684break; 2685} 2686} 2687 2688function set_all_canvas_size(w, h) { 2689_via_reg_canvas.height = h; 2690_via_reg_canvas.width = w; 2691 2692image_panel.style.height = h + 'px'; 2693image_panel.style.width = w + 'px'; 2694} 2695 2696function set_all_canvas_scale(s) { 2697_via_reg_ctx.scale(s, s); 2698} 2699 2700function show_all_canvas() { 2701image_panel.style.display = 'inline-block'; 2702} 2703 2704function hide_all_canvas() { 2705image_panel.style.display = 'none'; 2706} 2707 2708function jump_to_image(image_index) { 2709if ( _via_img_count <= 0 ) { 2710return; 2711} 2712 2713switch(_via_display_area_content_name) { 2714case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID: 2715if ( image_index >= 0 && image_index < _via_img_count) { 2716// @todo: jump to image grid page view with the given first image index 2717show_single_image_view(); 2718_via_show_img(image_index); 2719} 2720break; 2721default: 2722if ( image_index >= 0 && image_index < _via_img_count) { 2723_via_show_img(image_index); 2724} 2725break; 2726} 2727} 2728 2729function count_missing_region_attr(img_id) { 2730var miss_region_attr_count = 0; 2731var attr_count = Object.keys(_via_region_attributes).length; 2732for( var i=0; i < _via_img_metadata[img_id].regions.length; ++i ) { 2733var set_attr_count = Object.keys(_via_img_metadata[img_id].regions[i].region_attributes).length; 2734miss_region_attr_count += ( attr_count - set_attr_count ); 2735} 2736return miss_region_attr_count; 2737} 2738 2739function count_missing_file_attr(img_id) { 2740return Object.keys(_via_file_attributes).length - Object.keys(_via_img_metadata[img_id].file_attributes).length; 2741} 2742 2743function toggle_all_regions_selection(is_selected) { 2744var n = _via_img_metadata[_via_image_id].regions.length; 2745var i; 2746_via_region_selected_flag = []; 2747for ( i = 0; i < n; ++i) { 2748_via_region_selected_flag[i] = is_selected; 2749} 2750_via_is_all_region_selected = is_selected; 2751annotation_editor_hide(); 2752if ( _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS ) { 2753annotation_editor_clear_row_highlight(); 2754} 2755} 2756 2757function select_only_region(region_id) { 2758toggle_all_regions_selection(false); 2759set_region_select_state(region_id, true); 2760_via_is_region_selected = true; 2761_via_is_all_region_selected = false; 2762_via_user_sel_region_id = region_id; 2763} 2764 2765function set_region_select_state(region_id, is_selected) { 2766_via_region_selected_flag[region_id] = is_selected; 2767} 2768 2769function show_annotation_data() { 2770pack_via_metadata('csv').then( function(data) { 2771var hstr = '<pre>' + data.join('') + '</pre>'; 2772var window_features = 'toolbar=no,menubar=no,location=no,resizable=yes,scrollbars=yes,status=no'; 2773window_features += ',width=800,height=600'; 2774var annotation_data_window = window.open('', 'Annotations (preview) ', window_features); 2775annotation_data_window.document.body.innerHTML = hstr; 2776}.bind(this), function(err) { 2777show_message('Failed to collect annotation data!'); 2778}.bind(this)); 2779} 2780 2781// 2782// Image click handlers 2783// 2784 2785// enter annotation mode on double click 2786function _via_reg_canvas_dblclick_handler(e) { 2787e.stopPropagation(); 2788// @todo: use double click in future 2789} 2790 2791// user clicks on the canvas 2792function _via_reg_canvas_mousedown_handler(e) { 2793e.stopPropagation(); 2794_via_click_x0 = e.offsetX; _via_click_y0 = e.offsetY; 2795_via_region_edge = is_on_region_corner(_via_click_x0, _via_click_y0); 2796var region_id = is_inside_region(_via_click_x0, _via_click_y0); 2797 2798if ( _via_is_region_selected ) { 2799// check if user clicked on the region boundary 2800if ( _via_region_edge[1] > 0 ) { 2801if ( !_via_is_user_resizing_region ) { 2802if ( _via_region_edge[0] !== _via_user_sel_region_id ) { 2803_via_user_sel_region_id = _via_region_edge[0]; 2804} 2805// resize region 2806_via_is_user_resizing_region = true; 2807} 2808} else { 2809var yes = is_inside_this_region(_via_click_x0, 2810_via_click_y0, 2811_via_user_sel_region_id); 2812if (yes) { 2813if( !_via_is_user_moving_region ) { 2814_via_is_user_moving_region = true; 2815_via_region_click_x = _via_click_x0; 2816_via_region_click_y = _via_click_y0; 2817} 2818} 2819if ( region_id === -1 ) { 2820// mousedown on outside any region 2821_via_is_user_drawing_region = true; 2822// unselect all regions 2823_via_is_region_selected = false; 2824_via_user_sel_region_id = -1; 2825toggle_all_regions_selection(false); 2826} 2827} 2828} else { 2829if ( region_id === -1 ) { 2830// mousedown outside a region 2831if (_via_current_shape !== VIA_REGION_SHAPE.POLYGON && 2832_via_current_shape !== VIA_REGION_SHAPE.POLYLINE && 2833_via_current_shape !== VIA_REGION_SHAPE.POINT) { 2834// this is a bounding box drawing event 2835_via_is_user_drawing_region = true; 2836} 2837} else { 2838// mousedown inside a region 2839// this could lead to (1) region selection or (2) region drawing 2840_via_is_user_drawing_region = true; 2841} 2842} 2843} 2844 2845// implements the following functionalities: 2846// - new region drawing (including polygon) 2847// - moving/resizing/select/unselect existing region 2848function _via_reg_canvas_mouseup_handler(e) { 2849e.stopPropagation(); 2850_via_click_x1 = e.offsetX; _via_click_y1 = e.offsetY; 2851 2852var click_dx = Math.abs(_via_click_x1 - _via_click_x0); 2853var click_dy = Math.abs(_via_click_y1 - _via_click_y0); 2854 2855// indicates that user has finished moving a region 2856if ( _via_is_user_moving_region ) { 2857_via_is_user_moving_region = false; 2858_via_reg_canvas.style.cursor = "default"; 2859 2860var move_x = Math.round(_via_click_x1 - _via_region_click_x); 2861var move_y = Math.round(_via_click_y1 - _via_region_click_y); 2862 2863if (Math.abs(move_x) > VIA_MOUSE_CLICK_TOL || 2864Math.abs(move_y) > VIA_MOUSE_CLICK_TOL) { 2865// move all selected regions 2866_via_move_selected_regions(move_x, move_y); 2867} else { 2868// indicates a user click on an already selected region 2869// this could indicate the user's intention to select another 2870// nested region within this region 2871// OR 2872// draw a nested region (i.e. region inside a region) 2873 2874// traverse the canvas regions in alternating ascending 2875// and descending order to solve the issue of nested regions 2876var nested_region_id = is_inside_region(_via_click_x0, _via_click_y0, true); 2877if (nested_region_id >= 0 && 2878nested_region_id !== _via_user_sel_region_id) { 2879_via_user_sel_region_id = nested_region_id; 2880_via_is_region_selected = true; 2881_via_is_user_moving_region = false; 2882 2883// de-select all other regions if the user has not pressed Shift 2884if ( !e.shiftKey ) { 2885toggle_all_regions_selection(false); 2886} 2887set_region_select_state(nested_region_id, true); 2888annotation_editor_show(); 2889} else { 2890// user clicking inside an already selected region 2891// indicates that the user intends to draw a nested region 2892toggle_all_regions_selection(false); 2893_via_is_region_selected = false; 2894 2895switch (_via_current_shape) { 2896case VIA_REGION_SHAPE.POLYLINE: // handled by case for POLYGON 2897case VIA_REGION_SHAPE.POLYGON: 2898// user has clicked on the first point in a new polygon 2899// see also event 'mouseup' for _via_is_user_drawing_polygon=true 2900_via_is_user_drawing_polygon = true; 2901 2902var canvas_polygon_region = new file_region(); 2903canvas_polygon_region.shape_attributes['name'] = _via_current_shape; 2904canvas_polygon_region.shape_attributes['all_points_x'] = [Math.round(_via_click_x0)]; 2905canvas_polygon_region.shape_attributes['all_points_y'] = [Math.round(_via_click_y0)]; 2906var new_length = _via_canvas_regions.push(canvas_polygon_region); 2907_via_current_polygon_region_id = new_length - 1; 2908break; 2909 2910case VIA_REGION_SHAPE.POINT: 2911// user has marked a landmark point 2912var point_region = new file_region(); 2913point_region.shape_attributes['name'] = VIA_REGION_SHAPE.POINT; 2914point_region.shape_attributes['cx'] = Math.round(_via_click_x0 * _via_canvas_scale); 2915point_region.shape_attributes['cy'] = Math.round(_via_click_y0 * _via_canvas_scale); 2916_via_img_metadata[_via_image_id].regions.push(point_region); 2917 2918var canvas_point_region = new file_region(); 2919canvas_point_region.shape_attributes['name'] = VIA_REGION_SHAPE.POINT; 2920canvas_point_region.shape_attributes['cx'] = Math.round(_via_click_x0); 2921canvas_point_region.shape_attributes['cy'] = Math.round(_via_click_y0); 2922_via_canvas_regions.push(canvas_point_region); 2923break; 2924} 2925annotation_editor_update_content(); 2926} 2927} 2928_via_redraw_reg_canvas(); 2929_via_reg_canvas.focus(); 2930return; 2931} 2932 2933// indicates that user has finished resizing a region 2934if ( _via_is_user_resizing_region ) { 2935// _via_click(x0,y0) to _via_click(x1,y1) 2936_via_is_user_resizing_region = false; 2937_via_reg_canvas.style.cursor = "default"; 2938 2939// update the region 2940var region_id = _via_region_edge[0]; 2941var image_attr = _via_img_metadata[_via_image_id].regions[region_id].shape_attributes; 2942var canvas_attr = _via_canvas_regions[region_id].shape_attributes; 2943 2944switch (canvas_attr['name']) { 2945case VIA_REGION_SHAPE.RECT: 2946var d = [canvas_attr['x'], canvas_attr['y'], 0, 0]; 2947d[2] = d[0] + canvas_attr['width']; 2948d[3] = d[1] + canvas_attr['height']; 2949 2950var mx = _via_current_x; 2951var my = _via_current_y; 2952var preserve_aspect_ratio = false; 2953 2954// constrain (mx,my) to lie on a line connecting a diagonal of rectangle 2955if ( _via_is_ctrl_pressed ) { 2956preserve_aspect_ratio = true; 2957} 2958 2959rect_update_corner(_via_region_edge[1], d, mx, my, preserve_aspect_ratio); 2960rect_standardize_coordinates(d); 2961 2962var w = Math.abs(d[2] - d[0]); 2963var h = Math.abs(d[3] - d[1]); 2964 2965image_attr['x'] = Math.round(d[0] * _via_canvas_scale); 2966image_attr['y'] = Math.round(d[1] * _via_canvas_scale); 2967image_attr['width'] = Math.round(w * _via_canvas_scale); 2968image_attr['height'] = Math.round(h * _via_canvas_scale); 2969 2970canvas_attr['x'] = Math.round( image_attr['x'] / _via_canvas_scale); 2971canvas_attr['y'] = Math.round( image_attr['y'] / _via_canvas_scale); 2972canvas_attr['width'] = Math.round( image_attr['width'] / _via_canvas_scale); 2973canvas_attr['height'] = Math.round( image_attr['height'] / _via_canvas_scale); 2974break; 2975 2976case VIA_REGION_SHAPE.CIRCLE: 2977var dx = Math.abs(canvas_attr['cx'] - _via_current_x); 2978var dy = Math.abs(canvas_attr['cy'] - _via_current_y); 2979var new_r = Math.sqrt( dx*dx + dy*dy ); 2980 2981image_attr['r'] = fixfloat(new_r * _via_canvas_scale); 2982canvas_attr['r'] = Math.round( image_attr['r'] / _via_canvas_scale); 2983break; 2984 2985case VIA_REGION_SHAPE.ELLIPSE: 2986var new_rx = canvas_attr['rx']; 2987var new_ry = canvas_attr['ry']; 2988var new_theta = canvas_attr['theta']; 2989var dx = Math.abs(canvas_attr['cx'] - _via_current_x); 2990var dy = Math.abs(canvas_attr['cy'] - _via_current_y); 2991 2992switch(_via_region_edge[1]) { 2993case 5: 2994new_ry = Math.sqrt(dx*dx + dy*dy); 2995new_theta = Math.atan2(- (_via_current_x - canvas_attr['cx']), (_via_current_y - canvas_attr['cy'])); 2996break; 2997 2998case 6: 2999new_rx = Math.sqrt(dx*dx + dy*dy); 3000new_theta = Math.atan2((_via_current_y - canvas_attr['cy']), (_via_current_x - canvas_attr['cx'])); 3001break; 3002 3003default: 3004new_rx = dx; 3005new_ry = dy; 3006new_theta = 0; 3007break; 3008} 3009 3010image_attr['rx'] = fixfloat(new_rx * _via_canvas_scale); 3011image_attr['ry'] = fixfloat(new_ry * _via_canvas_scale); 3012image_attr['theta'] = fixfloat(new_theta); 3013 3014canvas_attr['rx'] = Math.round(image_attr['rx'] / _via_canvas_scale); 3015canvas_attr['ry'] = Math.round(image_attr['ry'] / _via_canvas_scale); 3016canvas_attr['theta'] = fixfloat(new_theta); 3017break; 3018 3019case VIA_REGION_SHAPE.POLYLINE: // handled by polygon 3020case VIA_REGION_SHAPE.POLYGON: 3021var moved_vertex_id = _via_region_edge[1] - VIA_POLYGON_RESIZE_VERTEX_OFFSET; 3022 3023if ( e.ctrlKey || e.metaKey ) { 3024// if on vertex, delete it 3025// if on edge, add a new vertex 3026var r = _via_canvas_regions[_via_user_sel_region_id].shape_attributes; 3027var shape = r.name; 3028var is_on_vertex = is_on_polygon_vertex(r['all_points_x'], r['all_points_y'], _via_current_x, _via_current_y); 3029 3030if ( is_on_vertex === _via_region_edge[1] ) { 3031// click on vertex, hence delete vertex 3032if ( _via_polygon_del_vertex(region_id, moved_vertex_id) ) { 3033show_message('Deleted vertex ' + moved_vertex_id + ' from region'); 3034} 3035} else { 3036var is_on_edge = is_on_polygon_edge(r['all_points_x'], r['all_points_y'], _via_current_x, _via_current_y); 3037if ( is_on_edge === _via_region_edge[1] ) { 3038// click on edge, hence add new vertex 3039var vertex_index = is_on_edge - VIA_POLYGON_RESIZE_VERTEX_OFFSET; 3040var canvas_x0 = Math.round(_via_click_x1); 3041var canvas_y0 = Math.round(_via_click_y1); 3042var img_x0 = Math.round( canvas_x0 * _via_canvas_scale ); 3043var img_y0 = Math.round( canvas_y0 * _via_canvas_scale ); 3044canvas_x0 = Math.round( img_x0 / _via_canvas_scale ); 3045canvas_y0 = Math.round( img_y0 / _via_canvas_scale ); 3046 3047_via_canvas_regions[region_id].shape_attributes['all_points_x'].splice(vertex_index+1, 0, canvas_x0); 3048_via_canvas_regions[region_id].shape_attributes['all_points_y'].splice(vertex_index+1, 0, canvas_y0); 3049_via_img_metadata[_via_image_id].regions[region_id].shape_attributes['all_points_x'].splice(vertex_index+1, 0, img_x0); 3050_via_img_metadata[_via_image_id].regions[region_id].shape_attributes['all_points_y'].splice(vertex_index+1, 0, img_y0); 3051 3052show_message('Added 1 new vertex to ' + shape + ' region'); 3053} 3054} 3055} else { 3056// update coordinate of vertex 3057var imx = Math.round(_via_current_x * _via_canvas_scale); 3058var imy = Math.round(_via_current_y * _via_canvas_scale); 3059image_attr['all_points_x'][moved_vertex_id] = imx; 3060image_attr['all_points_y'][moved_vertex_id] = imy; 3061canvas_attr['all_points_x'][moved_vertex_id] = Math.round( imx / _via_canvas_scale ); 3062canvas_attr['all_points_y'][moved_vertex_id] = Math.round( imy / _via_canvas_scale ); 3063} 3064break; 3065} // end of switch() 3066_via_redraw_reg_canvas(); 3067_via_reg_canvas.focus(); 3068return; 3069} 3070 3071// denotes a single click (= mouse down + mouse up) 3072if ( click_dx < VIA_MOUSE_CLICK_TOL || 3073click_dy < VIA_MOUSE_CLICK_TOL ) { 3074// if user is already drawing polygon, then each click adds a new point 3075if ( _via_is_user_drawing_polygon ) { 3076var canvas_x0 = Math.round(_via_click_x1); 3077var canvas_y0 = Math.round(_via_click_y1); 3078var n = _via_canvas_regions[_via_current_polygon_region_id].shape_attributes['all_points_x'].length; 3079var last_x0 = _via_canvas_regions[_via_current_polygon_region_id].shape_attributes['all_points_x'][n-1]; 3080var last_y0 = _via_canvas_regions[_via_current_polygon_region_id].shape_attributes['all_points_y'][n-1]; 3081// discard if the click was on the last vertex 3082if ( canvas_x0 !== last_x0 || canvas_y0 !== last_y0 ) { 3083// user clicked on a new polygon point 3084_via_canvas_regions[_via_current_polygon_region_id].shape_attributes['all_points_x'].push(canvas_x0); 3085_via_canvas_regions[_via_current_polygon_region_id].shape_attributes['all_points_y'].push(canvas_y0); 3086} 3087} else { 3088var region_id = is_inside_region(_via_click_x0, _via_click_y0); 3089if ( region_id >= 0 ) { 3090// first click selects region 3091_via_user_sel_region_id = region_id; 3092_via_is_region_selected = true; 3093_via_is_user_moving_region = false; 3094_via_is_user_drawing_region = false; 3095 3096// de-select all other regions if the user has not pressed Shift 3097if ( !e.shiftKey ) { 3098annotation_editor_clear_row_highlight(); 3099toggle_all_regions_selection(false); 3100} 3101set_region_select_state(region_id, true); 3102 3103// show annotation editor only when a single region is selected 3104if ( !e.shiftKey ) { 3105annotation_editor_show(); 3106} else { 3107annotation_editor_hide(); 3108} 3109 3110// show the region info 3111if (_via_is_region_info_visible) { 3112var canvas_attr = _via_canvas_regions[region_id].shape_attributes; 3113 3114switch (canvas_attr['name']) { 3115case VIA_REGION_SHAPE.RECT: 3116break; 3117 3118case VIA_REGION_SHAPE.CIRCLE: 3119var rf = document.getElementById('region_info'); 3120var attr = _via_canvas_regions[_via_user_sel_region_id].shape_attributes; 3121rf.innerHTML += ',' + ' Radius:' + attr['r']; 3122break; 3123 3124case VIA_REGION_SHAPE.ELLIPSE: 3125var rf = document.getElementById('region_info'); 3126var attr = _via_canvas_regions[_via_user_sel_region_id].shape_attributes; 3127rf.innerHTML += ',' + ' X-radius:' + attr['rx'] + ',' + ' Y-radius:' + attr['ry']; 3128break; 3129 3130case VIA_REGION_SHAPE.POLYLINE: 3131case VIA_REGION_SHAPE.POLYGON: 3132break; 3133} 3134} 3135 3136show_message('Region selected. If you intended to draw a region, click again inside the selected region to start drawing a region.') 3137} else { 3138if ( _via_is_user_drawing_region ) { 3139// clear all region selection 3140_via_is_user_drawing_region = false; 3141_via_is_region_selected = false; 3142toggle_all_regions_selection(false); 3143annotation_editor_hide(); 3144} else { 3145switch (_via_current_shape) { 3146case VIA_REGION_SHAPE.POLYLINE: // handled by case for POLYGON 3147case VIA_REGION_SHAPE.POLYGON: 3148// user has clicked on the first point in a new polygon 3149// see also event 'mouseup' for _via_is_user_moving_region=true 3150_via_is_user_drawing_polygon = true; 3151 3152var canvas_polygon_region = new file_region(); 3153canvas_polygon_region.shape_attributes['name'] = _via_current_shape; 3154canvas_polygon_region.shape_attributes['all_points_x'] = [ Math.round(_via_click_x0) ]; 3155canvas_polygon_region.shape_attributes['all_points_y'] = [ Math.round(_via_click_y0)] ; 3156 3157var new_length = _via_canvas_regions.push(canvas_polygon_region); 3158_via_current_polygon_region_id = new_length - 1; 3159break; 3160 3161case VIA_REGION_SHAPE.POINT: 3162// user has marked a landmark point 3163var point_region = new file_region(); 3164point_region.shape_attributes['name'] = VIA_REGION_SHAPE.POINT; 3165point_region.shape_attributes['cx'] = Math.round(_via_click_x0 * _via_canvas_scale); 3166point_region.shape_attributes['cy'] = Math.round(_via_click_y0 * _via_canvas_scale); 3167var region_count = _via_img_metadata[_via_image_id].regions.push(point_region); 3168var new_region_id = region_count - 1; 3169set_region_annotations_to_default_value( new_region_id ); 3170 3171var canvas_point_region = new file_region(); 3172canvas_point_region.shape_attributes['name'] = VIA_REGION_SHAPE.POINT; 3173canvas_point_region.shape_attributes['cx'] = Math.round(_via_click_x0); 3174canvas_point_region.shape_attributes['cy'] = Math.round(_via_click_y0); 3175_via_canvas_regions.push(canvas_point_region); 3176 3177annotation_editor_update_content(); 3178break; 3179} 3180} 3181} 3182} 3183_via_redraw_reg_canvas(); 3184_via_reg_canvas.focus(); 3185return; 3186} 3187 3188// indicates that user has finished drawing a new region 3189if ( _via_is_user_drawing_region ) { 3190_via_is_user_drawing_region = false; 3191var region_x0 = _via_click_x0; 3192var region_y0 = _via_click_y0; 3193var region_x1 = _via_click_x1; 3194var region_y1 = _via_click_y1; 3195 3196var original_img_region = new file_region(); 3197var canvas_img_region = new file_region(); 3198var region_dx = Math.abs(region_x1 - region_x0); 3199var region_dy = Math.abs(region_y1 - region_y0); 3200var new_region_added = false; 3201 3202if ( region_dx > VIA_REGION_MIN_DIM && region_dy > VIA_REGION_MIN_DIM ) { // avoid regions with 0 dim 3203switch(_via_current_shape) { 3204case VIA_REGION_SHAPE.RECT: 3205// ensure that (x0,y0) is top-left and (x1,y1) is bottom-right 3206if ( _via_click_x0 < _via_click_x1 ) { 3207region_x0 = _via_click_x0; 3208region_x1 = _via_click_x1; 3209} else { 3210region_x0 = _via_click_x1; 3211region_x1 = _via_click_x0; 3212} 3213 3214if ( _via_click_y0 < _via_click_y1 ) { 3215region_y0 = _via_click_y0; 3216region_y1 = _via_click_y1; 3217} else { 3218region_y0 = _via_click_y1; 3219region_y1 = _via_click_y0; 3220} 3221 3222var x = Math.round(region_x0 * _via_canvas_scale); 3223var y = Math.round(region_y0 * _via_canvas_scale); 3224var width = Math.round(region_dx * _via_canvas_scale); 3225var height = Math.round(region_dy * _via_canvas_scale); 3226original_img_region.shape_attributes['name'] = 'rect'; 3227original_img_region.shape_attributes['x'] = x; 3228original_img_region.shape_attributes['y'] = y; 3229original_img_region.shape_attributes['width'] = width; 3230original_img_region.shape_attributes['height'] = height; 3231 3232canvas_img_region.shape_attributes['name'] = 'rect'; 3233canvas_img_region.shape_attributes['x'] = Math.round( x / _via_canvas_scale ); 3234canvas_img_region.shape_attributes['y'] = Math.round( y / _via_canvas_scale ); 3235canvas_img_region.shape_attributes['width'] = Math.round( width / _via_canvas_scale ); 3236canvas_img_region.shape_attributes['height'] = Math.round( height / _via_canvas_scale ); 3237 3238new_region_added = true; 3239break; 3240 3241case VIA_REGION_SHAPE.CIRCLE: 3242var cx = Math.round(region_x0 * _via_canvas_scale); 3243var cy = Math.round(region_y0 * _via_canvas_scale); 3244var r = Math.round( Math.sqrt(region_dx*region_dx + region_dy*region_dy) * _via_canvas_scale ); 3245 3246original_img_region.shape_attributes['name'] = 'circle'; 3247original_img_region.shape_attributes['cx'] = cx; 3248original_img_region.shape_attributes['cy'] = cy; 3249original_img_region.shape_attributes['r'] = r; 3250 3251canvas_img_region.shape_attributes['name'] = 'circle'; 3252canvas_img_region.shape_attributes['cx'] = Math.round( cx / _via_canvas_scale ); 3253canvas_img_region.shape_attributes['cy'] = Math.round( cy / _via_canvas_scale ); 3254canvas_img_region.shape_attributes['r'] = Math.round( r / _via_canvas_scale ); 3255 3256new_region_added = true; 3257break; 3258 3259case VIA_REGION_SHAPE.ELLIPSE: 3260var cx = Math.round(region_x0 * _via_canvas_scale); 3261var cy = Math.round(region_y0 * _via_canvas_scale); 3262var rx = Math.round(region_dx * _via_canvas_scale); 3263var ry = Math.round(region_dy * _via_canvas_scale); 3264var theta = 0; 3265 3266original_img_region.shape_attributes['name'] = 'ellipse'; 3267original_img_region.shape_attributes['cx'] = cx; 3268original_img_region.shape_attributes['cy'] = cy; 3269original_img_region.shape_attributes['rx'] = rx; 3270original_img_region.shape_attributes['ry'] = ry; 3271original_img_region.shape_attributes['theta'] = theta; 3272 3273canvas_img_region.shape_attributes['name'] = 'ellipse'; 3274canvas_img_region.shape_attributes['cx'] = Math.round( cx / _via_canvas_scale ); 3275canvas_img_region.shape_attributes['cy'] = Math.round( cy / _via_canvas_scale ); 3276canvas_img_region.shape_attributes['rx'] = Math.round( rx / _via_canvas_scale ); 3277canvas_img_region.shape_attributes['ry'] = Math.round( ry / _via_canvas_scale ); 3278canvas_img_region.shape_attributes['theta'] = theta; 3279 3280new_region_added = true; 3281break; 3282 3283case VIA_REGION_SHAPE.POINT: // handled by case VIA_REGION_SHAPE.POLYGON 3284case VIA_REGION_SHAPE.POLYLINE: // handled by case VIA_REGION_SHAPE.POLYGON 3285case VIA_REGION_SHAPE.POLYGON: 3286// handled by _via_is_user_drawing_polygon 3287break; 3288} // end of switch 3289 3290if ( new_region_added ) { 3291var n1 = _via_img_metadata[_via_image_id].regions.push(original_img_region); 3292var n2 = _via_canvas_regions.push(canvas_img_region); 3293 3294if ( n1 !== n2 ) { 3295console.log('_via_img_metadata.regions[' + n1 + '] and _via_canvas_regions[' + n2 + '] count mismatch'); 3296} 3297var new_region_id = n1 - 1; 3298 3299set_region_annotations_to_default_value( new_region_id ); 3300select_only_region(new_region_id); 3301if ( _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS && 3302_via_metadata_being_updated === 'region' ) { 3303annotation_editor_add_row( new_region_id ); 3304annotation_editor_scroll_to_row( new_region_id ); 3305annotation_editor_clear_row_highlight(); 3306annotation_editor_highlight_row( new_region_id ); 3307} 3308annotation_editor_show(); 3309} 3310_via_redraw_reg_canvas(); 3311_via_reg_canvas.focus(); 3312} else { 3313show_message('Prevented accidental addition of a very small region.'); 3314} 3315return; 3316} 3317} 3318 3319function _via_reg_canvas_mouseover_handler(e) { 3320// change the mouse cursor icon 3321_via_redraw_reg_canvas(); 3322_via_reg_canvas.focus(); 3323} 3324 3325function _via_reg_canvas_mousemove_handler(e) { 3326if ( !_via_current_image_loaded ) { 3327return; 3328} 3329 3330_via_current_x = e.offsetX; _via_current_y = e.offsetY; 3331 3332// display the cursor coordinates 3333var rf = document.getElementById('region_info'); 3334if ( rf != null && _via_is_region_info_visible ) { 3335var img_x = Math.round( _via_current_x * _via_canvas_scale ); 3336var img_y = Math.round( _via_current_y * _via_canvas_scale ); 3337rf.innerHTML = 'X:' + img_x + ',' + ' Y:' + img_y; 3338} 3339 3340if ( _via_is_region_selected ) { 3341// display the region's info if a region is selected 3342if ( rf != null && _via_is_region_info_visible && _via_user_sel_region_id !== -1) { 3343var canvas_attr = _via_canvas_regions[_via_user_sel_region_id].shape_attributes; 3344switch (canvas_attr['name']) { 3345case VIA_REGION_SHAPE.RECT: 3346break; 3347 3348case VIA_REGION_SHAPE.CIRCLE: 3349var rf = document.getElementById('region_info'); 3350var attr = _via_canvas_regions[_via_user_sel_region_id].shape_attributes; 3351rf.innerHTML += ',' + ' Radius:' + attr['r']; 3352break; 3353 3354case VIA_REGION_SHAPE.ELLIPSE: 3355var rf = document.getElementById('region_info'); 3356var attr = _via_canvas_regions[_via_user_sel_region_id].shape_attributes; 3357rf.innerHTML += ',' + ' X-radius:' + attr['rx'] + ',' + ' Y-radius:' + attr['ry']; 3358break; 3359 3360case VIA_REGION_SHAPE.POLYLINE: 3361case VIA_REGION_SHAPE.POLYGON: 3362break; 3363} 3364} 3365 3366if ( !_via_is_user_resizing_region ) { 3367// check if user moved mouse cursor to region boundary 3368// which indicates an intention to resize the region 3369_via_region_edge = is_on_region_corner(_via_current_x, _via_current_y); 3370 3371if ( _via_region_edge[0] === _via_user_sel_region_id ) { 3372switch(_via_region_edge[1]) { 3373// rect 3374case 1: // Fall-through // top-left corner of rect 3375case 3: // bottom-right corner of rect 3376_via_reg_canvas.style.cursor = "nwse-resize"; 3377break; 3378case 2: // Fall-through // top-right corner of rect 3379case 4: // bottom-left corner of rect 3380_via_reg_canvas.style.cursor = "nesw-resize"; 3381break; 3382 3383case 5: // Fall-through // top-middle point of rect 3384case 7: // bottom-middle point of rect 3385_via_reg_canvas.style.cursor = "ns-resize"; 3386break; 3387case 6: // Fall-through // top-middle point of rect 3388case 8: // bottom-middle point of rect 3389_via_reg_canvas.style.cursor = "ew-resize"; 3390break; 3391 3392// circle and ellipse 3393case 5: 3394_via_reg_canvas.style.cursor = "n-resize"; 3395break; 3396case 6: 3397_via_reg_canvas.style.cursor = "e-resize"; 3398break; 3399 3400default: 3401_via_reg_canvas.style.cursor = "default"; 3402break; 3403} 3404 3405if (_via_region_edge[1] >= VIA_POLYGON_RESIZE_VERTEX_OFFSET) { 3406// indicates mouse over polygon vertex 3407_via_reg_canvas.style.cursor = "crosshair"; 3408show_message('To move vertex, simply drag the vertex. To add vertex, press [Ctrl] key and click on the edge. To delete vertex, press [Ctrl] (or [Command]) key and click on vertex.'); 3409} 3410} else { 3411var yes = is_inside_this_region(_via_current_x, 3412_via_current_y, 3413_via_user_sel_region_id); 3414if (yes) { 3415_via_reg_canvas.style.cursor = "move"; 3416} else { 3417_via_reg_canvas.style.cursor = "default"; 3418} 3419 3420} 3421} else { 3422annotation_editor_hide() // resizing 3423} 3424} 3425 3426if(_via_is_user_drawing_region) { 3427// draw region as the user drags the mouse cursor 3428if (_via_canvas_regions.length) { 3429_via_redraw_reg_canvas(); // clear old intermediate rectangle 3430} else { 3431// first region being drawn, just clear the full region canvas 3432_via_reg_ctx.clearRect(0, 0, _via_reg_canvas.width, _via_reg_canvas.height); 3433} 3434 3435var region_x0 = _via_click_x0; 3436var region_y0 = _via_click_y0; 3437 3438var dx = Math.round(Math.abs(_via_current_x - _via_click_x0)); 3439var dy = Math.round(Math.abs(_via_current_y - _via_click_y0)); 3440_via_reg_ctx.strokeStyle = VIA_THEME_BOUNDARY_FILL_COLOR; 3441 3442switch (_via_current_shape ) { 3443case VIA_REGION_SHAPE.RECT: 3444if ( _via_click_x0 < _via_current_x ) { 3445if ( _via_click_y0 < _via_current_y ) { 3446region_x0 = _via_click_x0; 3447region_y0 = _via_click_y0; 3448} else { 3449region_x0 = _via_click_x0; 3450region_y0 = _via_current_y; 3451} 3452} else { 3453if ( _via_click_y0 < _via_current_y ) { 3454region_x0 = _via_current_x; 3455region_y0 = _via_click_y0; 3456} else { 3457region_x0 = _via_current_x; 3458region_y0 = _via_current_y; 3459} 3460} 3461 3462_via_draw_rect_region(region_x0, region_y0, dx, dy, false); 3463 3464// display the current region info 3465if ( rf != null && _via_is_region_info_visible ) { 3466rf.innerHTML += ',' + ' W:' + dx + ',' + ' H:' + dy; 3467} 3468break; 3469 3470case VIA_REGION_SHAPE.CIRCLE: 3471var circle_radius = Math.round(Math.sqrt( dx*dx + dy*dy )); 3472_via_draw_circle_region(region_x0, region_y0, circle_radius, false); 3473 3474// display the current region info 3475if ( rf != null && _via_is_region_info_visible ) { 3476rf.innerHTML += ',' + ' Radius:' + circle_radius; 3477} 3478break; 3479 3480case VIA_REGION_SHAPE.ELLIPSE: 3481_via_draw_ellipse_region(region_x0, region_y0, dx, dy, 0, false); 3482 3483// display the current region info 3484if ( rf != null && _via_is_region_info_visible ) { 3485rf.innerHTML += ',' + ' X-radius:' + fixfloat(dx) + ',' + ' Y-radius:' + fixfloat(dy); 3486} 3487break; 3488 3489case VIA_REGION_SHAPE.POLYLINE: // handled by polygon 3490case VIA_REGION_SHAPE.POLYGON: 3491// this is handled by the if ( _via_is_user_drawing_polygon ) { ... } 3492// see below 3493break; 3494} 3495_via_reg_canvas.focus(); 3496} 3497 3498if ( _via_is_user_resizing_region ) { 3499// user has clicked mouse on bounding box edge and is now moving it 3500// draw region as the user drags the mouse coursor 3501if (_via_canvas_regions.length) { 3502_via_redraw_reg_canvas(); // clear old intermediate rectangle 3503} else { 3504// first region being drawn, just clear the full region canvas 3505_via_reg_ctx.clearRect(0, 0, _via_reg_canvas.width, _via_reg_canvas.height); 3506} 3507 3508var region_id = _via_region_edge[0]; 3509var attr = _via_canvas_regions[region_id].shape_attributes; 3510switch (attr['name']) { 3511case VIA_REGION_SHAPE.RECT: 3512// original rectangle 3513var d = [attr['x'], attr['y'], 0, 0]; 3514d[2] = d[0] + attr['width']; 3515d[3] = d[1] + attr['height']; 3516 3517var mx = _via_current_x; 3518var my = _via_current_y; 3519var preserve_aspect_ratio = false; 3520// constrain (mx,my) to lie on a line connecting a diagonal of rectangle 3521if ( _via_is_ctrl_pressed ) { 3522preserve_aspect_ratio = true; 3523} 3524 3525rect_update_corner(_via_region_edge[1], d, mx, my, preserve_aspect_ratio); 3526rect_standardize_coordinates(d); 3527 3528var w = Math.abs(d[2] - d[0]); 3529var h = Math.abs(d[3] - d[1]); 3530_via_draw_rect_region(d[0], d[1], w, h, true); 3531 3532if ( rf != null && _via_is_region_info_visible ) { 3533rf.innerHTML += ',' + ' W:' + w + ',' + ' H:' + h; 3534} 3535break; 3536 3537case VIA_REGION_SHAPE.CIRCLE: 3538var dx = Math.abs(attr['cx'] - _via_current_x); 3539var dy = Math.abs(attr['cy'] - _via_current_y); 3540var new_r = Math.sqrt( dx*dx + dy*dy ); 3541_via_draw_circle_region(attr['cx'], 3542attr['cy'], 3543new_r, 3544true); 3545if ( rf != null && _via_is_region_info_visible ) { 3546var curr_texts = rf.innerHTML.split(","); 3547rf.innerHTML = ""; 3548rf.innerHTML += curr_texts[0] + ',' + curr_texts[1] + ',' + ' Radius:' + Math.round(new_r); 3549} 3550break; 3551 3552case VIA_REGION_SHAPE.ELLIPSE: 3553var new_rx = attr['rx']; 3554var new_ry = attr['ry']; 3555var new_theta = attr['theta']; 3556var dx = Math.abs(attr['cx'] - _via_current_x); 3557var dy = Math.abs(attr['cy'] - _via_current_y); 3558switch(_via_region_edge[1]) { 3559case 5: 3560new_ry = Math.sqrt(dx*dx + dy*dy); 3561new_theta = Math.atan2(- (_via_current_x - attr['cx']), (_via_current_y - attr['cy'])); 3562break; 3563 3564case 6: 3565new_rx = Math.sqrt(dx*dx + dy*dy); 3566new_theta = Math.atan2((_via_current_y - attr['cy']), (_via_current_x - attr['cx'])); 3567break; 3568 3569default: 3570new_rx = dx; 3571new_ry = dy; 3572new_theta = 0; 3573break; 3574} 3575 3576_via_draw_ellipse_region(attr['cx'], 3577attr['cy'], 3578new_rx, 3579new_ry, 3580new_theta, 3581true); 3582if ( rf != null && _via_is_region_info_visible ) { 3583var curr_texts = rf.innerHTML.split(","); 3584rf.innerHTML = ""; 3585rf.innerHTML = curr_texts[0] + ',' + curr_texts[1] + ',' + ' X-radius:' + fixfloat(new_rx) + ',' + ' Y-radius:' + fixfloat(new_ry); 3586} 3587break; 3588 3589case VIA_REGION_SHAPE.POLYLINE: // handled by polygon 3590case VIA_REGION_SHAPE.POLYGON: 3591var moved_all_points_x = attr['all_points_x'].slice(0); 3592var moved_all_points_y = attr['all_points_y'].slice(0); 3593var moved_vertex_id = _via_region_edge[1] - VIA_POLYGON_RESIZE_VERTEX_OFFSET; 3594 3595moved_all_points_x[moved_vertex_id] = _via_current_x; 3596moved_all_points_y[moved_vertex_id] = _via_current_y; 3597 3598_via_draw_polygon_region(moved_all_points_x, 3599moved_all_points_y, 3600true, 3601attr['name']); 3602if ( rf != null && _via_is_region_info_visible ) { 3603rf.innerHTML += ',' + ' Vertices:' + attr['all_points_x'].length; 3604} 3605break; 3606} 3607_via_reg_canvas.focus(); 3608} 3609 3610if ( _via_is_user_moving_region ) { 3611// draw region as the user drags the mouse coursor 3612if (_via_canvas_regions.length) { 3613_via_redraw_reg_canvas(); // clear old intermediate rectangle 3614} else { 3615// first region being drawn, just clear the full region canvas 3616_via_reg_ctx.clearRect(0, 0, _via_reg_canvas.width, _via_reg_canvas.height); 3617} 3618 3619var move_x = (_via_current_x - _via_region_click_x); 3620var move_y = (_via_current_y - _via_region_click_y); 3621var attr = _via_canvas_regions[_via_user_sel_region_id].shape_attributes; 3622 3623switch (attr['name']) { 3624case VIA_REGION_SHAPE.RECT: 3625_via_draw_rect_region(attr['x'] + move_x, 3626attr['y'] + move_y, 3627attr['width'], 3628attr['height'], 3629true); 3630// display the current region info 3631if ( rf != null && _via_is_region_info_visible ) { 3632rf.innerHTML += ',' + ' W:' + attr['width'] + ',' + ' H:' + attr['height']; 3633} 3634break; 3635 3636case VIA_REGION_SHAPE.CIRCLE: 3637_via_draw_circle_region(attr['cx'] + move_x, 3638attr['cy'] + move_y, 3639attr['r'], 3640true); 3641break; 3642 3643case VIA_REGION_SHAPE.ELLIPSE: 3644if (typeof(attr['theta']) === 'undefined') { attr['theta'] = 0; } 3645_via_draw_ellipse_region(attr['cx'] + move_x, 3646attr['cy'] + move_y, 3647attr['rx'], 3648attr['ry'], 3649attr['theta'], 3650true); 3651break; 3652 3653case VIA_REGION_SHAPE.POLYLINE: // handled by polygon 3654case VIA_REGION_SHAPE.POLYGON: 3655var moved_all_points_x = attr['all_points_x'].slice(0); 3656var moved_all_points_y = attr['all_points_y'].slice(0); 3657for (var i=0; i<moved_all_points_x.length; ++i) { 3658moved_all_points_x[i] += move_x; 3659moved_all_points_y[i] += move_y; 3660} 3661_via_draw_polygon_region(moved_all_points_x, 3662moved_all_points_y, 3663true, 3664attr['name']); 3665if ( rf != null && _via_is_region_info_visible ) { 3666rf.innerHTML += ',' + ' Vertices:' + attr['all_points_x'].length; 3667} 3668break; 3669 3670case VIA_REGION_SHAPE.POINT: 3671_via_draw_point_region(attr['cx'] + move_x, 3672attr['cy'] + move_y, 3673true); 3674break; 3675} 3676_via_reg_canvas.focus(); 3677annotation_editor_hide() // moving 3678return; 3679} 3680 3681if ( _via_is_user_drawing_polygon ) { 3682_via_redraw_reg_canvas(); 3683var attr = _via_canvas_regions[_via_current_polygon_region_id].shape_attributes; 3684var all_points_x = attr['all_points_x']; 3685var all_points_y = attr['all_points_y']; 3686var npts = all_points_x.length; 3687 3688if ( npts > 0 ) { 3689var line_x = [all_points_x.slice(npts-1), _via_current_x]; 3690var line_y = [all_points_y.slice(npts-1), _via_current_y]; 3691_via_draw_polygon_region(line_x, line_y, false, attr['name']); 3692} 3693 3694if ( rf != null && _via_is_region_info_visible ) { 3695rf.innerHTML += ',' + ' Vertices:' + npts; 3696} 3697} 3698} 3699 3700function _via_move_selected_regions(move_x, move_y) { 3701var i, n; 3702n = _via_region_selected_flag.length; 3703for ( i = 0; i < n; ++i ) { 3704if ( _via_region_selected_flag[i] ) { 3705_via_move_region(i, move_x, move_y); 3706} 3707} 3708} 3709 3710function _via_validate_move_region(x, y, canvas_attr) { 3711switch( canvas_attr['name'] ) { 3712case VIA_REGION_SHAPE.RECT: 3713// left and top boundary check 3714if (x < 0 || y < 0) { 3715show_message('Region moved beyond image boundary. Resetting.'); 3716return false; 3717} 3718// right and bottom boundary check 3719if ((y + canvas_attr['height']) > _via_current_image_height || 3720(x + canvas_attr['width']) > _via_current_image_width) { 3721show_message('Region moved beyond image boundary. Resetting.'); 3722return false; 3723} 3724 3725// same validation for all 3726case VIA_REGION_SHAPE.CIRCLE: 3727case VIA_REGION_SHAPE.ELLIPSE: 3728case VIA_REGION_SHAPE.POINT: 3729case VIA_REGION_SHAPE.POLYLINE: 3730case VIA_REGION_SHAPE.POLYGON: 3731if (x < 0 || y < 0 || 3732x > _via_current_image_width || y > _via_current_image_height) { 3733show_message('Region moved beyond image boundary. Resetting.'); 3734return false; 3735} 3736} 3737return true; 3738} 3739 3740function _via_move_region(region_id, move_x, move_y) { 3741var image_attr = _via_img_metadata[_via_image_id].regions[region_id].shape_attributes; 3742var canvas_attr = _via_canvas_regions[region_id].shape_attributes; 3743 3744switch( canvas_attr['name'] ) { 3745case VIA_REGION_SHAPE.RECT: 3746var xnew = image_attr['x'] + Math.round(move_x * _via_canvas_scale); 3747var ynew = image_attr['y'] + Math.round(move_y * _via_canvas_scale); 3748 3749var is_valid = _via_validate_move_region(xnew, ynew, image_attr); 3750if (! is_valid ) { break; } 3751 3752image_attr['x'] = xnew; 3753image_attr['y'] = ynew; 3754 3755canvas_attr['x'] = Math.round( image_attr['x'] / _via_canvas_scale); 3756canvas_attr['y'] = Math.round( image_attr['y'] / _via_canvas_scale); 3757break; 3758 3759case VIA_REGION_SHAPE.CIRCLE: // Fall-through 3760case VIA_REGION_SHAPE.ELLIPSE: // Fall-through 3761case VIA_REGION_SHAPE.POINT: 3762var cxnew = image_attr['cx'] + Math.round(move_x * _via_canvas_scale); 3763var cynew = image_attr['cy'] + Math.round(move_y * _via_canvas_scale); 3764 3765var is_valid = _via_validate_move_region(cxnew, cynew, image_attr); 3766if (! is_valid ) { break; } 3767 3768image_attr['cx'] = cxnew; 3769image_attr['cy'] = cynew; 3770 3771canvas_attr['cx'] = Math.round( image_attr['cx'] / _via_canvas_scale); 3772canvas_attr['cy'] = Math.round( image_attr['cy'] / _via_canvas_scale); 3773break; 3774 3775case VIA_REGION_SHAPE.POLYLINE: // handled by polygon 3776case VIA_REGION_SHAPE.POLYGON: 3777var img_px = image_attr['all_points_x']; 3778var img_py = image_attr['all_points_y']; 3779var canvas_px = canvas_attr['all_points_x']; 3780var canvas_py = canvas_attr['all_points_y']; 3781// clone for reverting if valiation fails 3782var img_px_old = Object.assign({}, img_px); 3783var img_py_old = Object.assign({}, img_py); 3784 3785// validate move 3786for (var i=0; i<img_px.length; ++i) { 3787var pxnew = img_px[i] + Math.round(move_x * _via_canvas_scale); 3788var pynew = img_py[i] + Math.round(move_y * _via_canvas_scale); 3789if (! _via_validate_move_region(pxnew, pynew, image_attr) ) { 3790img_px = img_px_old; 3791img_py = img_py_old; 3792break; 3793} 3794} 3795// move points 3796for (var i=0; i<img_px.length; ++i) { 3797img_px[i] = img_px[i] + Math.round(move_x * _via_canvas_scale); 3798img_py[i] = img_py[i] + Math.round(move_y * _via_canvas_scale); 3799} 3800 3801for (var i=0; i<canvas_px.length; ++i) { 3802canvas_px[i] = Math.round( img_px[i] / _via_canvas_scale ); 3803canvas_py[i] = Math.round( img_py[i] / _via_canvas_scale ); 3804} 3805break; 3806} 3807} 3808 3809function _via_polygon_del_vertex(region_id, vertex_id) { 3810var rs = _via_canvas_regions[region_id].shape_attributes; 3811var npts = rs['all_points_x'].length; 3812var shape = rs['name']; 3813if ( shape !== VIA_REGION_SHAPE.POLYGON && shape !== VIA_REGION_SHAPE.POLYLINE ) { 3814show_message('Vertices can only be deleted from polygon/polyline.'); 3815return false; 3816} 3817if ( npts <=3 && shape === VIA_REGION_SHAPE.POLYGON ) { 3818show_message('Failed to delete vertex because a polygon must have at least 3 vertices.'); 3819return false; 3820} 3821if ( npts <=2 && shape === VIA_REGION_SHAPE.POLYLINE ) { 3822show_message('Failed to delete vertex because a polyline must have at least 2 vertices.'); 3823return false; 3824} 3825// delete vertex from canvas 3826_via_canvas_regions[region_id].shape_attributes['all_points_x'].splice(vertex_id, 1); 3827_via_canvas_regions[region_id].shape_attributes['all_points_y'].splice(vertex_id, 1); 3828 3829// delete vertex from image metadata 3830_via_img_metadata[_via_image_id].regions[region_id].shape_attributes['all_points_x'].splice(vertex_id, 1); 3831_via_img_metadata[_via_image_id].regions[region_id].shape_attributes['all_points_y'].splice(vertex_id, 1); 3832return true; 3833} 3834 3835// 3836// Canvas update routines 3837// 3838function _via_redraw_reg_canvas() { 3839if (_via_current_image_loaded) { 3840_via_reg_ctx.clearRect(0, 0, _via_reg_canvas.width, _via_reg_canvas.height); 3841if ( _via_canvas_regions.length > 0 ) { 3842if (_via_is_region_boundary_visible) { 3843draw_all_regions(); 3844} 3845if (_via_is_region_id_visible) { 3846draw_all_region_id(); 3847} 3848} 3849} 3850} 3851 3852function _via_clear_reg_canvas() { 3853_via_reg_ctx.clearRect(0, 0, _via_reg_canvas.width, _via_reg_canvas.height); 3854} 3855 3856function draw_all_regions() { 3857var aid = _via_settings.ui.image.region_color; 3858var attr, is_selected, aid, avalue; 3859for (var i=0; i < _via_canvas_regions.length; ++i) { 3860attr = _via_canvas_regions[i].shape_attributes; 3861is_selected = _via_region_selected_flag[i]; 3862 3863// region stroke style may depend on attribute value 3864_via_reg_ctx.strokeStyle = VIA_THEME_BOUNDARY_FILL_COLOR; 3865if ( ! _via_is_user_drawing_polygon && 3866aid !== '__via_default_region_color__' ) { 3867avalue = _via_img_metadata[_via_image_id].regions[i].region_attributes[aid]; 3868if ( _via_canvas_regions_group_color.hasOwnProperty(avalue) ) { 3869_via_reg_ctx.strokeStyle = _via_canvas_regions_group_color[avalue]; 3870} 3871} 3872 3873switch( attr['name'] ) { 3874case VIA_REGION_SHAPE.RECT: 3875_via_draw_rect_region(attr['x'], 3876attr['y'], 3877attr['width'], 3878attr['height'], 3879is_selected); 3880break; 3881 3882case VIA_REGION_SHAPE.CIRCLE: 3883_via_draw_circle_region(attr['cx'], 3884attr['cy'], 3885attr['r'], 3886is_selected); 3887break; 3888 3889case VIA_REGION_SHAPE.ELLIPSE: 3890if (typeof(attr['theta']) === 'undefined') { attr['theta'] = 0; } 3891_via_draw_ellipse_region(attr['cx'], 3892attr['cy'], 3893attr['rx'], 3894attr['ry'], 3895attr['theta'], 3896is_selected); 3897break; 3898 3899case VIA_REGION_SHAPE.POLYLINE: // handled by polygon 3900case VIA_REGION_SHAPE.POLYGON: 3901_via_draw_polygon_region(attr['all_points_x'], 3902attr['all_points_y'], 3903is_selected, 3904attr['name']); 3905break; 3906 3907case VIA_REGION_SHAPE.POINT: 3908_via_draw_point_region(attr['cx'], 3909attr['cy'], 3910is_selected); 3911break; 3912} 3913} 3914} 3915 3916// control point for resize of region boundaries 3917function _via_draw_control_point(cx, cy) { 3918_via_reg_ctx.beginPath(); 3919_via_reg_ctx.arc(cx, cy, VIA_REGION_SHAPES_POINTS_RADIUS, 0, 2*Math.PI, false); 3920_via_reg_ctx.closePath(); 3921 3922_via_reg_ctx.fillStyle = VIA_THEME_CONTROL_POINT_COLOR; 3923_via_reg_ctx.globalAlpha = 1.0; 3924_via_reg_ctx.fill(); 3925} 3926 3927function _via_draw_rect_region(x, y, w, h, is_selected) { 3928if (is_selected) { 3929_via_draw_rect(x, y, w, h); 3930 3931_via_reg_ctx.strokeStyle = VIA_THEME_SEL_REGION_FILL_BOUNDARY_COLOR; 3932_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/2; 3933_via_reg_ctx.stroke(); 3934 3935_via_reg_ctx.fillStyle = VIA_THEME_SEL_REGION_FILL_COLOR; 3936_via_reg_ctx.globalAlpha = VIA_THEME_SEL_REGION_OPACITY; 3937_via_reg_ctx.fill(); 3938_via_reg_ctx.globalAlpha = 1.0; 3939 3940_via_draw_control_point(x , y); 3941_via_draw_control_point(x+w, y+h); 3942_via_draw_control_point(x , y+h); 3943_via_draw_control_point(x+w, y); 3944_via_draw_control_point(x+w/2, y); 3945_via_draw_control_point(x+w/2, y+h); 3946_via_draw_control_point(x , y+h/2); 3947_via_draw_control_point(x+w , y+h/2); 3948} else { 3949// draw a fill line 3950_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/2; 3951_via_draw_rect(x, y, w, h); 3952_via_reg_ctx.stroke(); 3953 3954if ( w > VIA_THEME_REGION_BOUNDARY_WIDTH && 3955h > VIA_THEME_REGION_BOUNDARY_WIDTH ) { 3956// draw a boundary line on both sides of the fill line 3957_via_reg_ctx.strokeStyle = VIA_THEME_BOUNDARY_LINE_COLOR; 3958_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/4; 3959_via_draw_rect(x - VIA_THEME_REGION_BOUNDARY_WIDTH/2, 3960y - VIA_THEME_REGION_BOUNDARY_WIDTH/2, 3961w + VIA_THEME_REGION_BOUNDARY_WIDTH, 3962h + VIA_THEME_REGION_BOUNDARY_WIDTH); 3963_via_reg_ctx.stroke(); 3964 3965_via_draw_rect(x + VIA_THEME_REGION_BOUNDARY_WIDTH/2, 3966y + VIA_THEME_REGION_BOUNDARY_WIDTH/2, 3967w - VIA_THEME_REGION_BOUNDARY_WIDTH, 3968h - VIA_THEME_REGION_BOUNDARY_WIDTH); 3969_via_reg_ctx.stroke(); 3970} 3971} 3972} 3973 3974function _via_draw_rect(x, y, w, h) { 3975_via_reg_ctx.beginPath(); 3976_via_reg_ctx.moveTo(x , y); 3977_via_reg_ctx.lineTo(x+w, y); 3978_via_reg_ctx.lineTo(x+w, y+h); 3979_via_reg_ctx.lineTo(x , y+h); 3980_via_reg_ctx.closePath(); 3981} 3982 3983function _via_draw_circle_region(cx, cy, r, is_selected) { 3984if (is_selected) { 3985_via_draw_circle(cx, cy, r); 3986 3987_via_reg_ctx.strokeStyle = VIA_THEME_SEL_REGION_FILL_BOUNDARY_COLOR; 3988_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/2; 3989_via_reg_ctx.stroke(); 3990 3991_via_reg_ctx.fillStyle = VIA_THEME_SEL_REGION_FILL_COLOR; 3992_via_reg_ctx.globalAlpha = VIA_THEME_SEL_REGION_OPACITY; 3993_via_reg_ctx.fill(); 3994_via_reg_ctx.globalAlpha = 1.0; 3995 3996_via_draw_control_point(cx + r, cy); 3997} else { 3998// draw a fill line 3999_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/2; 4000_via_draw_circle(cx, cy, r); 4001_via_reg_ctx.stroke(); 4002 4003if ( r > VIA_THEME_REGION_BOUNDARY_WIDTH ) { 4004// draw a boundary line on both sides of the fill line 4005_via_reg_ctx.strokeStyle = VIA_THEME_BOUNDARY_LINE_COLOR; 4006_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/4; 4007_via_draw_circle(cx, cy, 4008r - VIA_THEME_REGION_BOUNDARY_WIDTH/2); 4009_via_reg_ctx.stroke(); 4010_via_draw_circle(cx, cy, 4011r + VIA_THEME_REGION_BOUNDARY_WIDTH/2); 4012_via_reg_ctx.stroke(); 4013} 4014} 4015} 4016 4017function _via_draw_circle(cx, cy, r) { 4018_via_reg_ctx.beginPath(); 4019_via_reg_ctx.arc(cx, cy, r, 0, 2*Math.PI, false); 4020_via_reg_ctx.closePath(); 4021} 4022 4023function _via_draw_ellipse_region(cx, cy, rx, ry, rr, is_selected) { 4024if (is_selected) { 4025_via_draw_ellipse(cx, cy, rx, ry, rr); 4026 4027_via_reg_ctx.strokeStyle = VIA_THEME_SEL_REGION_FILL_BOUNDARY_COLOR; 4028_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/2; 4029_via_reg_ctx.stroke(); 4030 4031_via_reg_ctx.fillStyle = VIA_THEME_SEL_REGION_FILL_COLOR; 4032_via_reg_ctx.globalAlpha = VIA_THEME_SEL_REGION_OPACITY; 4033_via_reg_ctx.fill(); 4034_via_reg_ctx.globalAlpha = 1.0; 4035 4036_via_draw_control_point(cx + rx * Math.cos(rr), cy + rx * Math.sin(rr)); 4037_via_draw_control_point(cx - rx * Math.cos(rr), cy - rx * Math.sin(rr)); 4038_via_draw_control_point(cx + ry * Math.sin(rr), cy - ry * Math.cos(rr)); 4039_via_draw_control_point(cx - ry * Math.sin(rr), cy + ry * Math.cos(rr)); 4040 4041} else { 4042// draw a fill line 4043_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/2; 4044_via_draw_ellipse(cx, cy, rx, ry, rr); 4045_via_reg_ctx.stroke(); 4046 4047if ( rx > VIA_THEME_REGION_BOUNDARY_WIDTH && 4048ry > VIA_THEME_REGION_BOUNDARY_WIDTH ) { 4049// draw a boundary line on both sides of the fill line 4050_via_reg_ctx.strokeStyle = VIA_THEME_BOUNDARY_LINE_COLOR; 4051_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/4; 4052_via_draw_ellipse(cx, cy, 4053rx + VIA_THEME_REGION_BOUNDARY_WIDTH/2, 4054ry + VIA_THEME_REGION_BOUNDARY_WIDTH/2, 4055rr); 4056_via_reg_ctx.stroke(); 4057_via_draw_ellipse(cx, cy, 4058rx - VIA_THEME_REGION_BOUNDARY_WIDTH/2, 4059ry - VIA_THEME_REGION_BOUNDARY_WIDTH/2, 4060rr); 4061_via_reg_ctx.stroke(); 4062} 4063} 4064} 4065 4066function _via_draw_ellipse(cx, cy, rx, ry, rr) { 4067_via_reg_ctx.save(); 4068 4069_via_reg_ctx.beginPath(); 4070_via_reg_ctx.ellipse(cx, cy, rx, ry, rr, 0, 2 * Math.PI); 4071 4072_via_reg_ctx.restore(); // restore to original state 4073_via_reg_ctx.closePath(); 4074} 4075 4076function _via_draw_polygon_region(all_points_x, all_points_y, is_selected, shape) { 4077if ( is_selected ) { 4078_via_reg_ctx.strokeStyle = VIA_THEME_SEL_REGION_FILL_BOUNDARY_COLOR; 4079_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/2; 4080_via_reg_ctx.beginPath(); 4081_via_reg_ctx.moveTo(all_points_x[0], all_points_y[0]); 4082for ( var i=1; i < all_points_x.length; ++i ) { 4083_via_reg_ctx.lineTo(all_points_x[i], all_points_y[i]); 4084} 4085if ( shape === VIA_REGION_SHAPE.POLYGON ) { 4086_via_reg_ctx.lineTo(all_points_x[0], all_points_y[0]); // close loop 4087} 4088_via_reg_ctx.stroke(); 4089 4090_via_reg_ctx.fillStyle = VIA_THEME_SEL_REGION_FILL_COLOR; 4091_via_reg_ctx.globalAlpha = VIA_THEME_SEL_REGION_OPACITY; 4092_via_reg_ctx.fill(); 4093_via_reg_ctx.globalAlpha = 1.0; 4094for ( var i=0; i < all_points_x.length; ++i ) { 4095_via_draw_control_point(all_points_x[i], all_points_y[i]); 4096} 4097} else { 4098// draw a fill line 4099_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/2; 4100_via_reg_ctx.beginPath(); 4101_via_reg_ctx.moveTo(all_points_x[0], all_points_y[0]); 4102for ( var i=0; i < all_points_x.length; ++i ) { 4103_via_reg_ctx.lineTo(all_points_x[i], all_points_y[i]); 4104} 4105if ( shape === VIA_REGION_SHAPE.POLYGON ) { 4106_via_reg_ctx.lineTo(all_points_x[0], all_points_y[0]); // close loop 4107} 4108_via_reg_ctx.stroke(); 4109} 4110} 4111 4112function _via_draw_point_region(cx, cy, is_selected) { 4113if (is_selected) { 4114_via_draw_point(cx, cy, VIA_REGION_POINT_RADIUS); 4115 4116_via_reg_ctx.strokeStyle = VIA_THEME_SEL_REGION_FILL_BOUNDARY_COLOR; 4117_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/2; 4118_via_reg_ctx.stroke(); 4119 4120_via_reg_ctx.fillStyle = VIA_THEME_SEL_REGION_FILL_COLOR; 4121_via_reg_ctx.globalAlpha = VIA_THEME_SEL_REGION_OPACITY; 4122_via_reg_ctx.fill(); 4123_via_reg_ctx.globalAlpha = 1.0; 4124} else { 4125// draw a fill line 4126_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/2; 4127_via_draw_point(cx, cy, VIA_REGION_POINT_RADIUS); 4128_via_reg_ctx.stroke(); 4129 4130// draw a boundary line on both sides of the fill line 4131_via_reg_ctx.strokeStyle = VIA_THEME_BOUNDARY_LINE_COLOR; 4132_via_reg_ctx.lineWidth = VIA_THEME_REGION_BOUNDARY_WIDTH/4; 4133_via_draw_point(cx, cy, 4134VIA_REGION_POINT_RADIUS - VIA_THEME_REGION_BOUNDARY_WIDTH/2); 4135_via_reg_ctx.stroke(); 4136_via_draw_point(cx, cy, 4137VIA_REGION_POINT_RADIUS + VIA_THEME_REGION_BOUNDARY_WIDTH/2); 4138_via_reg_ctx.stroke(); 4139} 4140} 4141 4142function _via_draw_point(cx, cy, r) { 4143_via_reg_ctx.beginPath(); 4144_via_reg_ctx.arc(cx, cy, r, 0, 2*Math.PI, false); 4145_via_reg_ctx.closePath(); 4146} 4147 4148function draw_all_region_id() { 4149_via_reg_ctx.shadowColor = "transparent"; 4150_via_reg_ctx.font = _via_settings.ui.image.region_label_font; 4151for ( var i = 0; i < _via_img_metadata[_via_image_id].regions.length; ++i ) { 4152var canvas_reg = _via_canvas_regions[i]; 4153 4154var bbox = get_region_bounding_box(canvas_reg); 4155var x = bbox[0]; 4156var y = bbox[1]; 4157var w = Math.abs(bbox[2] - bbox[0]); 4158 4159var char_width = _via_reg_ctx.measureText('M').width; 4160var char_height = 1.8 * char_width; 4161 4162var annotation_str = (i+1).toString(); 4163var rattr = _via_img_metadata[_via_image_id].regions[i].region_attributes[_via_settings.ui.image.region_label]; 4164var rshape = _via_img_metadata[_via_image_id].regions[i].shape_attributes['name']; 4165if ( _via_settings.ui.image.region_label !== '__via_region_id__' ) { 4166if ( typeof(rattr) !== 'undefined' ) { 4167switch( typeof(rattr) ) { 4168default: 4169case 'string': 4170annotation_str = rattr; 4171break; 4172case 'object': 4173annotation_str = Object.keys(rattr).join(','); 4174break; 4175} 4176} else { 4177annotation_str = 'undefined'; 4178} 4179} 4180 4181var bgnd_rect_width; 4182var strw = _via_reg_ctx.measureText(annotation_str).width; 4183if ( strw > w ) { 4184if ( _via_settings.ui.image.region_label === '__via_region_id__' ) { 4185// region-id is always visible in full 4186bgnd_rect_width = strw + char_width; 4187} else { 4188 4189// if text overflows, crop it 4190var str_max = Math.floor((w * annotation_str.length) / strw); 4191if ( str_max > 1 ) { 4192annotation_str = annotation_str.substr(0, str_max-1) + '.'; 4193bgnd_rect_width = w; 4194} else { 4195annotation_str = annotation_str.substr(0, 1) + '.'; 4196bgnd_rect_width = 2 * char_width; 4197} 4198} 4199} else { 4200bgnd_rect_width = strw + char_width; 4201} 4202 4203if (canvas_reg.shape_attributes['name'] === VIA_REGION_SHAPE.POLYGON || 4204canvas_reg.shape_attributes['name'] === VIA_REGION_SHAPE.POLYLINE) { 4205// put label near the first vertex 4206x = canvas_reg.shape_attributes['all_points_x'][0]; 4207y = canvas_reg.shape_attributes['all_points_y'][0]; 4208} else { 4209// center the label 4210x = x - (bgnd_rect_width/2 - w/2); 4211} 4212 4213// ensure that the text is within the image boundaries 4214if ( y < char_height ) { 4215y = char_height; 4216} 4217 4218// first, draw a background rectangle first 4219_via_reg_ctx.fillStyle = 'black'; 4220_via_reg_ctx.globalAlpha = 0.8; 4221_via_reg_ctx.fillRect(Math.floor(x), 4222Math.floor(y - 1.1*char_height), 4223Math.floor(bgnd_rect_width), 4224Math.floor(char_height)); 4225 4226// then, draw text over this background rectangle 4227_via_reg_ctx.globalAlpha = 1.0; 4228_via_reg_ctx.fillStyle = 'yellow'; 4229_via_reg_ctx.fillText(annotation_str, 4230Math.floor(x + 0.4*char_width), 4231Math.floor(y - 0.35*char_height)); 4232 4233} 4234} 4235 4236function get_region_bounding_box(region) { 4237var d = region.shape_attributes; 4238var bbox = new Array(4); 4239 4240switch( d['name'] ) { 4241case 'rect': 4242bbox[0] = d['x']; 4243bbox[1] = d['y']; 4244bbox[2] = d['x'] + d['width']; 4245bbox[3] = d['y'] + d['height']; 4246break; 4247 4248case 'circle': 4249bbox[0] = d['cx'] - d['r']; 4250bbox[1] = d['cy'] - d['r']; 4251bbox[2] = d['cx'] + d['r']; 4252bbox[3] = d['cy'] + d['r']; 4253break; 4254 4255case 'ellipse': 4256let radians = d['theta']; 4257let radians90 = radians + Math.PI / 2; 4258let ux = d['rx'] * Math.cos(radians); 4259let uy = d['rx'] * Math.sin(radians); 4260let vx = d['ry'] * Math.cos(radians90); 4261let vy = d['ry'] * Math.sin(radians90); 4262 4263let width = Math.sqrt(ux * ux + vx * vx) * 2; 4264let height = Math.sqrt(uy * uy + vy * vy) * 2; 4265 4266bbox[0] = d['cx'] - (width / 2); 4267bbox[1] = d['cy'] - (height / 2); 4268bbox[2] = d['cx'] + (width / 2); 4269bbox[3] = d['cy'] + (height / 2); 4270break; 4271 4272case 'polyline': // handled by polygon 4273case 'polygon': 4274var all_points_x = d['all_points_x']; 4275var all_points_y = d['all_points_y']; 4276 4277var minx = Number.MAX_SAFE_INTEGER; 4278var miny = Number.MAX_SAFE_INTEGER; 4279var maxx = 0; 4280var maxy = 0; 4281for ( var i=0; i < all_points_x.length; ++i ) { 4282if ( all_points_x[i] < minx ) { 4283minx = all_points_x[i]; 4284} 4285if ( all_points_x[i] > maxx ) { 4286maxx = all_points_x[i]; 4287} 4288if ( all_points_y[i] < miny ) { 4289miny = all_points_y[i]; 4290} 4291if ( all_points_y[i] > maxy ) { 4292maxy = all_points_y[i]; 4293} 4294} 4295bbox[0] = minx; 4296bbox[1] = miny; 4297bbox[2] = maxx; 4298bbox[3] = maxy; 4299break; 4300 4301case 'point': 4302bbox[0] = d['cx'] - VIA_REGION_POINT_RADIUS; 4303bbox[1] = d['cy'] - VIA_REGION_POINT_RADIUS; 4304bbox[2] = d['cx'] + VIA_REGION_POINT_RADIUS; 4305bbox[3] = d['cy'] + VIA_REGION_POINT_RADIUS; 4306break; 4307} 4308return bbox; 4309} 4310 4311// 4312// Region collision routines 4313// 4314function is_inside_region(px, py, descending_order) { 4315var N = _via_canvas_regions.length; 4316if ( N === 0 ) { 4317return -1; 4318} 4319var start, end, del; 4320// traverse the canvas regions in alternating ascending 4321// and descending order to solve the issue of nested regions 4322if ( descending_order ) { 4323start = N - 1; 4324end = -1; 4325del = -1; 4326} else { 4327start = 0; 4328end = N; 4329del = 1; 4330} 4331 4332var i = start; 4333while ( i !== end ) { 4334var yes = is_inside_this_region(px, py, i); 4335if (yes) { 4336return i; 4337} 4338i = i + del; 4339} 4340return -1; 4341} 4342 4343function is_inside_this_region(px, py, region_id) { 4344var attr = _via_canvas_regions[region_id].shape_attributes; 4345var result = false; 4346switch ( attr['name'] ) { 4347case VIA_REGION_SHAPE.RECT: 4348result = is_inside_rect(attr['x'], 4349attr['y'], 4350attr['width'], 4351attr['height'], 4352px, py); 4353break; 4354 4355case VIA_REGION_SHAPE.CIRCLE: 4356result = is_inside_circle(attr['cx'], 4357attr['cy'], 4358attr['r'], 4359px, py); 4360break; 4361 4362case VIA_REGION_SHAPE.ELLIPSE: 4363result = is_inside_ellipse(attr['cx'], 4364attr['cy'], 4365attr['rx'], 4366attr['ry'], 4367attr['theta'], 4368px, py); 4369break; 4370 4371case VIA_REGION_SHAPE.POLYLINE: // handled by POLYGON 4372case VIA_REGION_SHAPE.POLYGON: 4373result = is_inside_polygon(attr['all_points_x'], 4374attr['all_points_y'], 4375px, py); 4376break; 4377 4378case VIA_REGION_SHAPE.POINT: 4379result = is_inside_point(attr['cx'], 4380attr['cy'], 4381px, py); 4382break; 4383} 4384return result; 4385} 4386 4387function is_inside_circle(cx, cy, r, px, py) { 4388var dx = px - cx; 4389var dy = py - cy; 4390return (dx * dx + dy * dy) < r * r; 4391} 4392 4393function is_inside_rect(x, y, w, h, px, py) { 4394return px > x && 4395px < (x + w) && 4396py > y && 4397py < (y + h); 4398} 4399 4400function is_inside_ellipse(cx, cy, rx, ry, rr, px, py) { 4401// Inverse rotation of pixel coordinates 4402var dx = Math.cos(-rr) * (cx - px) - Math.sin(-rr) * (cy - py) 4403var dy = Math.sin(-rr) * (cx - px) + Math.cos(-rr) * (cy - py) 4404 4405return ((dx * dx) / (rx * rx)) + ((dy * dy) / (ry * ry)) < 1; 4406} 4407 4408// returns 0 when (px,py) is outside the polygon 4409// source: http://geomalgorithms.com/a03-_inclusion.html 4410function is_inside_polygon(all_points_x, all_points_y, px, py) { 4411if ( all_points_x.length === 0 || all_points_y.length === 0 ) { 4412return 0; 4413} 4414 4415var wn = 0; // the winding number counter 4416var n = all_points_x.length; 4417var i; 4418// loop through all edges of the polygon 4419for ( i = 0; i < n-1; ++i ) { // edge from V[i] to V[i+1] 4420var is_left_value = is_left( all_points_x[i], all_points_y[i], 4421all_points_x[i+1], all_points_y[i+1], 4422px, py); 4423 4424if (all_points_y[i] <= py) { 4425if (all_points_y[i+1] > py && is_left_value > 0) { 4426++wn; 4427} 4428} 4429else { 4430if (all_points_y[i+1] <= py && is_left_value < 0) { 4431--wn; 4432} 4433} 4434} 4435 4436// also take into account the loop closing edge that connects last point with first point 4437var is_left_value = is_left( all_points_x[n-1], all_points_y[n-1], 4438all_points_x[0], all_points_y[0], 4439px, py); 4440 4441if (all_points_y[n-1] <= py) { 4442if (all_points_y[0] > py && is_left_value > 0) { 4443++wn; 4444} 4445} 4446else { 4447if (all_points_y[0] <= py && is_left_value < 0) { 4448--wn; 4449} 4450} 4451 4452if ( wn === 0 ) { 4453return 0; 4454} 4455else { 4456return 1; 4457} 4458} 4459 4460function is_inside_point(cx, cy, px, py) { 4461var dx = px - cx; 4462var dy = py - cy; 4463var r2 = VIA_POLYGON_VERTEX_MATCH_TOL * VIA_POLYGON_VERTEX_MATCH_TOL; 4464return (dx * dx + dy * dy) < r2; 4465} 4466 4467// returns 4468// >0 if (x2,y2) lies on the left side of line joining (x0,y0) and (x1,y1) 4469// =0 if (x2,y2) lies on the line joining (x0,y0) and (x1,y1) 4470// >0 if (x2,y2) lies on the right side of line joining (x0,y0) and (x1,y1) 4471// source: http://geomalgorithms.com/a03-_inclusion.html 4472function is_left(x0, y0, x1, y1, x2, y2) { 4473return ( ((x1 - x0) * (y2 - y0)) - ((x2 - x0) * (y1 - y0)) ); 4474} 4475 4476function is_on_region_corner(px, py) { 4477var _via_region_edge = [-1, -1]; // region_id, corner_id [top-left=1,top-right=2,bottom-right=3,bottom-left=4] 4478 4479for ( var i = 0; i < _via_canvas_regions.length; ++i ) { 4480var attr = _via_canvas_regions[i].shape_attributes; 4481var result = false; 4482_via_region_edge[0] = i; 4483 4484switch ( attr['name'] ) { 4485case VIA_REGION_SHAPE.RECT: 4486result = is_on_rect_edge(attr['x'], 4487attr['y'], 4488attr['width'], 4489attr['height'], 4490px, py); 4491break; 4492 4493case VIA_REGION_SHAPE.CIRCLE: 4494result = is_on_circle_edge(attr['cx'], 4495attr['cy'], 4496attr['r'], 4497px, py); 4498break; 4499 4500case VIA_REGION_SHAPE.ELLIPSE: 4501result = is_on_ellipse_edge(attr['cx'], 4502attr['cy'], 4503attr['rx'], 4504attr['ry'], 4505attr['theta'], 4506px, py); 4507break; 4508 4509case VIA_REGION_SHAPE.POLYLINE: // handled by polygon 4510case VIA_REGION_SHAPE.POLYGON: 4511result = is_on_polygon_vertex(attr['all_points_x'], 4512attr['all_points_y'], 4513px, py); 4514if ( result === 0 ) { 4515result = is_on_polygon_edge(attr['all_points_x'], 4516attr['all_points_y'], 4517px, py); 4518} 4519break; 4520 4521case VIA_REGION_SHAPE.POINT: 4522// since there are no edges of a point 4523result = 0; 4524break; 4525} 4526 4527if (result > 0) { 4528_via_region_edge[1] = result; 4529return _via_region_edge; 4530} 4531} 4532_via_region_edge[0] = -1; 4533return _via_region_edge; 4534} 4535 4536function is_on_rect_edge(x, y, w, h, px, py) { 4537var dx0 = Math.abs(x - px); 4538var dy0 = Math.abs(y - py); 4539var dx1 = Math.abs(x + w - px); 4540var dy1 = Math.abs(y + h - py); 4541//[top-left=1,top-right=2,bottom-right=3,bottom-left=4] 4542if ( dx0 < VIA_REGION_EDGE_TOL && 4543dy0 < VIA_REGION_EDGE_TOL ) { 4544return 1; 4545} 4546if ( dx1 < VIA_REGION_EDGE_TOL && 4547dy0 < VIA_REGION_EDGE_TOL ) { 4548return 2; 4549} 4550if ( dx1 < VIA_REGION_EDGE_TOL && 4551dy1 < VIA_REGION_EDGE_TOL ) { 4552return 3; 4553} 4554 4555if ( dx0 < VIA_REGION_EDGE_TOL && 4556dy1 < VIA_REGION_EDGE_TOL ) { 4557return 4; 4558} 4559 4560var mx0 = Math.abs(x + w/2 - px); 4561var my0 = Math.abs(y + h/2 - py); 4562//[top-middle=5,right-middle=6,bottom-middle=7,left-middle=8] 4563if ( mx0 < VIA_REGION_EDGE_TOL && 4564dy0 < VIA_REGION_EDGE_TOL ) { 4565return 5; 4566} 4567if ( dx1 < VIA_REGION_EDGE_TOL && 4568my0 < VIA_REGION_EDGE_TOL ) { 4569return 6; 4570} 4571if ( mx0 < VIA_REGION_EDGE_TOL && 4572dy1 < VIA_REGION_EDGE_TOL ) { 4573return 7; 4574} 4575if ( dx0 < VIA_REGION_EDGE_TOL && 4576my0 < VIA_REGION_EDGE_TOL ) { 4577return 8; 4578} 4579 4580return 0; 4581} 4582 4583function is_on_circle_edge(cx, cy, r, px, py) { 4584var dx = cx - px; 4585var dy = cy - py; 4586if ( Math.abs(Math.sqrt( dx*dx + dy*dy ) - r) < VIA_REGION_EDGE_TOL ) { 4587var theta = Math.atan2( py - cy, px - cx ); 4588if ( Math.abs(theta - (Math.PI/2)) < VIA_THETA_TOL || 4589Math.abs(theta + (Math.PI/2)) < VIA_THETA_TOL) { 4590return 5; 4591} 4592if ( Math.abs(theta) < VIA_THETA_TOL || 4593Math.abs(Math.abs(theta) - Math.PI) < VIA_THETA_TOL) { 4594return 6; 4595} 4596 4597if ( theta > 0 && theta < (Math.PI/2) ) { 4598return 1; 4599} 4600if ( theta > (Math.PI/2) && theta < (Math.PI) ) { 4601return 4; 4602} 4603if ( theta < 0 && theta > -(Math.PI/2) ) { 4604return 2; 4605} 4606if ( theta < -(Math.PI/2) && theta > -Math.PI ) { 4607return 3; 4608} 4609} else { 4610return 0; 4611} 4612} 4613 4614function is_on_ellipse_edge(cx, cy, rx, ry, rr, px, py) { 4615// Inverse rotation of pixel coordinates 4616px = px - cx; 4617py = py - cy; 4618var px_ = Math.cos(-rr) * px - Math.sin(-rr) * py; 4619var py_ = Math.sin(-rr) * px + Math.cos(-rr) * py; 4620px = px_ + cx; 4621py = py_ + cy; 4622 4623var dx = (cx - px)/rx; 4624var dy = (cy - py)/ry; 4625 4626if ( Math.abs(Math.sqrt( dx*dx + dy*dy ) - 1) < VIA_ELLIPSE_EDGE_TOL ) { 4627var theta = Math.atan2( py - cy, px - cx ); 4628if ( Math.abs(theta - (Math.PI/2)) < VIA_THETA_TOL || 4629Math.abs(theta + (Math.PI/2)) < VIA_THETA_TOL) { 4630return 5; 4631} 4632if ( Math.abs(theta) < VIA_THETA_TOL || 4633Math.abs(Math.abs(theta) - Math.PI) < VIA_THETA_TOL) { 4634return 6; 4635} 4636} else { 4637return 0; 4638} 4639} 4640 4641function is_on_polygon_vertex(all_points_x, all_points_y, px, py) { 4642var i, n; 4643n = all_points_x.length; 4644 4645for ( i = 0; i < n; ++i ) { 4646if ( Math.abs(all_points_x[i] - px) < VIA_POLYGON_VERTEX_MATCH_TOL && 4647Math.abs(all_points_y[i] - py) < VIA_POLYGON_VERTEX_MATCH_TOL ) { 4648return (VIA_POLYGON_RESIZE_VERTEX_OFFSET+i); 4649} 4650} 4651return 0; 4652} 4653 4654function is_on_polygon_edge(all_points_x, all_points_y, px, py) { 4655var i, n, di, d; 4656n = all_points_x.length; 4657d = []; 4658for ( i = 0; i < n - 1; ++i ) { 4659di = dist_to_line(px, py, all_points_x[i], all_points_y[i], all_points_x[i+1], all_points_y[i+1]); 4660d.push(di); 4661} 4662// closing edge 4663di = dist_to_line(px, py, all_points_x[n-1], all_points_y[n-1], all_points_x[0], all_points_y[0]); 4664d.push(di); 4665 4666var smallest_value = d[0]; 4667var smallest_index = 0; 4668n = d.length; 4669for ( i = 1; i < n; ++i ) { 4670if ( d[i] < smallest_value ) { 4671smallest_value = d[i]; 4672smallest_index = i; 4673} 4674} 4675if ( smallest_value < VIA_POLYGON_VERTEX_MATCH_TOL ) { 4676return (VIA_POLYGON_RESIZE_VERTEX_OFFSET + smallest_index); 4677} else { 4678return 0; 4679} 4680} 4681 4682function is_point_inside_bounding_box(x, y, x1, y1, x2, y2) { 4683// ensure that (x1,y1) is top left and (x2,y2) is bottom right corner of rectangle 4684var rect = {}; 4685if( x1 < x2 ) { 4686rect.x1 = x1; 4687rect.x2 = x2; 4688} else { 4689rect.x1 = x2; 4690rect.x2 = x1; 4691} 4692if ( y1 < y2 ) { 4693rect.y1 = y1; 4694rect.y2 = y2; 4695} else { 4696rect.y1 = y2; 4697rect.y2 = y1; 4698} 4699 4700if ( x >= rect.x1 && x <= rect.x2 && y >= rect.y1 && y <= rect.y2 ) { 4701return true; 4702} else { 4703return false; 4704} 4705} 4706 4707function dist_to_line(x, y, x1, y1, x2, y2) { 4708if ( is_point_inside_bounding_box(x, y, x1, y1, x2, y2) ) { 4709var dy = y2 - y1; 4710var dx = x2 - x1; 4711var nr = Math.abs( dy*x - dx*y + x2*y1 - y2*x1 ); 4712var dr = Math.sqrt( dx*dx + dy*dy ); 4713var dist = nr / dr; 4714return Math.round(dist); 4715} else { 4716return Number.MAX_SAFE_INTEGER; 4717} 4718} 4719 4720function rect_standardize_coordinates(d) { 4721// d[x0,y0,x1,y1] 4722// ensures that (d[0],d[1]) is top-left corner while 4723// (d[2],d[3]) is bottom-right corner 4724if ( d[0] > d[2] ) { 4725// swap 4726var t = d[0]; 4727d[0] = d[2]; 4728d[2] = t; 4729} 4730 4731if ( d[1] > d[3] ) { 4732// swap 4733var t = d[1]; 4734d[1] = d[3]; 4735d[3] = t; 4736} 4737} 4738 4739function rect_update_corner(corner_id, d, x, y, preserve_aspect_ratio) { 4740// pre-condition : d[x0,y0,x1,y1] is standardized 4741// post-condition : corner is moved ( d may not stay standardized ) 4742if (preserve_aspect_ratio) { 4743switch(corner_id) { 4744case 1: // Fall-through // top-left 4745case 3: // bottom-right 4746var dx = d[2] - d[0]; 4747var dy = d[3] - d[1]; 4748var norm = Math.sqrt( dx*dx + dy*dy ); 4749var nx = dx / norm; // x component of unit vector along the diagonal of rect 4750var ny = dy / norm; // y component 4751var proj = (x - d[0]) * nx + (y - d[1]) * ny; 4752var proj_x = nx * proj; 4753var proj_y = ny * proj; 4754// constrain (mx,my) to lie on a line connecting (x0,y0) and (x1,y1) 4755x = Math.round( d[0] + proj_x ); 4756y = Math.round( d[1] + proj_y ); 4757break; 4758 4759case 2: // Fall-through // top-right 4760case 4: // bottom-left 4761var dx = d[2] - d[0]; 4762var dy = d[1] - d[3]; 4763var norm = Math.sqrt( dx*dx + dy*dy ); 4764var nx = dx / norm; // x component of unit vector along the diagonal of rect 4765var ny = dy / norm; // y component 4766var proj = (x - d[0]) * nx + (y - d[3]) * ny; 4767var proj_x = nx * proj; 4768var proj_y = ny * proj; 4769// constrain (mx,my) to lie on a line connecting (x0,y0) and (x1,y1) 4770x = Math.round( d[0] + proj_x ); 4771y = Math.round( d[3] + proj_y ); 4772break; 4773} 4774} 4775 4776switch(corner_id) { 4777case 1: // top-left 4778d[0] = x; 4779d[1] = y; 4780break; 4781 4782case 3: // bottom-right 4783d[2] = x; 4784d[3] = y; 4785break; 4786 4787case 2: // top-right 4788d[2] = x; 4789d[1] = y; 4790break; 4791 4792case 4: // bottom-left 4793d[0] = x; 4794d[3] = y; 4795break; 4796 4797case 5: // top-middle 4798d[1] = y; 4799break; 4800 4801case 6: // right-middle 4802d[2] = x; 4803break; 4804 4805case 7: // bottom-middle 4806d[3] = y; 4807break; 4808 4809case 8: // left-middle 4810d[0] = x; 4811break; 4812} 4813} 4814 4815function _via_update_ui_components() { 4816if ( ! _via_current_image_loaded ) { 4817return; 4818} 4819 4820show_message('Updating user interface components.'); 4821switch(_via_display_area_content_name) { 4822case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID: 4823image_grid_set_content_panel_height_fixed(); 4824image_grid_set_content_to_current_group(); 4825break; 4826case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE: 4827if ( !_via_is_window_resized && _via_current_image_loaded ) { 4828_via_is_window_resized = true; 4829_via_show_img(_via_image_index); 4830 4831if (_via_is_canvas_zoomed) { 4832reset_zoom_level(); 4833} 4834} 4835break; 4836} 4837} 4838 4839// 4840// Shortcut key handlers 4841// 4842function _via_window_keydown_handler(e) { 4843if ( e.target === document.body ) { 4844// process the keyboard event 4845_via_handle_global_keydown_event(e); 4846} 4847} 4848 4849// global keys are active irrespective of element focus 4850// arrow keys, n, p, s, o, space, d, Home, End, PageUp, PageDown 4851function _via_handle_global_keydown_event(e) { 4852// zoom 4853if (_via_current_image_loaded) { 4854if ( e.key === "+") { 4855zoom_in(); 4856return; 4857} 4858 4859if ( e.key === "=") { 4860reset_zoom_level(); 4861return; 4862} 4863 4864if ( e.key === "-") { 4865zoom_out(); 4866return; 4867} 4868} 4869 4870if ( e.key === 'ArrowRight' || e.key === 'n') { 4871move_to_next_image(); 4872e.preventDefault(); 4873return; 4874} 4875if ( e.key === 'ArrowLeft' || e.key === 'p') { 4876move_to_prev_image(); 4877e.preventDefault(); 4878return; 4879} 4880 4881if ( e.key === 'ArrowUp' ) { 4882region_visualisation_update('region_label', '__via_region_id__', 1); 4883e.preventDefault(); 4884return; 4885} 4886 4887if ( e.key === 'ArrowDown' ) { 4888region_visualisation_update('region_color', '__via_default_region_color__', -1); 4889e.preventDefault(); 4890return; 4891} 4892 4893 4894if ( e.key === 'Home') { 4895show_first_image(); 4896e.preventDefault(); 4897return; 4898} 4899if ( e.key === 'End') { 4900show_last_image(); 4901e.preventDefault(); 4902return; 4903} 4904if ( e.key === 'PageDown') { 4905jump_to_next_image_block(); 4906e.preventDefault(); 4907return; 4908} 4909if ( e.key === 'PageUp') { 4910jump_to_prev_image_block(); 4911e.preventDefault(); 4912return; 4913} 4914 4915if ( e.key === 'a' ) { 4916if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 4917// select all in image grid 4918image_grid_group_toggle_select_all(); 4919} 4920} 4921 4922if ( e.key === 'Escape' ) { 4923e.preventDefault(); 4924if ( _via_is_loading_current_image ) { 4925_via_cancel_current_image_loading(); 4926} 4927 4928if ( _via_is_user_resizing_region ) { 4929// cancel region resizing action 4930_via_is_user_resizing_region = false; 4931} 4932 4933if ( _via_is_region_selected ) { 4934// clear all region selections 4935_via_is_region_selected = false; 4936_via_user_sel_region_id = -1; 4937toggle_all_regions_selection(false); 4938} 4939 4940if ( _via_is_user_drawing_polygon ) { 4941_via_is_user_drawing_polygon = false; 4942_via_canvas_regions.splice(_via_current_polygon_region_id, 1); 4943} 4944 4945if ( _via_is_user_drawing_region ) { 4946_via_is_user_drawing_region = false; 4947} 4948 4949if ( _via_is_user_resizing_region ) { 4950_via_is_user_resizing_region = false 4951} 4952 4953if ( _via_is_user_moving_region ) { 4954_via_is_user_moving_region = false 4955} 4956 4957_via_redraw_reg_canvas(); 4958return; 4959} 4960 4961if ( e.key === ' ' ) { // Space key 4962if ( e.ctrlKey ) { 4963annotation_editor_toggle_on_image_editor(); 4964} else { 4965annotation_editor_toggle_all_regions_editor(); 4966} 4967e.preventDefault(); 4968return; 4969} 4970 4971if ( e.key === 'F1' ) { // F1 for help 4972set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_GETTING_STARTED); 4973e.preventDefault(); 4974return; 4975} 4976if ( e.key === 'F2' ) { // F2 for about 4977set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_ABOUT); 4978e.preventDefault(); 4979return; 4980} 4981} 4982 4983function _via_reg_canvas_keyup_handler(e) { 4984if ( e.key === 'Control' ) { 4985_via_is_ctrl_pressed = false; 4986} 4987} 4988 4989function _via_reg_canvas_keydown_handler(e) { 4990if ( e.key === 'Control' ) { 4991_via_is_ctrl_pressed = true; 4992} 4993 4994if (_via_current_image_loaded) { 4995if ( e.key === 'Enter' ) { 4996if ( _via_current_shape === VIA_REGION_SHAPE.POLYLINE || 4997_via_current_shape === VIA_REGION_SHAPE.POLYGON) { 4998_via_polyshape_finish_drawing(); 4999} 5000} 5001if ( e.key === 'Backspace' ) { 5002if ( _via_current_shape === VIA_REGION_SHAPE.POLYLINE || 5003_via_current_shape === VIA_REGION_SHAPE.POLYGON) { 5004_via_polyshape_delete_last_vertex(); 5005} 5006} 5007 5008if ( e.key === 'a' ) { 5009sel_all_regions(); 5010e.preventDefault(); 5011return; 5012} 5013 5014if ( e.key === 'c' ) { 5015if (_via_is_region_selected || 5016_via_is_all_region_selected) { 5017copy_sel_regions(); 5018} 5019e.preventDefault(); 5020return; 5021} 5022 5023if ( e.key === 'v' ) { 5024paste_sel_regions_in_current_image(); 5025e.preventDefault(); 5026return; 5027} 5028 5029if ( e.key === 'b' ) { 5030toggle_region_boundary_visibility(); 5031e.preventDefault(); 5032return; 5033} 5034 5035if ( e.key === 'l' ) { 5036toggle_region_id_visibility(); 5037e.preventDefault(); 5038return; 5039} 5040 5041if ( e.key === 'd' ) { 5042if ( _via_is_region_selected || 5043_via_is_all_region_selected ) { 5044del_sel_regions(); 5045} 5046e.preventDefault(); 5047return; 5048} 5049 5050if ( _via_is_region_selected ) { 5051if ( e.key === 'ArrowRight' || 5052e.key === 'ArrowLeft' || 5053e.key === 'ArrowDown' || 5054e.key === 'ArrowUp' ) { 5055var del = 1; 5056if ( e.shiftKey ) { 5057del = 10; 5058} 5059var move_x = 0; 5060var move_y = 0; 5061switch( e.key ) { 5062case 'ArrowLeft': 5063move_x = -del; 5064break; 5065case 'ArrowUp': 5066move_y = -del; 5067break; 5068case 'ArrowRight': 5069move_x = del; 5070break; 5071case 'ArrowDown': 5072move_y = del; 5073break; 5074} 5075_via_move_selected_regions(move_x, move_y); 5076_via_redraw_reg_canvas(); 5077e.preventDefault(); 5078return; 5079} 5080} 5081} 5082_via_handle_global_keydown_event(e); 5083} 5084 5085function _via_polyshape_finish_drawing() { 5086if ( _via_is_user_drawing_polygon ) { 5087// double click is used to indicate completion of 5088// polygon or polyline drawing action 5089var new_region_id = _via_current_polygon_region_id; 5090var new_region_shape = _via_current_shape; 5091 5092var npts = _via_canvas_regions[new_region_id].shape_attributes['all_points_x'].length; 5093if ( npts <=2 && new_region_shape === VIA_REGION_SHAPE.POLYGON ) { 5094show_message('For a polygon, you must define at least 3 points. ' + 5095'Press [Esc] to cancel drawing operation.!'); 5096return; 5097} 5098if ( npts <=1 && new_region_shape === VIA_REGION_SHAPE.POLYLINE ) { 5099show_message('A polyline must have at least 2 points. ' + 5100'Press [Esc] to cancel drawing operation.!'); 5101return; 5102} 5103 5104var img_id = _via_image_id; 5105_via_current_polygon_region_id = -1; 5106_via_is_user_drawing_polygon = false; 5107_via_is_user_drawing_region = false; 5108 5109_via_img_metadata[img_id].regions[new_region_id] = {}; // create placeholder 5110_via_polyshape_add_new_polyshape(img_id, new_region_shape, new_region_id); 5111select_only_region(new_region_id); // select new region 5112set_region_annotations_to_default_value( new_region_id ); 5113annotation_editor_add_row( new_region_id ); 5114annotation_editor_scroll_to_row( new_region_id ); 5115 5116_via_redraw_reg_canvas(); 5117_via_reg_canvas.focus(); 5118} 5119return; 5120} 5121 5122function _via_polyshape_delete_last_vertex() { 5123if ( _via_is_user_drawing_polygon ) { 5124var npts = _via_canvas_regions[_via_current_polygon_region_id].shape_attributes['all_points_x'].length; 5125if ( npts > 0 ) { 5126_via_canvas_regions[_via_current_polygon_region_id].shape_attributes['all_points_x'].splice(npts - 1, 1); 5127_via_canvas_regions[_via_current_polygon_region_id].shape_attributes['all_points_y'].splice(npts - 1, 1); 5128 5129_via_redraw_reg_canvas(); 5130_via_reg_canvas.focus(); 5131} 5132} 5133} 5134 5135function _via_polyshape_add_new_polyshape(img_id, region_shape, region_id) { 5136// add all polygon points stored in _via_canvas_regions[] 5137var all_points_x = _via_canvas_regions[region_id].shape_attributes['all_points_x'].slice(0); 5138var all_points_y = _via_canvas_regions[region_id].shape_attributes['all_points_y'].slice(0); 5139 5140var canvas_all_points_x = []; 5141var canvas_all_points_y = []; 5142var n = all_points_x.length; 5143var i; 5144for ( i = 0; i < n; ++i ) { 5145all_points_x[i] = Math.round( all_points_x[i] * _via_canvas_scale ); 5146all_points_y[i] = Math.round( all_points_y[i] * _via_canvas_scale ); 5147 5148canvas_all_points_x[i] = Math.round( all_points_x[i] / _via_canvas_scale ); 5149canvas_all_points_y[i] = Math.round( all_points_y[i] / _via_canvas_scale ); 5150} 5151 5152var polygon_region = new file_region(); 5153polygon_region.shape_attributes['name'] = region_shape; 5154polygon_region.shape_attributes['all_points_x'] = all_points_x; 5155polygon_region.shape_attributes['all_points_y'] = all_points_y; 5156_via_img_metadata[img_id].regions[region_id] = polygon_region; 5157 5158// update canvas 5159if ( img_id === _via_image_id ) { 5160_via_canvas_regions[region_id].shape_attributes['name'] = region_shape; 5161_via_canvas_regions[region_id].shape_attributes['all_points_x'] = canvas_all_points_x; 5162_via_canvas_regions[region_id].shape_attributes['all_points_y'] = canvas_all_points_y; 5163} 5164} 5165 5166function del_sel_regions() { 5167if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5168return; 5169} 5170 5171if ( !_via_current_image_loaded ) { 5172show_message('First load some images!'); 5173return; 5174} 5175 5176var del_region_count = 0; 5177if ( _via_is_all_region_selected ) { 5178del_region_count = _via_canvas_regions.length; 5179_via_canvas_regions.splice(0); 5180_via_img_metadata[_via_image_id].regions.splice(0); 5181} else { 5182var sorted_sel_reg_id = []; 5183for ( var i = 0; i < _via_canvas_regions.length; ++i ) { 5184if ( _via_region_selected_flag[i] ) { 5185sorted_sel_reg_id.push(i); 5186_via_region_selected_flag[i] = false; 5187} 5188} 5189sorted_sel_reg_id.sort( function(a,b) { 5190return (b-a); 5191}); 5192for ( var i = 0; i < sorted_sel_reg_id.length; ++i ) { 5193_via_canvas_regions.splice( sorted_sel_reg_id[i], 1); 5194_via_img_metadata[_via_image_id].regions.splice( sorted_sel_reg_id[i], 1); 5195del_region_count += 1; 5196} 5197 5198if ( sorted_sel_reg_id.length ) { 5199_via_reg_canvas.style.cursor = "default"; 5200} 5201} 5202 5203_via_is_all_region_selected = false; 5204_via_is_region_selected = false; 5205_via_user_sel_region_id = -1; 5206 5207if ( _via_canvas_regions.length === 0 ) { 5208// all regions were deleted, hence clear region canvas 5209_via_clear_reg_canvas(); 5210} else { 5211_via_redraw_reg_canvas(); 5212} 5213_via_reg_canvas.focus(); 5214annotation_editor_show(); 5215 5216show_message('Deleted ' + del_region_count + ' selected regions'); 5217} 5218 5219function sel_all_regions() { 5220if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5221image_grid_group_toggle_select_all(); 5222return; 5223} 5224 5225if (!_via_current_image_loaded) { 5226show_message('First load some images!'); 5227return; 5228} 5229 5230toggle_all_regions_selection(true); 5231_via_is_all_region_selected = true; 5232_via_redraw_reg_canvas(); 5233} 5234 5235function copy_sel_regions() { 5236if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5237return; 5238} 5239 5240if (!_via_current_image_loaded) { 5241show_message('First load some images!'); 5242return; 5243} 5244 5245if (_via_is_region_selected || 5246_via_is_all_region_selected) { 5247_via_copied_image_regions.splice(0); 5248for ( var i = 0; i < _via_img_metadata[_via_image_id].regions.length; ++i ) { 5249var img_region = _via_img_metadata[_via_image_id].regions[i]; 5250var canvas_region = _via_canvas_regions[i]; 5251if ( _via_region_selected_flag[i] ) { 5252_via_copied_image_regions.push( clone_image_region(img_region) ); 5253} 5254} 5255show_message('Copied ' + _via_copied_image_regions.length + 5256' selected regions. Press Ctrl + v to paste'); 5257} else { 5258show_message('Select a region first!'); 5259} 5260} 5261 5262function paste_sel_regions_in_current_image() { 5263if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5264return; 5265} 5266 5267if ( !_via_current_image_loaded ) { 5268show_message('First load some images!'); 5269return; 5270} 5271 5272if ( _via_copied_image_regions.length ) { 5273var pasted_reg_count = 0; 5274for ( var i = 0; i < _via_copied_image_regions.length; ++i ) { 5275// ensure copied the regions are within this image's boundaries 5276var bbox = get_region_bounding_box( _via_copied_image_regions[i] ); 5277if (bbox[2] < _via_current_image_width && 5278bbox[3] < _via_current_image_height) { 5279var r = clone_image_region(_via_copied_image_regions[i]); 5280_via_img_metadata[_via_image_id].regions.push(r); 5281 5282pasted_reg_count += 1; 5283} 5284} 5285_via_load_canvas_regions(); 5286var discarded_reg_count = _via_copied_image_regions.length - pasted_reg_count; 5287show_message('Pasted ' + pasted_reg_count + ' regions. ' + 5288'Discarded ' + discarded_reg_count + ' regions exceeding image boundary.'); 5289_via_redraw_reg_canvas(); 5290_via_reg_canvas.focus(); 5291} else { 5292show_message('To paste a region, you first need to select a region and copy it!'); 5293} 5294} 5295 5296function paste_to_multiple_images_with_confirm() { 5297if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5298return; 5299} 5300 5301if ( _via_copied_image_regions.length === 0 ) { 5302show_message('First copy some regions!'); 5303return; 5304} 5305 5306var config = {'title':'Paste Regions to Multiple Images' }; 5307var input = { 'region_count': { type:'text', name:'Number of copied regions', value:_via_copied_image_regions.length, disabled:true }, 5308'prev_next_count':{ type:'text', name:'Copy to (count format)<br><span style="font-size:0.8rem">For example: to paste copied regions to the <i>previous 2 images</i> and <i>next 3 images</i>, type <strong>2,3</strong> in the textbox and to paste only in <i>next 5 images</i>, type <strong>0,5</strong></span>', placeholder:'2,3', disabled:false, size:30}, 5309'img_index_list':{ type:'text', name:'Copy to (image index list)<br><span style="font-size:0.8rem">For example: <strong>2-5,7,9</strong> pastes the copied regions to the images with the following id <i>2,3,4,5,7,9</i> and <strong>3,8,141</strong> pastes to the images with id <i>3,8 and 141</i></span>', placeholder:'2-5,7,9', disabled:false, size:30}, 5310'regex':{ type:'text', name:'Copy to filenames matching a regular expression<br><span style="font-size:0.8rem">For example: <strong>_large</strong> pastes the copied regions to all images whose filename contain the keyword <i>_large</i></span>', placeholder:'regular expression', disabled:false, size:30}, 5311'include_region_attributes':{ type:'checkbox', name:'Paste also the region annotations', checked:true}, 5312}; 5313 5314invoke_with_user_inputs(paste_to_multiple_images_confirmed, input, config); 5315} 5316 5317function paste_to_multiple_images_confirmed(input) { 5318// keep a copy of user inputs for the undo operation 5319_via_paste_to_multiple_images_input = input; 5320var intersect = generate_img_index_list(input); 5321var i; 5322var total_pasted_region_count = 0; 5323for ( i = 0; i < intersect.length; i++ ) { 5324total_pasted_region_count += paste_regions( intersect[i] ); 5325} 5326 5327show_message('Pasted [' + total_pasted_region_count + '] regions ' + 5328'in ' + intersect.length + ' images'); 5329 5330if ( intersect.includes(_via_image_index) ) { 5331_via_load_canvas_regions(); 5332_via_redraw_reg_canvas(); 5333_via_reg_canvas.focus(); 5334} 5335user_input_default_cancel_handler(); 5336} 5337 5338function paste_regions(img_index) { 5339var pasted_reg_count = 0; 5340if ( _via_copied_image_regions.length ) { 5341var img_id = _via_image_id_list[img_index]; 5342var i; 5343for ( i = 0; i < _via_copied_image_regions.length; ++i ) { 5344var r = clone_image_region(_via_copied_image_regions[i]); 5345_via_img_metadata[img_id].regions.push(r); 5346 5347pasted_reg_count += 1; 5348} 5349} 5350return pasted_reg_count; 5351} 5352 5353 5354function del_sel_regions_with_confirm() { 5355if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5356return; 5357} 5358 5359if ( _via_copied_image_regions.length === 0 ) { 5360show_message('First copy some regions!'); 5361return; 5362} 5363 5364var prev_next_count, img_index_list, regex; 5365if ( _via_paste_to_multiple_images_input ) { 5366prev_next_count = _via_paste_to_multiple_images_input.prev_next_count.value; 5367img_index_list = _via_paste_to_multiple_images_input.img_index_list.value; 5368regex = _via_paste_to_multiple_images_input.regex.value; 5369} 5370 5371var config = {'title':'Undo Regions Pasted to Multiple Images' }; 5372var input = { 'region_count': { type:'text', name:'Number of regions selected', value:_via_copied_image_regions.length, disabled:true }, 5373'prev_next_count':{ type:'text', name:'Delete from (count format)<br><span style="font-size:0.8rem">For example: to delete copied regions from the <i>previous 2 images</i> and <i>next 3 images</i>, type <strong>2,3</strong> in the textbox and to delete regions only in <i>next 5 images</i>, type <strong>0,5</strong></span>', placeholder:'2,3', disabled:false, size:30, value:prev_next_count}, 5374'img_index_list':{ type:'text', name:'Delete from (image index list)<br><span style="font-size:0.8rem">For example: <strong>2-5,7,9</strong> deletes the copied regions to the images with the following id <i>2,3,4,5,7,9</i> and <strong>3,8,141</strong> deletes regions from the images with id <i>3,8 and 141</i></span>', placeholder:'2-5,7,9', disabled:false, size:30, value:img_index_list}, 5375'regex':{ type:'text', name:'Delete from filenames matching a regular expression<br><span style="font-size:0.8rem">For example: <strong>_large</strong> deletes the copied regions from all images whose filename contain the keyword <i>_large</i></span>', placeholder:'regular expression', disabled:false, size:30, value:regex}, 5376}; 5377 5378invoke_with_user_inputs(del_sel_regions_confirmed, input, config); 5379} 5380 5381function del_sel_regions_confirmed(input) { 5382user_input_default_cancel_handler(); 5383var intersect = generate_img_index_list(input); 5384var i; 5385var total_deleted_region_count = 0; 5386for ( i = 0; i < intersect.length; i++ ) { 5387total_deleted_region_count += delete_regions( intersect[i] ); 5388} 5389 5390show_message('Deleted [' + total_deleted_region_count + '] regions ' + 5391'in ' + intersect.length + ' images'); 5392 5393if ( intersect.includes(_via_image_index) ) { 5394_via_load_canvas_regions(); 5395_via_redraw_reg_canvas(); 5396_via_reg_canvas.focus(); 5397} 5398} 5399 5400function delete_regions(img_index) { 5401var del_region_count = 0; 5402if ( _via_copied_image_regions.length ) { 5403var img_id = _via_image_id_list[img_index]; 5404var i; 5405for ( i = 0; i < _via_copied_image_regions.length; ++i ) { 5406var copied_region_shape_str = JSON.stringify(_via_copied_image_regions[i].shape_attributes); 5407var j; 5408// start from last region in order to delete the last pasted region 5409for ( j = _via_img_metadata[img_id].regions.length-1; j >= 0; --j ) { 5410if ( JSON.stringify(_via_img_metadata[img_id].regions[j].shape_attributes) === copied_region_shape_str ) { 5411_via_img_metadata[img_id].regions.splice( j, 1); 5412del_region_count += 1; 5413break; // delete only one matching region 5414} 5415} 5416} 5417} 5418return del_region_count; 5419} 5420 5421function show_first_image() { 5422if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5423if ( _via_image_grid_group_var.length ) { 5424image_grid_group_prev( { 'value':0 } ); // simulate button click 5425} else { 5426show_message('First, create groups by selecting items from "Group by" dropdown list'); 5427} 5428return; 5429} 5430 5431if (_via_img_count > 0) { 5432_via_show_img( _via_img_fn_list_img_index_list[0] ); 5433} 5434} 5435 5436function show_last_image() { 5437if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5438if ( _via_image_grid_group_var.length ) { 5439image_grid_group_prev( { 'value':_via_image_grid_group_var.length-1 } ); // simulate button click 5440} else { 5441show_message('First, create groups by selecting items from "Group by" dropdown list'); 5442} 5443return; 5444} 5445 5446if (_via_img_count > 0) { 5447var last_img_index = _via_img_fn_list_img_index_list.length - 1; 5448_via_show_img( _via_img_fn_list_img_index_list[ last_img_index ] ); 5449} 5450} 5451 5452function jump_image_block_get_count() { 5453var n = _via_img_fn_list_img_index_list.length; 5454if ( n < 20 ) { 5455return 2; 5456} 5457if ( n < 100 ) { 5458return 10; 5459} 5460if ( n < 1000 ) { 5461return 25; 5462} 5463if ( n < 5000 ) { 5464return 50; 5465} 5466if ( n < 10000 ) { 5467return 100; 5468} 5469if ( n < 50000 ) { 5470return 500; 5471} 5472 5473return Math.round( n / 50 ); 5474} 5475 5476function jump_to_next_image_block() { 5477if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5478return; 5479} 5480 5481var jump_count = jump_image_block_get_count(); 5482if ( jump_count > 1 ) { 5483var current_img_index = _via_image_index; 5484if ( _via_img_fn_list_img_index_list.includes( current_img_index ) ) { 5485var list_index = _via_img_fn_list_img_index_list.indexOf( current_img_index ); 5486var next_list_index = list_index + jump_count; 5487if ( (next_list_index + 1) > _via_img_fn_list_img_index_list.length ) { 5488next_list_index = 0; 5489} 5490var next_img_index = _via_img_fn_list_img_index_list[next_list_index]; 5491_via_show_img(next_img_index); 5492} 5493} else { 5494move_to_next_image(); 5495} 5496} 5497 5498function jump_to_prev_image_block() { 5499if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5500return; 5501} 5502 5503var jump_count = jump_image_block_get_count(); 5504if ( jump_count > 1 ) { 5505var current_img_index = _via_image_index; 5506if ( _via_img_fn_list_img_index_list.includes( current_img_index ) ) { 5507var list_index = _via_img_fn_list_img_index_list.indexOf( current_img_index ); 5508var prev_list_index = list_index - jump_count; 5509if ( prev_list_index < 0 ) { 5510prev_list_index = _via_img_fn_list_img_index_list.length - 1; 5511} 5512var prev_img_index = _via_img_fn_list_img_index_list[prev_list_index]; 5513_via_show_img(prev_img_index); 5514} 5515} else { 5516move_to_prev_image(); 5517} 5518} 5519 5520function move_to_prev_image() { 5521if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5522if ( _via_image_grid_group_var.length ) { 5523var last_group_index = _via_image_grid_group_var.length - 1; 5524image_grid_group_prev( { 'value':last_group_index } ); // simulate button click 5525} else { 5526show_message('First, create groups by selecting items from "Group by" dropdown list'); 5527} 5528return; 5529} 5530 5531if (_via_img_count > 0) { 5532var current_img_index = _via_image_index; 5533if ( _via_img_fn_list_img_index_list.includes( current_img_index ) ) { 5534var list_index = _via_img_fn_list_img_index_list.indexOf( current_img_index ); 5535var next_list_index = list_index - 1; 5536if ( next_list_index === -1 ) { 5537next_list_index = _via_img_fn_list_img_index_list.length - 1; 5538} 5539var next_img_index = _via_img_fn_list_img_index_list[next_list_index]; 5540_via_show_img(next_img_index); 5541} else { 5542if ( _via_img_fn_list_img_index_list.length === 0 ) { 5543show_message('Filtered file list does not any files!'); 5544} else { 5545_via_show_img( _via_img_fn_list_img_index_list[0] ); 5546} 5547} 5548 5549if (typeof _via_hook_prev_image === 'function') { 5550_via_hook_prev_image(current_img_index); 5551} 5552} 5553} 5554 5555function move_to_next_image() { 5556if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5557if ( _via_image_grid_group_var.length ) { 5558var last_group_index = _via_image_grid_group_var.length - 1; 5559image_grid_group_next( { 'value':last_group_index } ); // simulate button click 5560} else { 5561show_message('First, create groups by selecting items from "Group by" dropdown list'); 5562} 5563return; 5564} 5565 5566if (_via_img_count > 0) { 5567var current_img_index = _via_image_index; 5568if ( _via_img_fn_list_img_index_list.includes( current_img_index ) ) { 5569var list_index = _via_img_fn_list_img_index_list.indexOf( current_img_index ); 5570var next_list_index = list_index + 1; 5571if ( next_list_index === _via_img_fn_list_img_index_list.length ) { 5572next_list_index = 0; 5573} 5574var next_img_index = _via_img_fn_list_img_index_list[next_list_index]; 5575_via_show_img(next_img_index); 5576} else { 5577if ( _via_img_fn_list_img_index_list.length === 0 ) { 5578show_message('Filtered file list does not contain any files!'); 5579} else { 5580_via_show_img( _via_img_fn_list_img_index_list[0] ); 5581} 5582} 5583 5584if (typeof _via_hook_next_image === 'function') { 5585_via_hook_next_image(current_img_index); 5586} 5587} 5588} 5589 5590function set_zoom(zoom_level_index) { 5591if ( zoom_level_index === VIA_CANVAS_DEFAULT_ZOOM_LEVEL_INDEX ) { 5592_via_is_canvas_zoomed = false; 5593_via_canvas_zoom_level_index = VIA_CANVAS_DEFAULT_ZOOM_LEVEL_INDEX; 5594} else { 5595_via_is_canvas_zoomed = true; 5596_via_canvas_zoom_level_index = zoom_level_index; 5597} 5598 5599var zoom_scale = VIA_CANVAS_ZOOM_LEVELS[_via_canvas_zoom_level_index]; 5600set_all_canvas_scale(zoom_scale); 5601var canvas_w = ( _via_current_image.naturalWidth * zoom_scale ) / _via_canvas_scale_without_zoom; 5602var canvas_h = ( _via_current_image.naturalHeight * zoom_scale ) / _via_canvas_scale_without_zoom; 5603set_all_canvas_size(canvas_w, canvas_h); 5604_via_canvas_scale = _via_canvas_scale_without_zoom / zoom_scale; 5605_via_canvas_scale = _via_canvas_scale_without_zoom / zoom_scale; 5606 5607if ( zoom_scale === 1 ) { 5608VIA_REGION_POINT_RADIUS = VIA_REGION_POINT_RADIUS_DEFAULT; 5609} else { 5610if ( zoom_scale > 1 ) { 5611VIA_REGION_POINT_RADIUS = VIA_REGION_POINT_RADIUS_DEFAULT * zoom_scale; 5612} 5613} 5614 5615_via_load_canvas_regions(); // image to canvas space transform 5616_via_redraw_reg_canvas(); 5617_via_reg_canvas.focus(); 5618update_vertical_space(); 5619} 5620 5621function reset_zoom_level() { 5622if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5623image_grid_image_size_reset(); 5624show_message('Zoom reset'); 5625return; 5626} 5627 5628if (!_via_current_image_loaded) { 5629show_message('First load some images!'); 5630return; 5631} 5632 5633if (_via_is_canvas_zoomed) { 5634set_zoom(VIA_CANVAS_DEFAULT_ZOOM_LEVEL_INDEX); 5635show_message('Zoom reset'); 5636} else { 5637show_message('Cannot reset zoom because image zoom has not been applied!'); 5638} 5639update_vertical_space(); 5640} 5641 5642function zoom_in() { 5643if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5644image_grid_image_size_increase(); 5645show_message('Increased size of images shown in image grid'); 5646return; 5647} 5648 5649if (!_via_current_image_loaded) { 5650show_message('First load some images!'); 5651return; 5652} 5653 5654if ( _via_is_user_drawing_polygon || _via_is_user_drawing_region ) { 5655return; 5656} 5657 5658if (_via_canvas_zoom_level_index === (VIA_CANVAS_ZOOM_LEVELS.length-1)) { 5659show_message('Further zoom-in not possible'); 5660} else { 5661var new_zoom_level_index = _via_canvas_zoom_level_index + 1; 5662set_zoom( new_zoom_level_index ); 5663show_message('Zoomed in to level ' + VIA_CANVAS_ZOOM_LEVELS[_via_canvas_zoom_level_index] + 'X'); 5664} 5665} 5666 5667function zoom_out() { 5668if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5669image_grid_image_size_decrease(); 5670show_message('Reduced size of images shown in image grid'); 5671return; 5672} 5673 5674if (!_via_current_image_loaded) { 5675show_message('First load some images!'); 5676return; 5677} 5678 5679if ( _via_is_user_drawing_polygon || _via_is_user_drawing_region ) { 5680return; 5681} 5682 5683if (_via_canvas_zoom_level_index === 0) { 5684show_message('Further zoom-out not possible'); 5685} else { 5686var new_zoom_level_index = _via_canvas_zoom_level_index - 1; 5687set_zoom( new_zoom_level_index ); 5688show_message('Zoomed out to level ' + VIA_CANVAS_ZOOM_LEVELS[_via_canvas_zoom_level_index] + 'X'); 5689} 5690} 5691 5692function toggle_region_boundary_visibility() { 5693if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE ) { 5694_via_is_region_boundary_visible = !_via_is_region_boundary_visible; 5695_via_redraw_reg_canvas(); 5696_via_reg_canvas.focus(); 5697} 5698 5699if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 5700if ( _via_settings.ui.image_grid.show_region_shape ) { 5701_via_settings.ui.image_grid.show_region_shape = false; 5702document.getElementById('image_grid_content_rshape').innerHTML = ''; 5703} else { 5704_via_settings.ui.image_grid.show_region_shape = true; 5705image_grid_page_show_all_regions(); 5706} 5707} 5708} 5709 5710function toggle_region_id_visibility() { 5711_via_is_region_id_visible = !_via_is_region_id_visible; 5712_via_redraw_reg_canvas(); 5713_via_reg_canvas.focus(); 5714} 5715 5716function toggle_region_info_visibility() { 5717var elem = document.getElementById('region_info'); 5718// toggle between displaying and not displaying 5719if ( elem.classList.contains('display_none') ) { 5720elem.classList.remove('display_none'); 5721_via_is_region_info_visible = true; 5722} else { 5723elem.classList.add('display_none'); 5724_via_is_region_info_visible = false; 5725} 5726} 5727 5728// 5729// Mouse wheel event listener 5730// 5731function _via_reg_canvas_mouse_wheel_listener(e) { 5732if (!_via_current_image_loaded) { 5733return; 5734} 5735 5736if ( e.ctrlKey ) { 5737// perform zoom 5738if (e.deltaY < 0) { 5739zoom_in(); 5740} else { 5741zoom_out(); 5742} 5743e.preventDefault(); 5744} 5745} 5746 5747function region_visualisation_update(type, default_id, next_offset) { 5748var attr_list = [ default_id ]; 5749attr_list = attr_list.concat(Object.keys(_via_attributes['region'])); 5750var n = attr_list.length; 5751var current_index = attr_list.indexOf(_via_settings.ui.image[type]); 5752var new_index; 5753if ( current_index !== -1 ) { 5754new_index = current_index + next_offset; 5755 5756if ( new_index < 0 ) { 5757new_index = n + new_index; 5758} 5759if ( new_index >= n ) { 5760new_index = new_index - n; 5761} 5762switch(type) { 5763case 'region_label': 5764_via_settings.ui.image.region_label = attr_list[new_index]; 5765_via_redraw_reg_canvas(); 5766break; 5767case 'region_color': 5768_via_settings.ui.image.region_color = attr_list[new_index]; 5769_via_regions_group_color_init(); 5770_via_redraw_reg_canvas(); 5771} 5772 5773var type_str = type.replace('_', ' '); 5774if ( _via_settings.ui.image[type].startsWith('__via') ) { 5775show_message(type_str + ' cleared'); 5776} else { 5777show_message(type_str + ' set to region attribute [' + _via_settings.ui.image[type] + ']'); 5778} 5779} 5780} 5781 5782// 5783// left sidebar toolbox maintainer 5784// 5785function leftsidebar_toggle() { 5786var leftsidebar = document.getElementById('leftsidebar'); 5787if ( leftsidebar.style.display === 'none' ) { 5788leftsidebar.style.display = 'table-cell'; 5789document.getElementById('leftsidebar_collapse_panel').style.display = 'none'; 5790} else { 5791leftsidebar.style.display = 'none'; 5792document.getElementById('leftsidebar_collapse_panel').style.display = 'table-cell'; 5793} 5794_via_update_ui_components(); 5795} 5796 5797function leftsidebar_increase_width() { 5798var leftsidebar = document.getElementById('leftsidebar'); 5799var new_width = _via_settings.ui.leftsidebar_width + VIA_LEFTSIDEBAR_WIDTH_CHANGE; 5800leftsidebar.style.width = new_width + 'rem'; 5801_via_settings.ui.leftsidebar_width = new_width; 5802if ( _via_current_image_loaded ) { 5803_via_show_img(_via_image_index); 5804} 5805} 5806 5807function leftsidebar_decrease_width() { 5808var leftsidebar = document.getElementById('leftsidebar'); 5809var new_width = _via_settings.ui.leftsidebar_width - VIA_LEFTSIDEBAR_WIDTH_CHANGE; 5810if ( new_width >= 5 ) { 5811leftsidebar.style.width = new_width + 'rem'; 5812_via_settings.ui.leftsidebar_width = new_width; 5813if ( _via_current_image_loaded ) { 5814_via_show_img(_via_image_index); 5815} 5816} 5817} 5818 5819function leftsidebar_show() { 5820var leftsidebar = document.getElementById('leftsidebar'); 5821leftsidebar.style.display = 'table-cell'; 5822document.getElementById('leftsidebar_collapse_panel').style.display = 'none'; 5823} 5824 5825// source: https://www.w3schools.com/howto/howto_js_accordion.asp 5826function init_leftsidebar_accordion() { 5827var leftsidebar = document.getElementById('leftsidebar'); 5828leftsidebar.style.width = _via_settings.ui.leftsidebar_width + 'rem'; 5829 5830var acc = document.getElementsByClassName('leftsidebar_accordion'); 5831var i; 5832for ( i = 0; i < acc.length; ++i ) { 5833acc[i].addEventListener('click', function() { 5834update_vertical_space(); 5835this.classList.toggle('active'); 5836this.nextElementSibling.classList.toggle('show'); 5837 5838switch( this.innerHTML ) { 5839case 'Attributes': 5840update_attributes_update_panel(); 5841break; 5842case 'Project': 5843update_img_fn_list(); 5844break; 5845} 5846}); 5847} 5848} 5849 5850// 5851// image filename list shown in leftsidebar panel 5852// 5853function is_img_fn_list_visible() { 5854return img_fn_list_panel.classList.contains('show'); 5855} 5856 5857function img_loading_spinbar(image_index, show) { 5858var panel = document.getElementById('project_panel_title'); 5859if ( show ) { 5860panel.innerHTML = 'Project <span style="margin-left:1rem;" class="loading_spinbox"></span>'; 5861} else { 5862panel.innerHTML = 'Project'; 5863} 5864} 5865 5866function update_img_fn_list() { 5867var regex = document.getElementById('img_fn_list_regex').value; 5868var p = document.getElementById('filelist_preset_filters_list'); 5869if ( regex === '' || regex === null ) { 5870if ( p.selectedIndex === 0 ) { 5871// show all files 5872_via_img_fn_list_html = []; 5873_via_img_fn_list_img_index_list = []; 5874_via_img_fn_list_html.push('<ul>'); 5875for ( var i=0; i < _via_image_filename_list.length; ++i ) { 5876_via_img_fn_list_html.push( img_fn_list_ith_entry_html(i) ); 5877_via_img_fn_list_img_index_list.push(i); 5878} 5879_via_img_fn_list_html.push('</ul>'); 5880img_fn_list.innerHTML = _via_img_fn_list_html.join(''); 5881img_fn_list_scroll_to_current_file(); 5882} else { 5883// filter according to preset filters 5884img_fn_list_onpresetfilter_select(); 5885} 5886} else { 5887if ( p.selectedIndex === 6 ) { 5888// Filter By Region Attribute 5889filter_selected_region_attribute(regex); 5890} else { 5891// RegExp 5892img_fn_list_generate_html(regex); 5893img_fn_list.innerHTML = _via_img_fn_list_html.join(''); 5894img_fn_list_scroll_to_current_file(); 5895} 5896} 5897} 5898 5899function img_fn_list_onregex() { 5900var regex = document.getElementById('img_fn_list_regex').value; 5901var p = document.getElementById('filelist_preset_filters_list'); 5902 5903if ( p.selectedIndex === 6 ) { 5904filter_selected_region_attribute(regex); 5905return; 5906} 5907 5908img_fn_list_generate_html( regex ); 5909img_fn_list.innerHTML = _via_img_fn_list_html.join(''); 5910img_fn_list_scroll_to_current_file(); 5911 5912// select 'regex' in the predefined filter list ( Unless Filter By Attribute is Selected) 5913if ( regex === '' ) { 5914p.selectedIndex = 0; 5915} else { 5916if ( p.options[p.selectedIndex].value != 'files_region_attribute' && 5917p.options[p.selectedIndex].value != 'regex' ) { 5918var i; 5919for ( i=0; i<p.options.length; ++i) { 5920if ( p.options[i].value === 'regex' ) { 5921p.selectedIndex = i; 5922break; 5923} 5924} 5925} 5926} 5927} 5928 5929function img_fn_list_onpresetfilter_select() { 5930var p = document.getElementById('filelist_preset_filters_list'); 5931var filter = p.options[p.selectedIndex].value; 5932switch(filter) { 5933case 'all': 5934document.getElementById('img_fn_list_regex').value = ''; 5935img_fn_list_generate_html(); 5936img_fn_list.innerHTML = _via_img_fn_list_html.join(''); 5937img_fn_list_scroll_to_current_file(); 5938break; 5939case 'regex': 5940img_fn_list_onregex(); 5941document.getElementById('img_fn_list_regex').focus(); 5942break; 5943case 'files_region_attribute': 5944img_fn_list_onregex(); 5945document.getElementById('img_fn_list_regex').focus(); 5946break; 5947default: 5948_via_img_fn_list_html = []; 5949_via_img_fn_list_img_index_list = []; 5950_via_img_fn_list_html.push('<ul>'); 5951var i; 5952for ( i=0; i < _via_image_filename_list.length; ++i ) { 5953var img_id = _via_image_id_list[i]; 5954var add_to_list = false; 5955switch(filter) { 5956case 'files_without_region': 5957if ( _via_img_metadata[img_id].regions.length === 0 ) { 5958add_to_list = true; 5959} 5960break; 5961case 'files_missing_region_annotations': 5962if ( is_region_annotation_missing(img_id) ) { 5963add_to_list = true; 5964} 5965break; 5966case 'files_missing_file_annotations': 5967if ( is_file_annotation_missing(img_id) ) { 5968add_to_list = true; 5969} 5970break; 5971case 'files_error_loading': 5972if ( _via_image_load_error[i] === true ) { 5973add_to_list = true; 5974} 5975} 5976if ( add_to_list ) { 5977_via_img_fn_list_html.push( img_fn_list_ith_entry_html(i) ); 5978_via_img_fn_list_img_index_list.push(i); 5979} 5980} 5981_via_img_fn_list_html.push('</ul>'); 5982img_fn_list.innerHTML = _via_img_fn_list_html.join(''); 5983img_fn_list_scroll_to_current_file(); 5984break; 5985} 5986} 5987 5988function filter_selected_region_attribute(property_entry) { 5989const propArray = property_entry.split("="); 5990if ( propArray.length != 2 || propArray[0] === "" || propArray[1] === "") return; 5991var selected_attribute = propArray[0]; 5992var selected_value = propArray[1]; 5993if ( selected_value === "\"\"") selected_value = ""; 5994 5995_via_img_fn_list_html = []; 5996_via_img_fn_list_img_index_list = []; 5997_via_img_fn_list_html.push('<ul>'); 5998 5999if ( _via_attributes.region.hasOwnProperty(selected_attribute) ) { 6000var i; 6001 6002for ( i=0; i < _via_image_filename_list.length; ++i ) { 6003var img_id = _via_image_id_list[i]; 6004var add_to_list = false; 6005var idx; 6006 6007for ( idx = 0; idx < _via_img_metadata[img_id].regions.length; ++idx ) { 6008if ( _via_img_metadata[img_id].regions[idx].region_attributes.hasOwnProperty(selected_attribute) ) { 6009if ( JSON.stringify(_via_img_metadata[img_id].regions[idx].region_attributes[selected_attribute]) === JSON.stringify(selected_value) ) { 6010add_to_list = true; 6011_via_img_fn_list_html.push( img_fn_list_ith_entry_html(i) ); 6012_via_img_fn_list_img_index_list.push(i); 6013break; 6014} 6015} 6016} 6017} 6018} 6019_via_img_fn_list_html.push('</ul>'); 6020img_fn_list.innerHTML = _via_img_fn_list_html.join(''); 6021img_fn_list_scroll_to_current_file(); 6022} 6023 6024function is_region_annotation_missing(img_id) { 6025var region_attribute; 6026var i; 6027for ( i = 0; i < _via_img_metadata[img_id].regions.length; ++i ) { 6028for ( region_attribute in _via_attributes['region'] ) { 6029if ( _via_img_metadata[img_id].regions[i].region_attributes.hasOwnProperty(region_attribute) ) { 6030if ( _via_img_metadata[img_id].regions[i].region_attributes[region_attribute] === '' ) { 6031return true; 6032} 6033} else { 6034return true; 6035} 6036} 6037} 6038return false; 6039} 6040 6041function is_file_annotation_missing(img_id) { 6042var file_attribute; 6043for ( file_attribute in _via_attributes['file'] ) { 6044if ( _via_img_metadata[img_id].file_attributes.hasOwnProperty(file_attribute) ) { 6045if ( _via_img_metadata[img_id].file_attributes[file_attribute] === '' ) { 6046return true; 6047} 6048} else { 6049return true; 6050} 6051} 6052return false; 6053} 6054 6055function img_fn_list_ith_entry_selected(img_index, is_selected) { 6056if ( is_selected ) { 6057img_fn_list_ith_entry_add_css_class(img_index, 'sel'); 6058} else { 6059img_fn_list_ith_entry_remove_css_class(img_index, 'sel'); 6060} 6061} 6062 6063function img_fn_list_ith_entry_error(img_index, is_error) { 6064if ( is_error ) { 6065img_fn_list_ith_entry_add_css_class(img_index, 'error'); 6066} else { 6067img_fn_list_ith_entry_remove_css_class(img_index, 'error'); 6068} 6069} 6070 6071function img_fn_list_ith_entry_add_css_class(img_index, classname) { 6072var li = document.getElementById('fl' + img_index); 6073if ( li && ! li.classList.contains(classname) ) { 6074li.classList.add(classname); 6075} 6076} 6077 6078function img_fn_list_ith_entry_remove_css_class(img_index, classname) { 6079var li = document.getElementById('fl' + img_index); 6080if ( li && li.classList.contains(classname) ) { 6081li.classList.remove(classname); 6082} 6083} 6084 6085function img_fn_list_clear_all_style() { 6086var cn = document.getElementById('img_fn_list').childNodes[0].childNodes; 6087var i, j; 6088var n = cn.length; 6089var nclass; 6090for ( i = 0; i < n; ++i ) { 6091//cn[i].classList = []; // throws error in Edge browser 6092nclass = cn[i].classList.length; 6093if ( nclass ) { 6094for ( j = 0; j < nclass; ++j ) { 6095cn[i].classList.remove( cn[i].classList.item(j) ); 6096} 6097} 6098} 6099} 6100 6101function img_fn_list_clear_css_classname(classname) { 6102var cn = document.getElementById('img_fn_list').childNodes[0].childNodes; 6103var i; 6104var n = cn.length; 6105for ( i = 0; i < n; ++i ) { 6106if ( cn[i].classList.contains(classname) ) { 6107cn[i].classList.remove(classname); 6108} 6109} 6110} 6111 6112function img_fn_list_ith_entry_html(i) { 6113var htmli = ''; 6114var filename = _via_image_filename_list[i]; 6115if ( is_url(filename) ) { 6116filename = filename.substr(0,4) + '...' + get_filename_from_url(filename); 6117} 6118 6119htmli += '<li id="fl' + i + '"'; 6120if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 6121if ( _via_image_grid_page_img_index_list.includes(i) ) { 6122// highlight images being shown in image grid 6123htmli += ' class="sel"'; 6124} 6125 6126} else { 6127if ( i === _via_image_index ) { 6128// highlight the current entry 6129htmli += ' class="sel"'; 6130} 6131} 6132htmli += ' onclick="jump_to_image(' + (i) + ')" title="' + _via_image_filename_list[i] + '">[' + (i+1) + '] ' + decodeURIComponent(filename) + '</li>'; 6133return htmli; 6134} 6135 6136function img_fn_list_generate_html(regex) { 6137_via_img_fn_list_html = []; 6138_via_img_fn_list_img_index_list = []; 6139_via_img_fn_list_html.push('<ul>'); 6140for ( var i=0; i < _via_image_filename_list.length; ++i ) { 6141var filename = _via_image_filename_list[i]; 6142if ( filename.match(regex) !== null ) { 6143_via_img_fn_list_html.push( img_fn_list_ith_entry_html(i) ); 6144_via_img_fn_list_img_index_list.push(i); 6145} 6146} 6147_via_img_fn_list_html.push('</ul>'); 6148} 6149 6150function img_fn_list_scroll_to_current_file() { 6151img_fn_list_scroll_to_file( _via_image_index ); 6152} 6153 6154function img_fn_list_scroll_to_file(file_index) { 6155if( _via_img_fn_list_img_index_list.includes(file_index) ) { 6156var sel_file = document.getElementById( 'fl' + file_index ); 6157var panel_height = img_fn_list.clientHeight - 20; 6158var window_top = img_fn_list.scrollTop; 6159var window_bottom = img_fn_list.scrollTop + panel_height 6160if ( sel_file.offsetTop > window_top ) { 6161if ( sel_file.offsetTop > window_bottom ) { 6162img_fn_list.scrollTop = sel_file.offsetTop; 6163} 6164} else { 6165img_fn_list.scrollTop = sel_file.offsetTop - panel_height; 6166} 6167} 6168} 6169 6170function toggle_img_fn_list_visibility() { 6171leftsidebar_show(); 6172document.getElementById('img_fn_list_panel').classList.toggle('show'); 6173document.getElementById('project_panel_title').classList.toggle('active'); 6174} 6175 6176function toggle_attributes_editor() { 6177leftsidebar_show(); 6178document.getElementById('attributes_editor_panel').classList.toggle('show'); 6179document.getElementById('attributes_editor_panel_title').classList.toggle('active'); 6180} 6181 6182// this vertical spacer is needed to allow scrollbar to show 6183// items like Keyboard Shortcut hidden under the attributes panel 6184function update_vertical_space() { 6185var panel = document.getElementById('vertical_space'); 6186var aepanel = document.getElementById('annotation_editor_panel'); 6187panel.style.height = (aepanel.offsetHeight + 40) + 'px'; 6188} 6189 6190// 6191// region and file attributes update panel 6192// 6193function attribute_update_panel_set_active_button() { 6194var attribute_type; 6195for ( attribute_type in _via_attributes ) { 6196var bid = 'button_show_' + attribute_type + '_attributes'; 6197document.getElementById(bid).classList.remove('active'); 6198} 6199var bid = 'button_show_' + _via_attribute_being_updated + '_attributes'; 6200document.getElementById(bid).classList.add('active'); 6201} 6202 6203function show_region_attributes_update_panel() { 6204_via_attribute_being_updated = 'region'; 6205var rattr_list = Object.keys(_via_attributes['region']); 6206if ( rattr_list.length ) { 6207_via_current_attribute_id = rattr_list[0]; 6208} else { 6209_via_current_attribute_id = ''; 6210} 6211update_attributes_update_panel(); 6212attribute_update_panel_set_active_button(); 6213 6214} 6215 6216function show_file_attributes_update_panel() { 6217_via_attribute_being_updated = 'file'; 6218var fattr_list = Object.keys(_via_attributes['file']); 6219if ( fattr_list.length ) { 6220_via_current_attribute_id = fattr_list[0]; 6221} else { 6222_via_current_attribute_id = ''; 6223} 6224update_attributes_update_panel(); 6225attribute_update_panel_set_active_button(); 6226} 6227 6228function update_attributes_name_list() { 6229var p = document.getElementById('attributes_name_list'); 6230p.innerHTML = ''; 6231 6232var attr; 6233for ( attr in _via_attributes[_via_attribute_being_updated] ) { 6234var option = document.createElement('option'); 6235option.setAttribute('value', attr) 6236option.innerHTML = attr; 6237if ( attr === _via_current_attribute_id ) { 6238option.setAttribute('selected', 'selected'); 6239} 6240p.appendChild(option); 6241} 6242} 6243 6244function update_attributes_update_panel() { 6245if ( document.getElementById('attributes_editor_panel').classList.contains('show') ) { 6246update_attributes_name_list(); 6247show_attribute_properties(); 6248show_attribute_options(); 6249} 6250} 6251 6252function update_attribute_properties_panel() { 6253if ( document.getElementById('attributes_editor_panel').classList.contains('show') ) { 6254show_attribute_properties(); 6255show_attribute_options(); 6256} 6257} 6258 6259function show_attribute_properties() { 6260var attr_list = document.getElementById('attributes_name_list'); 6261document.getElementById('attribute_properties').innerHTML = ''; 6262 6263if ( attr_list.options.length === 0 ) { 6264return; 6265} 6266 6267if ( typeof(_via_current_attribute_id) === 'undefined' || _via_current_attribute_id === '' ) { 6268_via_current_attribute_id = attr_list.options[0].value; 6269} 6270 6271var attr_id = _via_current_attribute_id; 6272var attr_type = _via_attribute_being_updated; 6273var attr_input_type = _via_attributes[attr_type][attr_id].type; 6274var attr_desc = _via_attributes[attr_type][attr_id].description; 6275 6276attribute_property_add_input_property('Name of attribute (appears in exported annotations)', 6277'Name', 6278attr_id, 6279'attribute_name'); 6280attribute_property_add_input_property('Description of attribute (shown to user during annotation session)', 6281'Desc.', 6282attr_desc, 6283'attribute_description'); 6284 6285if ( attr_input_type === 'text' ) { 6286var attr_default_value = _via_attributes[attr_type][attr_id].default_value; 6287attribute_property_add_input_property('Default value of this attribute', 6288'Def.', 6289attr_default_value, 6290'attribute_default_value'); 6291} 6292 6293// add dropdown for type of attribute 6294var p = document.createElement('div'); 6295p.setAttribute('class', 'property'); 6296var c0 = document.createElement('span'); 6297c0.setAttribute('title', 'Attribute type (e.g. text, checkbox, radio, etc)'); 6298c0.innerHTML = 'Type'; 6299var c1 = document.createElement('span'); 6300var c1b = document.createElement('select'); 6301c1b.setAttribute('onchange', 'attribute_property_on_update(this)'); 6302c1b.setAttribute('id', 'attribute_type'); 6303var type_id; 6304for ( type_id in VIA_ATTRIBUTE_TYPE ) { 6305var type = VIA_ATTRIBUTE_TYPE[type_id]; 6306var option = document.createElement('option'); 6307option.setAttribute('value', type); 6308option.innerHTML = type; 6309if ( attr_input_type == type ) { 6310option.setAttribute('selected', 'selected'); 6311} 6312c1b.appendChild(option); 6313} 6314c1.appendChild(c1b); 6315p.appendChild(c0); 6316p.appendChild(c1); 6317document.getElementById('attribute_properties').appendChild(p); 6318} 6319 6320function show_attribute_options() { 6321var attr_list = document.getElementById('attributes_name_list'); 6322document.getElementById('attribute_options').innerHTML = ''; 6323if ( attr_list.options.length === 0 ) { 6324return; 6325} 6326 6327var attr_id = attr_list.value; 6328var attr_type = _via_attributes[_via_attribute_being_updated][attr_id].type; 6329 6330// populate additional options based on attribute type 6331switch( attr_type ) { 6332case VIA_ATTRIBUTE_TYPE.TEXT: 6333// text does not have any additional properties 6334break; 6335case VIA_ATTRIBUTE_TYPE.IMAGE: 6336var p = document.createElement('div'); 6337p.setAttribute('class', 'property'); 6338p.setAttribute('style', 'text-align:center'); 6339var c0 = document.createElement('span'); 6340c0.setAttribute('style', 'width:25%'); 6341c0.setAttribute('title', 'When selected, this is the value that appears in exported annotations'); 6342c0.innerHTML = 'id'; 6343var c1 = document.createElement('span'); 6344c1.setAttribute('style', 'width:60%'); 6345c1.setAttribute('title', 'URL or base64 (see https://www.base64-image.de/) encoded image data that corresponds to the image shown as an option to the annotator'); 6346c1.innerHTML = 'image url or b64'; 6347var c2 = document.createElement('span'); 6348c2.setAttribute('title', 'The default value of this attribute'); 6349c2.innerHTML = 'def.'; 6350p.appendChild(c0); 6351p.appendChild(c1); 6352p.appendChild(c2); 6353document.getElementById('attribute_options').appendChild(p); 6354 6355var options = _via_attributes[_via_attribute_being_updated][attr_id].options; 6356var option_id; 6357for ( option_id in options ) { 6358var option_desc = options[option_id]; 6359 6360var option_default = _via_attributes[_via_attribute_being_updated][attr_id].default_options[option_id]; 6361attribute_property_add_option(attr_id, option_id, option_desc, option_default, attr_type); 6362} 6363attribute_property_add_new_entry_option(attr_id, attr_type); 6364break; 6365case VIA_ATTRIBUTE_TYPE.CHECKBOX: // handled by next case 6366case VIA_ATTRIBUTE_TYPE.DROPDOWN: // handled by next case 6367case VIA_ATTRIBUTE_TYPE.RADIO: 6368var p = document.createElement('div'); 6369p.setAttribute('class', 'property'); 6370p.setAttribute('style', 'text-align:center'); 6371var c0 = document.createElement('span'); 6372c0.setAttribute('style', 'width:25%'); 6373c0.setAttribute('title', 'When selected, this is the value that appears in exported annotations'); 6374c0.innerHTML = 'id'; 6375var c1 = document.createElement('span'); 6376c1.setAttribute('style', 'width:60%'); 6377c1.setAttribute('title', 'This is the text shown as an option to the annotator'); 6378c1.innerHTML = 'description'; 6379var c2 = document.createElement('span'); 6380c2.setAttribute('title', 'The default value of this attribute'); 6381c2.innerHTML = 'def.'; 6382p.appendChild(c0); 6383p.appendChild(c1); 6384p.appendChild(c2); 6385document.getElementById('attribute_options').appendChild(p); 6386 6387var options = _via_attributes[_via_attribute_being_updated][attr_id].options; 6388var option_id; 6389for ( option_id in options ) { 6390var option_desc = options[option_id]; 6391 6392var option_default = _via_attributes[_via_attribute_being_updated][attr_id].default_options[option_id]; 6393attribute_property_add_option(attr_id, option_id, option_desc, option_default, attr_type); 6394} 6395attribute_property_add_new_entry_option(attr_id, attr_type); 6396break; 6397default: 6398console.log('Attribute type ' + attr_type + ' is unavailable'); 6399} 6400} 6401 6402function attribute_property_add_input_property(title, name, value, id) { 6403var p = document.createElement('div'); 6404p.setAttribute('class', 'property'); 6405var c0 = document.createElement('span'); 6406c0.setAttribute('title', title); 6407c0.innerHTML = name; 6408var c1 = document.createElement('span'); 6409var c1b = document.createElement('input'); 6410c1b.setAttribute('onchange', 'attribute_property_on_update(this)'); 6411if ( typeof(value) !== 'undefined' ) { 6412c1b.setAttribute('value', value); 6413} 6414c1b.setAttribute('id', id); 6415c1.appendChild(c1b); 6416p.appendChild(c0); 6417p.appendChild(c1); 6418 6419document.getElementById('attribute_properties').appendChild(p); 6420} 6421 6422function attribute_property_add_option(attr_id, option_id, option_desc, option_default, attribute_type) { 6423var p = document.createElement('div'); 6424p.setAttribute('class', 'property'); 6425var c0 = document.createElement('span'); 6426var c0b = document.createElement('input'); 6427c0b.setAttribute('type', 'text'); 6428c0b.setAttribute('value', option_id); 6429c0b.setAttribute('title', option_id); 6430c0b.setAttribute('onchange', 'attribute_property_on_option_update(this)'); 6431c0b.setAttribute('id', '_via_attribute_option_id_' + option_id); 6432 6433var c1 = document.createElement('span'); 6434var c1b = document.createElement('input'); 6435c1b.setAttribute('type', 'text'); 6436 6437if ( attribute_type === VIA_ATTRIBUTE_TYPE.IMAGE ) { 6438var option_desc_info = option_desc.length + ' bytes of base64 image data'; 6439c1b.setAttribute('value', option_desc_info); 6440c1b.setAttribute('title', 'To update, copy and paste base64 image data in this text box'); 6441} else { 6442c1b.setAttribute('value', option_desc); 6443c1b.setAttribute('title', option_desc); 6444} 6445c1b.setAttribute('onchange', 'attribute_property_on_option_update(this)'); 6446c1b.setAttribute('id', '_via_attribute_option_description_' + option_id); 6447 6448var c2 = document.createElement('span'); 6449var c2b = document.createElement('input'); 6450c2b.setAttribute('type', attribute_type); 6451if ( typeof option_default !== 'undefined' ) { 6452c2b.checked = option_default; 6453} 6454if ( attribute_type === 'radio' || attribute_type === 'image' || attribute_type === 'dropdown' ) { 6455// ensured that user can activate only one radio button 6456c2b.setAttribute('type', 'radio'); 6457c2b.setAttribute('name', attr_id); 6458} 6459 6460c2b.setAttribute('onchange', 'attribute_property_on_option_update(this)'); 6461c2b.setAttribute('id', '_via_attribute_option_default_' + option_id); 6462 6463c0.appendChild(c0b); 6464c1.appendChild(c1b); 6465c2.appendChild(c2b); 6466p.appendChild(c0); 6467p.appendChild(c1); 6468p.appendChild(c2); 6469 6470document.getElementById('attribute_options').appendChild(p); 6471} 6472 6473function attribute_property_add_new_entry_option(attr_id, attribute_type) { 6474var p = document.createElement('div'); 6475p.setAttribute('class', 'new_option_id_entry'); 6476var c0b = document.createElement('input'); 6477c0b.setAttribute('type', 'text'); 6478c0b.setAttribute('onchange', 'attribute_property_on_option_add(this)'); 6479c0b.setAttribute('id', '_via_attribute_new_option_id'); 6480c0b.setAttribute('placeholder', 'Add new option id'); 6481p.appendChild(c0b); 6482document.getElementById('attribute_options').appendChild(p); 6483} 6484 6485function attribute_property_on_update(p) { 6486var attr_id = get_current_attribute_id(); 6487var attr_type = _via_attribute_being_updated; 6488var new_attr_type = p.value; 6489 6490switch(p.id) { 6491case 'attribute_name': 6492if ( new_attr_type !== attr_id ) { 6493Object.defineProperty(_via_attributes[attr_type], 6494new_attr_type, 6495Object.getOwnPropertyDescriptor(_via_attributes[attr_type], attr_id)); 6496 6497delete _via_attributes[attr_type][attr_id]; 6498update_attributes_update_panel(); 6499annotation_editor_update_content(); 6500} 6501break; 6502case 'attribute_description': 6503_via_attributes[attr_type][attr_id].description = new_attr_type; 6504update_attributes_update_panel(); 6505annotation_editor_update_content(); 6506break; 6507case 'attribute_default_value': 6508_via_attributes[attr_type][attr_id].default_value = new_attr_type; 6509update_attributes_update_panel(); 6510annotation_editor_update_content(); 6511break; 6512case 'attribute_type': 6513var old_attr_type = _via_attributes[attr_type][attr_id].type; 6514_via_attributes[attr_type][attr_id].type = new_attr_type; 6515if( new_attr_type === VIA_ATTRIBUTE_TYPE.TEXT ) { 6516_via_attributes[attr_type][attr_id].default_value = ''; 6517delete _via_attributes[attr_type][attr_id].options; 6518delete _via_attributes[attr_type][attr_id].default_options; 6519} else { 6520// add options entry (if missing) 6521if ( ! _via_attributes[attr_type][attr_id].hasOwnProperty('options') ) { 6522_via_attributes[attr_type][attr_id].options = {}; 6523_via_attributes[attr_type][attr_id].default_options = {}; 6524} 6525if ( _via_attributes[attr_type][attr_id].hasOwnProperty('default_value') ) { 6526delete _via_attributes[attr_type][attr_id].default_value; 6527} 6528 6529// 1. gather all the attribute values in existing metadata 6530var existing_attr_values = attribute_get_unique_values(attr_type, attr_id); 6531 6532// 2. for checkbox, radio, dropdown: create options based on existing options and existing values 6533for(var option_id in _via_attributes[attr_type][attr_id]['options']) { 6534if( !existing_attr_values.includes(option_id) ) { 6535_via_attributes[attr_type][attr_id]['options'][option_id] = option_id; 6536} 6537} 6538 6539// update existing metadata to reflect changes in attribute type 6540// ensure that attribute has only one value 6541for(var img_id in _via_img_metadata ) { 6542for(var rindex in _via_img_metadata[img_id]['regions']) { 6543if(_via_img_metadata[img_id]['regions'][rindex]['region_attributes'].hasOwnProperty(attr_id)) { 6544if(old_attr_type === VIA_ATTRIBUTE_TYPE.CHECKBOX && 6545(new_attr_type === VIA_ATTRIBUTE_TYPE.RADIO || 6546new_attr_type === VIA_ATTRIBUTE_TYPE.DROPDOWN) ) { 6547// add only if checkbox has only single option selected 6548var sel_option_count = 0; 6549var sel_option_id; 6550for(var option_id in _via_img_metadata[img_id]['regions'][rindex]['region_attributes'][attr_id]) { 6551if(_via_img_metadata[img_id]['regions'][rindex]['region_attributes'][attr_id][option_id]) { 6552sel_option_count = sel_option_count + 1; 6553sel_option_id = option_id; 6554} 6555} 6556if(sel_option_count === 1) { 6557_via_img_metadata[img_id]['regions'][rindex]['region_attributes'][attr_id] = sel_option_id; 6558} else { 6559// delete as multiple options cannot be represented as radio or dropdown 6560delete _via_img_metadata[img_id]['regions'][rindex]['region_attributes'][attr_id]; 6561} 6562} 6563if( (old_attr_type === VIA_ATTRIBUTE_TYPE.RADIO || 6564old_attr_type === VIA_ATTRIBUTE_TYPE.DROPDOWN) && 6565new_attr_type === VIA_ATTRIBUTE_TYPE.CHECKBOX) { 6566var old_option_id = _via_img_metadata[img_id]['regions'][rindex]['region_attributes'][attr_id]; 6567_via_img_metadata[img_id]['regions'][rindex]['region_attributes'][attr_id] = {}; 6568_via_img_metadata[img_id]['regions'][rindex]['region_attributes'][attr_id][old_option_id] = true; 6569} 6570} 6571} 6572} 6573} 6574show_attribute_properties(); 6575show_attribute_options(); 6576annotation_editor_update_content(); 6577break; 6578} 6579} 6580 6581function attribute_get_unique_values(attr_type, attr_id) { 6582var values = []; 6583switch ( attr_type ) { 6584case 'file': 6585var img_id, attr_val; 6586for ( img_id in _via_img_metadata ) { 6587if ( _via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_id) ) { 6588attr_val = _via_img_metadata[img_id].file_attributes[attr_id]; 6589if ( ! values.includes(attr_val) ) { 6590values.push(attr_val); 6591} 6592} 6593} 6594break; 6595case 'region': 6596var img_id, attr_val, i; 6597for ( img_id in _via_img_metadata ) { 6598for ( i = 0; i < _via_img_metadata[img_id].regions.length; ++i ) { 6599if ( _via_img_metadata[img_id].regions[i].region_attributes.hasOwnProperty(attr_id) ) { 6600attr_val = _via_img_metadata[img_id].regions[i].region_attributes[attr_id]; 6601if( typeof(attr_val) === 'object' ) { 6602for(var option_id in _via_img_metadata[img_id].regions[i].region_attributes[attr_id]) { 6603if ( ! values.includes(option_id) ) { 6604values.push(option_id); 6605} 6606} 6607} else { 6608if ( ! values.includes(attr_val) ) { 6609values.push(attr_val); 6610} 6611} 6612} 6613} 6614} 6615break; 6616default: 6617break; 6618} 6619return values; 6620} 6621 6622function attribute_property_on_option_update(p) { 6623var attr_id = get_current_attribute_id(); 6624if ( p.id.startsWith('_via_attribute_option_id_') ) { 6625var old_key = p.id.substr( '_via_attribute_option_id_'.length ); 6626var new_key = p.value; 6627if ( old_key !== new_key ) { 6628var option_id_test = attribute_property_option_id_is_valid(attr_id, new_key); 6629if ( option_id_test.is_valid ) { 6630update_attribute_option_id_with_confirm(_via_attribute_being_updated, 6631attr_id, 6632old_key, 6633new_key); 6634} else { 6635p.value = old_key; // restore old value 6636show_message( option_id_test.message ); 6637show_attribute_properties(); 6638} 6639return; 6640} 6641} 6642 6643if ( p.id.startsWith('_via_attribute_option_description_') ) { 6644var key = p.id.substr( '_via_attribute_option_description_'.length ); 6645var old_value = _via_attributes[_via_attribute_being_updated][attr_id].options[key]; 6646var new_value = p.value; 6647if ( new_value !== old_value ) { 6648_via_attributes[_via_attribute_being_updated][attr_id].options[key] = new_value; 6649show_attribute_properties(); 6650annotation_editor_update_content(); 6651} 6652} 6653 6654if ( p.id.startsWith('_via_attribute_option_default_') ) { 6655var new_default_option_id = p.id.substr( '_via_attribute_option_default_'.length ); 6656var old_default_option_id_list = Object.keys(_via_attributes[_via_attribute_being_updated][attr_id].default_options); 6657 6658if ( old_default_option_id_list.length === 0 ) { 6659// default set for the first time 6660_via_attributes[_via_attribute_being_updated][attr_id].default_options[new_default_option_id] = p.checked; 6661} else { 6662switch ( _via_attributes[_via_attribute_being_updated][attr_id].type ) { 6663case 'image': // fallback 6664case 'dropdown': // fallback 6665case 'radio': // fallback 6666// to ensure that only one radio button is selected at a time 6667_via_attributes[_via_attribute_being_updated][attr_id].default_options = {}; 6668_via_attributes[_via_attribute_being_updated][attr_id].default_options[new_default_option_id] = p.checked; 6669break; 6670case 'checkbox': 6671_via_attributes[_via_attribute_being_updated][attr_id].default_options[new_default_option_id] = p.checked; 6672break; 6673} 6674} 6675// default option updated 6676attribute_property_on_option_default_update(_via_attribute_being_updated, 6677attr_id, 6678new_default_option_id).then( function() { 6679show_attribute_properties(); 6680annotation_editor_update_content(); 6681}); 6682} 6683} 6684 6685function attribute_property_on_option_default_update(attribute_being_updated, attr_id, new_default_option_id) { 6686return new Promise( function(ok_callback, err_callback) { 6687// set all metadata to new_value if: 6688// - metadata[attr_id] is missing 6689// - metadata[attr_id] is set to option_old_value 6690var img_id, attr_value, n, i; 6691var attr_type = _via_attributes[attribute_being_updated][attr_id].type; 6692switch( attribute_being_updated ) { 6693case 'file': 6694for ( img_id in _via_img_metadata ) { 6695if ( ! _via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_id) ) { 6696_via_img_metadata[img_id].file_attributes[attr_id] = new_default_option_id; 6697} 6698} 6699break; 6700case 'region': 6701for ( img_id in _via_img_metadata ) { 6702n = _via_img_metadata[img_id].regions.length; 6703for ( i = 0; i < n; ++i ) { 6704if ( ! _via_img_metadata[img_id].regions[i].region_attributes.hasOwnProperty(attr_id) ) { 6705_via_img_metadata[img_id].regions[i].region_attributes[attr_id] = new_default_option_id; 6706} 6707} 6708} 6709break; 6710} 6711ok_callback(); 6712}); 6713} 6714 6715function attribute_property_on_option_add(p) { 6716if ( p.value === '' || p.value === null ) { 6717return; 6718} 6719 6720if ( p.id === '_via_attribute_new_option_id' ) { 6721var attr_id = get_current_attribute_id(); 6722var option_id = p.value; 6723var option_id_test = attribute_property_option_id_is_valid(attr_id, option_id); 6724if ( option_id_test.is_valid ) { 6725_via_attributes[_via_attribute_being_updated][attr_id].options[option_id] = ''; 6726show_attribute_options(); 6727annotation_editor_update_content(); 6728} else { 6729show_message( option_id_test.message ); 6730attribute_property_reset_new_entry_inputs(); 6731} 6732} 6733} 6734 6735function attribute_property_reset_new_entry_inputs() { 6736var container = document.getElementById('attribute_options'); 6737var p = container.lastChild; 6738if ( p.childNodes[0] ) { 6739p.childNodes[0].value = ''; 6740} 6741if ( p.childNodes[1] ) { 6742p.childNodes[1].value = ''; 6743} 6744} 6745 6746function attribute_property_show_new_entry_inputs(attr_id, attribute_type) { 6747var n0 = document.createElement('div'); 6748n0.classList.add('property'); 6749var n1a = document.createElement('span'); 6750var n1b = document.createElement('input'); 6751n1b.setAttribute('onchange', 'attribute_property_on_option_add(this)'); 6752n1b.setAttribute('placeholder', 'Add new id'); 6753n1b.setAttribute('value', ''); 6754n1b.setAttribute('id', '_via_attribute_new_option_id'); 6755n1a.appendChild(n1b); 6756 6757var n2a = document.createElement('span'); 6758var n2b = document.createElement('input'); 6759n2b.setAttribute('onchange', 'attribute_property_on_option_add(this)'); 6760n2b.setAttribute('placeholder', 'Optional description'); 6761n2b.setAttribute('value', ''); 6762n2b.setAttribute('id', '_via_attribute_new_option_description'); 6763n2a.appendChild(n2b); 6764 6765var n3a = document.createElement('span'); 6766var n3b = document.createElement('input'); 6767n3b.setAttribute('type', attribute_type); 6768if ( attribute_type === 'radio' ) { 6769n3b.setAttribute('name', attr_id); 6770} 6771n3b.setAttribute('onchange', 'attribute_property_on_option_add(this)'); 6772n3b.setAttribute('id', '_via_attribute_new_option_default'); 6773n3a.appendChild(n3b); 6774 6775n0.appendChild(n1a); 6776n0.appendChild(n2a); 6777n0.appendChild(n3a); 6778 6779var container = document.getElementById('attribute_options'); 6780container.appendChild(n0); 6781} 6782 6783function attribute_property_option_id_is_valid(attr_id, new_option_id) { 6784var option_id; 6785for ( option_id in _via_attributes[_via_attribute_being_updated][attr_id].options ) { 6786if ( option_id === new_option_id ) { 6787return { 'is_valid':false, 'message':'Option id [' + attr_id + '] already exists' }; 6788} 6789} 6790 6791if ( new_option_id.includes('__') ) { // reserved separator for attribute-id, row-id, option-id 6792return {'is_valid':false, 'message':'Option id cannot contain two consecutive underscores'}; 6793} 6794 6795return {'is_valid':true}; 6796} 6797 6798function attribute_property_id_exists(name) { 6799var attr_name; 6800for ( attr_name in _via_attributes[_via_attribute_being_updated] ) { 6801if ( attr_name === name ) { 6802return true; 6803} 6804} 6805return false; 6806} 6807 6808function delete_existing_attribute_with_confirm() { 6809var attr_id = document.getElementById('user_input_attribute_id').value; 6810if ( attr_id === '' ) { 6811show_message('Enter the name of attribute that you wish to delete'); 6812return; 6813} 6814if ( attribute_property_id_exists(attr_id) ) { 6815var config = {'title':'Delete ' + _via_attribute_being_updated + ' attribute [' + attr_id + ']', 6816'warning': 'Warning: Deleting an attribute will lead to the attribute being deleted in all the annotations. Please click OK only if you are sure.'}; 6817var input = { 'attr_type':{'type':'text', 'name':'Attribute Type', 'value':_via_attribute_being_updated, 'disabled':true}, 6818'attr_id':{'type':'text', 'name':'Attribute Id', 'value':attr_id, 'disabled':true} 6819}; 6820invoke_with_user_inputs(delete_existing_attribute_confirmed, input, config); 6821} else { 6822show_message('Attribute [' + attr_id + '] does not exist!'); 6823return; 6824} 6825} 6826 6827function delete_existing_attribute_confirmed(input) { 6828var attr_type = input.attr_type.value; 6829var attr_id = input.attr_id.value; 6830delete_existing_attribute(attr_type, attr_id); 6831document.getElementById('user_input_attribute_id').value = ''; 6832show_message('Deleted ' + attr_type + ' attribute [' + attr_id + ']'); 6833user_input_default_cancel_handler(); 6834} 6835 6836function delete_existing_attribute(attribute_type, attr_id) { 6837if ( _via_attributes[attribute_type].hasOwnProperty( attr_id ) ) { 6838var attr_id_list = Object.keys(_via_attributes[attribute_type]); 6839if ( attr_id_list.length === 1 ) { 6840_via_current_attribute_id = ''; 6841} else { 6842var current_index = attr_id_list.indexOf(attr_id); 6843var next_index = current_index + 1; 6844if ( next_index === attr_id_list.length ) { 6845next_index = current_index - 1; 6846} 6847_via_current_attribute_id = attr_id_list[next_index]; 6848} 6849delete _via_attributes[attribute_type][attr_id]; 6850delete_region_attribute_in_all_metadata(attr_id); 6851update_attributes_update_panel(); 6852annotation_editor_update_content(); 6853} 6854} 6855 6856function add_new_attribute_from_user_input() { 6857var attr_id = document.getElementById('user_input_attribute_id').value; 6858if ( attr_id === '' ) { 6859show_message('Enter the name of attribute that you wish to delete'); 6860return; 6861} 6862 6863if ( attribute_property_id_exists(attr_id) ) { 6864show_message('The ' + _via_attribute_being_updated + ' attribute [' + attr_id + '] already exists.'); 6865} else { 6866_via_current_attribute_id = attr_id; 6867add_new_attribute(attr_id); 6868update_attributes_update_panel(); 6869annotation_editor_update_content(); 6870show_message('Added ' + _via_attribute_being_updated + ' attribute [' + attr_id + '].'); 6871} 6872} 6873 6874function add_new_attribute(attribute_id) { 6875_via_attributes[_via_attribute_being_updated][attribute_id] = {}; 6876_via_attributes[_via_attribute_being_updated][attribute_id].type = 'text'; 6877_via_attributes[_via_attribute_being_updated][attribute_id].description = ''; 6878_via_attributes[_via_attribute_being_updated][attribute_id].default_value = ''; 6879} 6880 6881function update_current_attribute_id(p) { 6882_via_current_attribute_id = p.options[p.selectedIndex].value; 6883update_attribute_properties_panel(); 6884} 6885 6886function get_current_attribute_id() { 6887return document.getElementById('attributes_name_list').value; 6888} 6889 6890function update_attribute_option_id_with_confirm(attr_type, attr_id, option_id, new_option_id) { 6891var is_delete = false; 6892var config; 6893if ( new_option_id === '' || typeof(new_option_id) === 'undefined' ) { 6894// an empty new_option_id indicates deletion of option_id 6895config = {'title':'Delete an option for ' + attr_type + ' attribute'}; 6896is_delete = true; 6897} else { 6898config = {'title':'Rename an option for ' + attr_type + ' attribute'}; 6899} 6900 6901var input = { 'attr_type':{'type':'text', 'name':'Attribute Type', 'value':attr_type, 'disabled':true}, 6902'attr_id':{'type':'text', 'name':'Attribute Id', 'value':attr_id, 'disabled':true} 6903}; 6904 6905if ( is_delete ) { 6906input['option_id'] = {'type':'text', 'name':'Attribute Option', 'value':option_id, 'disabled':true}; 6907} else { 6908input['option_id'] = {'type':'text', 'name':'Attribute Option (old)', 'value':option_id, 'disabled':true}, 6909input['new_option_id'] = {'type':'text', 'name':'Attribute Option (new)', 'value':new_option_id, 'disabled':true}; 6910} 6911 6912invoke_with_user_inputs(update_attribute_option_id_confirmed, input, config, update_attribute_option_id_cancel); 6913} 6914 6915function update_attribute_option_id_cancel(input) { 6916update_attribute_properties_panel(); 6917} 6918 6919function update_attribute_option_id_confirmed(input) { 6920var attr_type = input.attr_type.value; 6921var attr_id = input.attr_id.value; 6922var option_id = input.option_id.value; 6923var is_delete; 6924var new_option_id; 6925if ( typeof(input.new_option_id) === 'undefined' || input.new_option_id === '' ) { 6926is_delete = true; 6927new_option_id = ''; 6928} else { 6929is_delete = false; 6930new_option_id = input.new_option_id.value; 6931} 6932 6933update_attribute_option(is_delete, attr_type, attr_id, option_id, new_option_id); 6934 6935if ( is_delete ) { 6936show_message('Deleted option [' + option_id + '] for ' + attr_type + ' attribute [' + attr_id + '].'); 6937} else { 6938show_message('Renamed option [' + option_id + '] to [' + new_option_id + '] for ' + attr_type + ' attribute [' + attr_id + '].'); 6939} 6940update_attribute_properties_panel(); 6941annotation_editor_update_content(); 6942user_input_default_cancel_handler(); 6943} 6944 6945function update_attribute_option(is_delete, attr_type, attr_id, option_id, new_option_id) { 6946switch ( attr_type ) { 6947case 'region': 6948update_region_attribute_option_in_all_metadata(is_delete, attr_id, option_id, new_option_id); 6949if ( ! is_delete ) { 6950Object.defineProperty(_via_attributes[attr_type][attr_id].options, 6951new_option_id, 6952Object.getOwnPropertyDescriptor(_via_attributes[_via_attribute_being_updated][attr_id].options, option_id)); 6953} 6954delete _via_attributes['region'][attr_id].options[option_id]; 6955 6956break; 6957case 'file': 6958update_file_attribute_option_in_all_metadata(attr_id, option_id); 6959if ( ! is_delete ) { 6960Object.defineProperty(_via_attributes[attr_type][attr_id].options, 6961new_option_id, 6962Object.getOwnPropertyDescriptor(_via_attributes[_via_attribute_being_updated][attr_id].options, option_id)); 6963} 6964 6965delete _via_attributes['file'][attr_id].options[option_id]; 6966break; 6967} 6968} 6969 6970function update_file_attribute_option_in_all_metadata(is_delete, attr_id, option_id, new_option_id) { 6971var image_id; 6972for ( image_id in _via_img_metadata ) { 6973if ( _via_img_metadata[image_id].file_attributes.hasOwnProperty(attr_id) ) { 6974if ( _via_img_metadata[image_id].file_attributes[attr_id].hasOwnProperty(option_id) ) { 6975Object.defineProperty(_via_img_metadata[image_id].file_attributes[attr_id], 6976new_option_id, 6977Object.getOwnPropertyDescriptor(_via_img_metadata[image_id].file_attributes[attr_id], option_id)); 6978delete _via_img_metadata[image_id].file_attributes[attr_id][option_id]; 6979} 6980} 6981} 6982} 6983 6984function update_region_attribute_option_in_all_metadata(is_delete, attr_id, option_id, new_option_id) { 6985var image_id; 6986for ( image_id in _via_img_metadata ) { 6987for (var i = 0; i < _via_img_metadata[image_id].regions.length; ++i ) { 6988if ( _via_img_metadata[image_id].regions[i].region_attributes.hasOwnProperty(attr_id) ) { 6989if ( _via_img_metadata[image_id].regions[i].region_attributes[attr_id].hasOwnProperty(option_id) ) { 6990Object.defineProperty(_via_img_metadata[image_id].regions[i].region_attributes[attr_id], 6991new_option_id, 6992Object.getOwnPropertyDescriptor(_via_img_metadata[image_id].regions[i].region_attributes[attr_id], option_id)); 6993delete _via_img_metadata[image_id].regions[i].region_attributes[attr_id][option_id]; 6994} 6995} 6996} 6997} 6998} 6999 7000function delete_region_attribute_in_all_metadata(attr_id) { 7001var image_id; 7002for ( image_id in _via_img_metadata ) { 7003for (var i = 0; i < _via_img_metadata[image_id].regions.length; ++i ) { 7004if ( _via_img_metadata[image_id].regions[i].region_attributes.hasOwnProperty(attr_id)) { 7005delete _via_img_metadata[image_id].regions[i].region_attributes[attr_id]; 7006} 7007} 7008} 7009} 7010 7011function delete_file_attribute_option_from_all_metadata(attr_id, option_id) { 7012var image_id; 7013for ( image_id in _via_img_metadata ) { 7014if ( _via_img_metadata.hasOwnProperty(image_id) ) { 7015delete_file_attribute_option_from_metadata(image_id, attr_id, option_id); 7016} 7017} 7018} 7019 7020function delete_file_attribute_option_from_metadata(image_id, attr_id, option_id) { 7021var i; 7022if ( _via_img_metadata[image_id].file_attributes.hasOwnProperty(attr_id) ) { 7023if ( _via_img_metadata[image_id].file_attributes[attr_id].hasOwnProperty(option_id) ) { 7024delete _via_img_metadata[image_id].file_attributes[attr_id][option_id]; 7025} 7026} 7027} 7028 7029function delete_file_attribute_from_all_metadata(image_id, attr_id) { 7030var image_id; 7031for ( image_id in _via_img_metadata ) { 7032if ( _via_img_metadata.hasOwnProperty(image_id) ) { 7033if ( _via_img_metadata[image_id].file_attributes.hasOwnProperty(attr_id) ) { 7034delete _via_img_metadata[image_id].file_attributes[attr_id]; 7035} 7036} 7037} 7038} 7039 7040// 7041// invoke a method after receiving inputs from user 7042// 7043function invoke_with_user_inputs(ok_handler, input, config, cancel_handler) { 7044setup_user_input_panel(ok_handler, input, config, cancel_handler); 7045show_user_input_panel(); 7046} 7047 7048function setup_user_input_panel(ok_handler, input, config, cancel_handler) { 7049// create html page with OK and CANCEL button 7050// when OK is clicked 7051// - setup input with all the user entered values 7052// - invoke handler with input 7053// when CANCEL is clicked 7054// - invoke user_input_cancel() 7055_via_user_input_ok_handler = ok_handler; 7056_via_user_input_cancel_handler = cancel_handler; 7057_via_user_input_data = input; 7058 7059var p = document.getElementById('user_input_panel'); 7060var c = document.createElement('div'); 7061c.setAttribute('class', 'content'); 7062var html = []; 7063html.push('<p class="title">' + config.title + '</p>'); 7064 7065html.push('<div class="user_inputs">'); 7066var key; 7067for ( key in _via_user_input_data ) { 7068html.push('<div class="row">'); 7069html.push('<span class="cell">' + _via_user_input_data[key].name + '</span>'); 7070var disabled_html = ''; 7071if ( _via_user_input_data[key].disabled ) { 7072disabled_html = 'disabled="disabled"'; 7073} 7074var value_html = ''; 7075if ( _via_user_input_data[key].value ) { 7076value_html = 'value="' + _via_user_input_data[key].value + '"'; 7077} 7078 7079switch(_via_user_input_data[key].type) { 7080case 'checkbox': 7081if ( _via_user_input_data[key].checked ) { 7082value_html = 'checked="checked"'; 7083} else { 7084value_html = ''; 7085} 7086html.push('<span class="cell">' + 7087'<input class="_via_user_input_variable" ' + 7088value_html + ' ' + 7089disabled_html + ' ' + 7090'type="checkbox" id="' + key + '"></span>'); 7091break; 7092case 'text': 7093var size = '50'; 7094if ( _via_user_input_data[key].size ) { 7095size = _via_user_input_data[key].size; 7096} 7097var placeholder = ''; 7098if ( _via_user_input_data[key].placeholder ) { 7099placeholder = _via_user_input_data[key].placeholder; 7100} 7101html.push('<span class="cell">' + 7102'<input class="_via_user_input_variable" ' + 7103value_html + ' ' + 7104disabled_html + ' ' + 7105'size="' + size + '" ' + 7106'placeholder="' + placeholder + '" ' + 7107'type="text" id="' + key + '"></span>'); 7108 7109break; 7110case 'textarea': 7111var rows = '5'; 7112var cols = '50' 7113if ( _via_user_input_data[key].rows ) { 7114rows = _via_user_input_data[key].rows; 7115} 7116if ( _via_user_input_data[key].cols ) { 7117cols = _via_user_input_data[key].cols; 7118} 7119var placeholder = ''; 7120if ( _via_user_input_data[key].placeholder ) { 7121placeholder = _via_user_input_data[key].placeholder; 7122} 7123html.push('<span class="cell">' + 7124'<textarea class="_via_user_input_variable" ' + 7125disabled_html + ' ' + 7126'rows="' + rows + '" ' + 7127'cols="' + cols + '" ' + 7128'placeholder="' + placeholder + '" ' + 7129'id="' + key + '">' + value_html + '</textarea></span>'); 7130 7131break; 7132 7133} 7134html.push('</div>'); // end of row 7135} 7136html.push('</div>'); // end of user_input div 7137// optional warning before confirmation 7138if (config.hasOwnProperty("warning") ) { 7139html.push('<div class="warning">' + config.warning + '</div>'); 7140} 7141html.push('<div class="user_confirm">' + 7142'<span class="ok">' + 7143'<button id="user_input_ok_button" onclick="user_input_parse_and_invoke_handler()"> OK </button></span>' + 7144'<span class="cancel">' + 7145'<button id="user_input_cancel_button" onclick="user_input_cancel_handler()">CANCEL</button></span></div>'); 7146c.innerHTML = html.join(''); 7147p.innerHTML = ''; 7148p.appendChild(c); 7149 7150} 7151 7152function user_input_default_cancel_handler() { 7153hide_user_input_panel(); 7154_via_user_input_data = {}; 7155_via_user_input_ok_handler = null; 7156_via_user_input_cancel_handler = null; 7157} 7158 7159function user_input_cancel_handler() { 7160if ( _via_user_input_cancel_handler ) { 7161_via_user_input_cancel_handler(); 7162} 7163user_input_default_cancel_handler(); 7164} 7165 7166function user_input_parse_and_invoke_handler() { 7167var elist = document.getElementsByClassName('_via_user_input_variable'); 7168var i; 7169for ( i=0; i < elist.length; ++i ) { 7170var eid = elist[i].id; 7171if ( _via_user_input_data.hasOwnProperty(eid) ) { 7172switch(_via_user_input_data[eid].type) { 7173case 'checkbox': 7174_via_user_input_data[eid].value = elist[i].checked; 7175break; 7176default: 7177_via_user_input_data[eid].value = elist[i].value; 7178break; 7179} 7180} 7181} 7182if ( typeof(_via_user_input_data.confirm) !== 'undefined' ) { 7183if ( _via_user_input_data.confirm.value ) { 7184_via_user_input_ok_handler(_via_user_input_data); 7185} else { 7186if ( _via_user_input_cancel_handler ) { 7187_via_user_input_cancel_handler(); 7188} 7189} 7190} else { 7191_via_user_input_ok_handler(_via_user_input_data); 7192} 7193user_input_default_cancel_handler(); 7194} 7195 7196function show_user_input_panel() { 7197document.getElementById('user_input_panel').style.display = 'block'; 7198} 7199 7200function hide_user_input_panel() { 7201document.getElementById('user_input_panel').style.display = 'none'; 7202} 7203 7204// 7205// annotations editor panel 7206// 7207function annotation_editor_show() { 7208// remove existing annotation editor (if any) 7209annotation_editor_remove(); 7210 7211// create new container of annotation editor 7212var ae = document.createElement('div'); 7213ae.setAttribute('id', 'annotation_editor'); 7214 7215if ( _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION ) { 7216if ( _via_settings.ui.image.on_image_annotation_editor_placement === VIA_ANNOTATION_EDITOR_PLACEMENT.DISABLE ) { 7217return; 7218} 7219 7220// only display on-image annotation editor if 7221// - region attribute are defined 7222// - region is selected 7223if ( _via_is_region_selected && 7224Object.keys(_via_attributes['region']).length && 7225_via_attributes['region'].constructor === Object ) { 7226ae.classList.add('force_small_font'); 7227ae.classList.add('display_area_content'); // to enable automatic hiding of this content 7228// add annotation editor to image_panel 7229if ( _via_settings.ui.image.on_image_annotation_editor_placement === VIA_ANNOTATION_EDITOR_PLACEMENT.NEAR_REGION ) { 7230var html_position = annotation_editor_get_placement(_via_user_sel_region_id); 7231ae.style.top = html_position.top; 7232ae.style.left = html_position.left; 7233} 7234_via_display_area.appendChild(ae); 7235annotation_editor_update_content(); 7236update_vertical_space(); 7237} 7238} else { 7239// show annotation editor in a separate panel at the bottom 7240_via_annotaion_editor_panel.appendChild(ae); 7241annotation_editor_update_content(); 7242update_vertical_space(); 7243 7244if ( _via_is_region_selected ) { 7245// highlight entry for region_id in annotation editor panel 7246annotation_editor_scroll_to_row(_via_user_sel_region_id); 7247annotation_editor_highlight_row(_via_user_sel_region_id); 7248} 7249} 7250} 7251 7252function annotation_editor_hide() { 7253if ( _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION ) { 7254// remove existing annotation editor (if any) 7255annotation_editor_remove(); 7256} else { 7257annotation_editor_clear_row_highlight(); 7258} 7259} 7260 7261function annotation_editor_toggle_on_image_editor() { 7262if ( _via_settings.ui.image.on_image_annotation_editor_placement === VIA_ANNOTATION_EDITOR_PLACEMENT.DISABLE ) { 7263_via_annotation_editor_mode = VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION; 7264_via_settings.ui.image.on_image_annotation_editor_placement = VIA_ANNOTATION_EDITOR_PLACEMENT.NEAR_REGION; 7265annotation_editor_show(); 7266show_message('Enabled on image annotation editor'); 7267} else { 7268_via_settings.ui.image.on_image_annotation_editor_placement = VIA_ANNOTATION_EDITOR_PLACEMENT.DISABLE; 7269_via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS; 7270annotation_editor_hide(); 7271show_message('Disabled on image annotation editor'); 7272} 7273} 7274 7275function annotation_editor_update_content() { 7276return new Promise( function(ok_callback, err_callback) { 7277var ae = document.getElementById('annotation_editor'); 7278if (ae ) { 7279ae.innerHTML = ''; 7280annotation_editor_update_header_html(); 7281annotation_editor_update_metadata_html(); 7282} 7283ok_callback(); 7284}); 7285} 7286 7287function annotation_editor_get_placement(region_id) { 7288var html_position = {}; 7289var r = _via_canvas_regions[region_id]['shape_attributes']; 7290var shape = r['name']; 7291switch( shape ) { 7292case 'rect': 7293html_position.top = r['y'] + r['height']; 7294html_position.left = r['x'] + r['width']; 7295break; 7296case 'circle': 7297html_position.top = r['cy'] + r['r']; 7298html_position.left = r['cx']; 7299break; 7300case 'ellipse': 7301html_position.top = r['cy'] + r['ry'] * Math.cos(r['theta']); 7302html_position.left = r['cx'] - r['ry'] * Math.sin(r['theta']); 7303break; 7304case 'polygon': 7305case 'polyline': 7306var most_left = 7307Object.keys(r['all_points_x']).reduce(function(a, b){ 7308return r['all_points_x'][a] > r['all_points_x'][b] ? a : b }); 7309html_position.top = Math.max( r['all_points_y'][most_left] ); 7310html_position.left = Math.max( r['all_points_x'][most_left] ); 7311break; 7312case 'point': 7313html_position.top = r['cy']; 7314html_position.left = r['cx']; 7315break; 7316} 7317html_position.top = html_position.top + _via_img_panel.offsetTop + VIA_REGION_EDGE_TOL + 'px'; 7318html_position.left = html_position.left + _via_img_panel.offsetLeft + VIA_REGION_EDGE_TOL + 'px'; 7319return html_position; 7320} 7321 7322function annotation_editor_remove() { 7323var p = document.getElementById('annotation_editor'); 7324if ( p ) { 7325p.remove(); 7326} 7327} 7328 7329function is_annotation_editor_visible() { 7330return document.getElementById('annotation_editor_panel').classList.contains('display_block'); 7331} 7332 7333function annotation_editor_toggle_all_regions_editor() { 7334var p = document.getElementById('annotation_editor_panel'); 7335if ( p.classList.contains('display_block') ) { 7336p.classList.remove('display_block'); 7337_via_annotation_editor_mode = VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION; 7338} else { 7339_via_annotation_editor_mode = VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS; 7340p.classList.add('display_block'); 7341p.style.height = _via_settings.ui.annotation_editor_height + '%'; 7342p.style.fontSize = _via_settings.ui.annotation_editor_fontsize + 'rem'; 7343annotation_editor_show(); 7344} 7345} 7346 7347function annotation_editor_set_active_button() { 7348var attribute_type; 7349for ( attribute_type in _via_attributes ) { 7350var bid = 'button_edit_' + attribute_type + '_metadata'; 7351document.getElementById(bid).classList.remove('active'); 7352} 7353var bid = 'button_edit_' + _via_metadata_being_updated + '_metadata'; 7354document.getElementById(bid).classList.add('active'); 7355} 7356 7357function edit_region_metadata_in_annotation_editor() { 7358_via_metadata_being_updated = 'region'; 7359annotation_editor_set_active_button(); 7360annotation_editor_update_content(); 7361} 7362function edit_file_metadata_in_annotation_editor() { 7363_via_metadata_being_updated = 'file'; 7364annotation_editor_set_active_button(); 7365annotation_editor_update_content(); 7366} 7367 7368function annotation_editor_update_header_html() { 7369var head = document.createElement('div'); 7370head.setAttribute('class', 'row'); 7371head.setAttribute('id', 'annotation_editor_header'); 7372 7373if ( _via_metadata_being_updated === 'region' ) { 7374var rid_col = document.createElement('span'); 7375rid_col.setAttribute('class', 'col'); 7376rid_col.innerHTML = ''; 7377head.appendChild(rid_col); 7378} 7379 7380if ( _via_metadata_being_updated === 'file' ) { 7381var rid_col = document.createElement('span'); 7382rid_col.setAttribute('class', 'col header'); 7383if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 7384rid_col.innerHTML = 'group'; 7385} else { 7386rid_col.innerHTML = 'filename'; 7387} 7388head.appendChild(rid_col); 7389} 7390 7391var attr_id; 7392for ( attr_id in _via_attributes[_via_metadata_being_updated] ) { 7393var col = document.createElement('span'); 7394col.setAttribute('class', 'col header'); 7395col.innerHTML = attr_id; 7396head.appendChild(col); 7397} 7398 7399var ae = document.getElementById('annotation_editor'); 7400if ( ae.childNodes.length === 0 ) { 7401ae.appendChild(head); 7402} else { 7403if ( ae.firstChild.id === 'annotation_editor_header') { 7404ae.replaceChild(head, ae.firstChild); 7405} else { 7406// header node is absent 7407ae.insertBefore(head, ae.firstChild); 7408} 7409} 7410} 7411 7412function annotation_editor_update_metadata_html() { 7413if ( ! _via_img_count ) { 7414return; 7415} 7416 7417var ae = document.getElementById('annotation_editor'); 7418switch ( _via_metadata_being_updated ) { 7419case 'region': 7420var rindex; 7421if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 7422ae.appendChild( annotation_editor_get_metadata_row_html(0) ); 7423} else { 7424if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE ) { 7425if ( _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION ) { 7426ae.appendChild( annotation_editor_get_metadata_row_html(_via_user_sel_region_id) ); 7427} else { 7428for ( rindex = 0; rindex < _via_img_metadata[_via_image_id].regions.length; ++rindex ) { 7429ae.appendChild( annotation_editor_get_metadata_row_html(rindex) ); 7430} 7431} 7432} 7433} 7434break; 7435 7436case 'file': 7437ae.appendChild( annotation_editor_get_metadata_row_html(0) ); 7438break; 7439} 7440} 7441 7442function annotation_editor_update_row(row_id) { 7443var ae = document.getElementById('annotation_editor'); 7444 7445var new_row = annotation_editor_get_metadata_row_html(row_id); 7446var old_row = document.getElementById(new_row.getAttribute('id')); 7447ae.replaceChild(new_row, old_row); 7448} 7449 7450function annotation_editor_add_row(row_id) { 7451if ( is_annotation_editor_visible() ) { 7452var ae = document.getElementById('annotation_editor'); 7453var new_row = annotation_editor_get_metadata_row_html(row_id); 7454var penultimate_row_id = parseInt(row_id) - 1; 7455if ( penultimate_row_id >= 0 ) { 7456var penultimate_row_html_id = 'ae_' + _via_metadata_being_updated + '_' + penultimate_row_id; 7457var penultimate_row = document.getElementById(penultimate_row_html_id); 7458ae.insertBefore(new_row, penultimate_row.nextSibling); 7459} else { 7460ae.appendChild(new_row); 7461} 7462} 7463} 7464 7465function annotation_editor_get_metadata_row_html(row_id) { 7466var row = document.createElement('div'); 7467row.setAttribute('class', 'row'); 7468row.setAttribute('id', 'ae_' + _via_metadata_being_updated + '_' + row_id); 7469 7470if ( _via_metadata_being_updated === 'region' ) { 7471var rid = document.createElement('span'); 7472 7473switch(_via_display_area_content_name) { 7474case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID: 7475rid.setAttribute('class', 'col'); 7476rid.innerHTML = 'Grouped regions in ' + _via_image_grid_selected_img_index_list.length + ' files'; 7477break; 7478case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE: 7479rid.setAttribute('class', 'col id'); 7480rid.innerHTML = (row_id + 1); 7481break; 7482} 7483row.appendChild(rid); 7484} 7485 7486if ( _via_metadata_being_updated === 'file' ) { 7487var rid = document.createElement('span'); 7488rid.setAttribute('class', 'col'); 7489switch(_via_display_area_content_name) { 7490case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID: 7491rid.innerHTML = 'Group of ' + _via_image_grid_selected_img_index_list.length + ' files'; 7492break; 7493case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE: 7494rid.innerHTML = _via_image_filename_list[_via_image_index]; 7495break; 7496} 7497 7498row.appendChild(rid); 7499} 7500 7501var attr_id; 7502for ( attr_id in _via_attributes[_via_metadata_being_updated] ) { 7503var col = document.createElement('span'); 7504col.setAttribute('class', 'col'); 7505 7506var attr_type = _via_attributes[_via_metadata_being_updated][attr_id].type; 7507var attr_desc = _via_attributes[_via_metadata_being_updated][attr_id].desc; 7508if ( typeof(attr_desc) === 'undefined' ) { 7509attr_desc = ''; 7510} 7511var attr_html_id = attr_id + '__' + row_id; 7512 7513var attr_value = ''; 7514var attr_placeholder = ''; 7515if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE ) { 7516switch(_via_metadata_being_updated) { 7517case 'region': 7518if ( _via_img_metadata[_via_image_id].regions[row_id].region_attributes.hasOwnProperty(attr_id) ) { 7519attr_value = _via_img_metadata[_via_image_id].regions[row_id].region_attributes[attr_id]; 7520} else { 7521attr_placeholder = 'not defined yet!'; 7522} 7523case 'file': 7524if ( _via_img_metadata[_via_image_id].file_attributes.hasOwnProperty(attr_id) ) { 7525attr_value = _via_img_metadata[_via_image_id].file_attributes[attr_id]; 7526} else { 7527attr_placeholder = 'not defined yet!'; 7528} 7529} 7530} 7531 7532if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 7533var attr_metadata_stat; 7534switch(_via_metadata_being_updated) { 7535case 'region': 7536attr_metadata_stat = _via_get_region_metadata_stat(_via_image_grid_selected_img_index_list, attr_id); 7537break; 7538case 'file': 7539attr_metadata_stat = _via_get_file_metadata_stat(_via_image_grid_selected_img_index_list, attr_id); 7540break; 7541} 7542 7543switch ( attr_type ) { 7544case 'text': 7545if ( attr_metadata_stat.hasOwnProperty(attr_id) ) { 7546var attr_value_set = Object.keys(attr_metadata_stat[attr_id]); 7547if ( attr_value_set.includes('undefined') ) { 7548attr_value = ''; 7549attr_placeholder = 'includes ' + attr_metadata_stat[attr_id]['undefined'] + ' undefined values'; 7550} else { 7551switch( attr_value_set.length ) { 7552case 0: 7553attr_value = ''; 7554attr_placeholder = 'not applicable'; 7555break; 7556case 1: 7557attr_value = attr_value_set[0]; 7558attr_placeholder = ''; 7559break; 7560default: 7561attr_value = ''; 7562attr_placeholder = attr_value_set.length + ' different values: ' + JSON.stringify(attr_value_set).replace(/"/g,'\''); 7563} 7564} 7565} else { 7566attr_value = ''; 7567attr_placeholder = 'not defined yet!'; 7568} 7569break; 7570 7571case 'radio': // fallback 7572case 'dropdown': // fallback 7573case 'image': // fallback 7574if ( attr_metadata_stat.hasOwnProperty(attr_id) ) { 7575var attr_value_set = Object.keys(attr_metadata_stat[attr_id]); 7576if ( attr_value_set.length === 1 ) { 7577attr_value = attr_value_set[0]; 7578} else { 7579attr_value = ''; 7580} 7581} else { 7582attr_value = ''; 7583} 7584break; 7585 7586case 'checkbox': 7587attr_value = {}; 7588if ( attr_metadata_stat.hasOwnProperty(attr_id) ) { 7589var attr_value_set = Object.keys(attr_metadata_stat[attr_id]); 7590var same_count = true; 7591var i, n; 7592var attr_value_curr, attr_value_next; 7593n = attr_value_set.length; 7594for ( i = 0; i < n - 1; ++i ) { 7595attr_value_curr = attr_value_set[i]; 7596attr_value_next = attr_value_set[i+1]; 7597 7598if ( attr_metadata_stat[attr_id][attr_value_curr] !== attr_metadata_stat[attr_id][attr_value_next] ) { 7599same_count = false; 7600break; 7601} 7602} 7603if ( same_count ) { 7604var attr_value_i; 7605for ( attr_value_i in attr_metadata_stat[attr_id] ) { 7606attr_value[attr_value_i] = true; 7607} 7608} 7609} 7610break; 7611} 7612} 7613 7614switch(attr_type) { 7615case 'text': 7616col.innerHTML = '<textarea ' + 7617'onchange="annotation_editor_on_metadata_update(this)" ' + 7618'onfocus="annotation_editor_on_metadata_focus(this)" ' + 7619'title="' + attr_desc + '" ' + 7620'placeholder="' + attr_placeholder + '" ' + 7621'id="' + attr_html_id + '">' + attr_value + '</textarea>'; 7622break; 7623case 'checkbox': 7624var options = _via_attributes[_via_metadata_being_updated][attr_id].options; 7625var option_id; 7626for ( option_id in options ) { 7627var option_html_id = attr_html_id + '__' + option_id; 7628var option = document.createElement('input'); 7629option.setAttribute('type', 'checkbox'); 7630option.setAttribute('value', option_id); 7631option.setAttribute('id', option_html_id); 7632option.setAttribute('onfocus', 'annotation_editor_on_metadata_focus(this)'); 7633option.setAttribute('onchange', 'annotation_editor_on_metadata_update(this)'); 7634 7635var option_desc = _via_attributes[_via_metadata_being_updated][attr_id].options[option_id]; 7636if ( option_desc === '' || typeof(option_desc) === 'undefined' ) { 7637// option description is optional, use option_id when description is not present 7638option_desc = option_id; 7639} 7640 7641// set the value of options based on the user annotations 7642if ( typeof attr_value !== 'undefined') { 7643if ( attr_value.hasOwnProperty(option_id) ) { 7644option.checked = attr_value[option_id]; 7645} 7646} 7647 7648var label = document.createElement('label'); 7649label.setAttribute('for', option_html_id); 7650label.innerHTML = option_desc; 7651 7652var container = document.createElement('span'); 7653container.appendChild(option); 7654container.appendChild(label); 7655col.appendChild(container); 7656} 7657break; 7658case 'radio': 7659var option_id; 7660for ( option_id in _via_attributes[_via_metadata_being_updated][attr_id].options ) { 7661var option_html_id = attr_html_id + '__' + option_id; 7662var option = document.createElement('input'); 7663option.setAttribute('type', 'radio'); 7664option.setAttribute('name', attr_html_id); 7665option.setAttribute('value', option_id); 7666option.setAttribute('id', option_html_id); 7667option.setAttribute('onfocus', 'annotation_editor_on_metadata_focus(this)'); 7668option.setAttribute('onchange', 'annotation_editor_on_metadata_update(this)'); 7669 7670var option_desc = _via_attributes[_via_metadata_being_updated][attr_id].options[option_id]; 7671if ( option_desc === '' || typeof(option_desc) === 'undefined' ) { 7672// option description is optional, use option_id when description is not present 7673option_desc = option_id; 7674} 7675 7676if ( attr_value === option_id ) { 7677option.checked = true; 7678} 7679 7680var label = document.createElement('label'); 7681label.setAttribute('for', option_html_id); 7682label.innerHTML = option_desc; 7683 7684var container = document.createElement('span'); 7685container.appendChild(option); 7686container.appendChild(label); 7687col.appendChild(container); 7688} 7689break; 7690case 'image': 7691var option_id; 7692var option_count = 0; 7693for ( option_id in _via_attributes[_via_metadata_being_updated][attr_id].options ) { 7694option_count = option_count + 1; 7695} 7696var img_options = document.createElement('div'); 7697img_options.setAttribute('class', 'img_options'); 7698col.appendChild(img_options); 7699 7700var option_index = 0; 7701for ( option_id in _via_attributes[_via_metadata_being_updated][attr_id].options ) { 7702var option_html_id = attr_html_id + '__' + option_id; 7703var option = document.createElement('input'); 7704option.setAttribute('type', 'radio'); 7705option.setAttribute('name', attr_html_id); 7706option.setAttribute('value', option_id); 7707option.setAttribute('id', option_html_id); 7708option.setAttribute('onfocus', 'annotation_editor_on_metadata_focus(this)'); 7709option.setAttribute('onchange', 'annotation_editor_on_metadata_update(this)'); 7710 7711var option_desc = _via_attributes[_via_metadata_being_updated][attr_id].options[option_id]; 7712if ( option_desc === '' || typeof(option_desc) === 'undefined' ) { 7713// option description is optional, use option_id when description is not present 7714option_desc = option_id; 7715} 7716 7717if ( attr_value === option_id ) { 7718option.checked = true; 7719} 7720 7721var label = document.createElement('label'); 7722label.setAttribute('for', option_html_id); 7723label.innerHTML = '<img src="' + option_desc + '"><p>' + option_id + '</p>'; 7724 7725var container = document.createElement('span'); 7726container.appendChild(option); 7727container.appendChild(label); 7728img_options.appendChild(container); 7729} 7730break; 7731 7732case 'dropdown': 7733var sel = document.createElement('select'); 7734sel.setAttribute('id', attr_html_id); 7735sel.setAttribute('onfocus', 'annotation_editor_on_metadata_focus(this)'); 7736sel.setAttribute('onchange', 'annotation_editor_on_metadata_update(this)'); 7737var option_id; 7738var option_selected = false; 7739for ( option_id in _via_attributes[_via_metadata_being_updated][attr_id].options ) { 7740var option_html_id = attr_html_id + '__' + option_id; 7741var option = document.createElement('option'); 7742option.setAttribute('value', option_id); 7743 7744var option_desc = _via_attributes[_via_metadata_being_updated][attr_id].options[option_id]; 7745if ( option_desc === '' || typeof(option_desc) === 'undefined' ) { 7746// option description is optional, use option_id when description is not present 7747option_desc = option_id; 7748} 7749 7750if ( option_id === attr_value ) { 7751option.setAttribute('selected', 'selected'); 7752option_selected = true; 7753} 7754option.innerHTML = option_desc; 7755sel.appendChild(option); 7756} 7757 7758if ( ! option_selected ) { 7759sel.selectedIndex = '-1'; 7760} 7761col.appendChild(sel); 7762break; 7763} 7764 7765row.appendChild(col); 7766} 7767return row; 7768} 7769 7770function annotation_editor_scroll_to_row(row_id) { 7771if ( is_annotation_editor_visible() ) { 7772var row_html_id = 'ae_' + _via_metadata_being_updated + '_' + row_id; 7773var row = document.getElementById(row_html_id); 7774row.scrollIntoView(false); 7775} 7776} 7777 7778function annotation_editor_highlight_row(row_id) { 7779if ( is_annotation_editor_visible() ) { 7780var row_html_id = 'ae_' + _via_metadata_being_updated + '_' + row_id; 7781var row = document.getElementById(row_html_id); 7782row.classList.add('highlight'); 7783} 7784} 7785 7786function annotation_editor_clear_row_highlight() { 7787if ( is_annotation_editor_visible() ) { 7788var ae = document.getElementById('annotation_editor'); 7789var i; 7790for ( i=0; i<ae.childNodes.length; ++i ) { 7791ae.childNodes[i].classList.remove('highlight'); 7792} 7793} 7794} 7795 7796function annotation_editor_extract_html_id_components(html_id) { 7797// html_id : attribute_name__row-id__option_id 7798var parts = html_id.split('__'); 7799var parsed_id = {}; 7800switch( parts.length ) { 7801case 3: 7802// html_id : attribute-id__row-id__option_id 7803parsed_id.attr_id = parts[0]; 7804parsed_id.row_id = parts[1]; 7805parsed_id.option_id = parts[2]; 7806break; 7807case 2: 7808// html_id : attribute-id__row-id 7809parsed_id.attr_id = parts[0]; 7810parsed_id.row_id = parts[1]; 7811break; 7812default: 7813} 7814return parsed_id; 7815} 7816 7817function _via_get_file_metadata_stat(img_index_list, attr_id) { 7818var stat = {}; 7819stat[attr_id] = {}; 7820var i, n, img_id, img_index, value; 7821n = img_index_list.length; 7822for ( i = 0; i < n; ++i ) { 7823img_index = img_index_list[i]; 7824img_id = _via_image_id_list[img_index]; 7825if ( _via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_id) ) { 7826value = _via_img_metadata[img_id].file_attributes[attr_id]; 7827if ( typeof(value) === 'object' ) { 7828// checkbox has multiple values and hence is object 7829var key; 7830for ( key in value ) { 7831if ( stat[attr_id].hasOwnProperty(key) ) { 7832stat[attr_id][key] += 1; 7833} else { 7834stat[attr_id][key] = 1; 7835} 7836} 7837} else { 7838if ( stat[attr_id].hasOwnProperty(value) ) { 7839stat[attr_id][value] += 1; 7840} else { 7841stat[attr_id][value] = 1; 7842} 7843} 7844} 7845 7846} 7847return stat; 7848} 7849 7850function _via_get_region_metadata_stat(img_index_list, attr_id) { 7851var stat = {}; 7852stat[attr_id] = {}; 7853var i, n, img_id, img_index, value; 7854var j, m; 7855n = img_index_list.length; 7856for ( i = 0; i < n; ++i ) { 7857img_index = img_index_list[i]; 7858img_id = _via_image_id_list[img_index]; 7859m = _via_img_metadata[img_id].regions.length; 7860for ( j = 0; j < m; ++j ) { 7861if ( ! image_grid_is_region_in_current_group( _via_img_metadata[img_id].regions[j].region_attributes ) ) { 7862// skip region not in current group 7863continue; 7864} 7865 7866value = _via_img_metadata[img_id].regions[j].region_attributes[attr_id]; 7867if ( typeof(value) === 'object' ) { 7868// checkbox has multiple values and hence is object 7869var key; 7870for ( key in value ) { 7871if ( stat[attr_id].hasOwnProperty(key) ) { 7872stat[attr_id][key] += 1; 7873} else { 7874stat[attr_id][key] = 1; 7875} 7876} 7877} else { 7878if ( stat[attr_id].hasOwnProperty(value) ) { 7879stat[attr_id][value] += 1; 7880} else { 7881stat[attr_id][value] = 1; 7882} 7883} 7884} 7885} 7886return stat; 7887} 7888 7889// invoked when the input entry in annotation editor receives focus 7890function annotation_editor_on_metadata_focus(p) { 7891if ( _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS ) { 7892var pid = annotation_editor_extract_html_id_components(p.id); 7893var region_id = pid.row_id; 7894// clear existing highlights (if any) 7895toggle_all_regions_selection(false); 7896annotation_editor_clear_row_highlight(); 7897// set new selection highlights 7898set_region_select_state(region_id, true); 7899annotation_editor_scroll_to_row(region_id); 7900annotation_editor_highlight_row(region_id); 7901 7902_via_redraw_reg_canvas(); 7903} 7904} 7905 7906// invoked when the user updates annotations using the annotation editor 7907function annotation_editor_on_metadata_update(p) { 7908var pid = annotation_editor_extract_html_id_components(p.id); 7909var img_id = _via_image_id; 7910 7911var img_index_list = [ _via_image_index ]; 7912var region_id = pid.row_id; 7913if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 7914img_index_list = _via_image_grid_selected_img_index_list.slice(0); 7915region_id = -1; // this flag denotes that we want to update all regions 7916} 7917 7918if ( _via_metadata_being_updated === 'file' ) { 7919annotation_editor_update_file_metadata(img_index_list, pid.attr_id, p.value, p.checked).then( function(update_count) { 7920annotation_editor_on_metadata_update_done('file', pid.attr_id, update_count); 7921}, function(err) { 7922console.log(err) 7923show_message('Failed to update file attributes! ' + err); 7924}); 7925return; 7926} 7927 7928if ( _via_metadata_being_updated === 'region' ) { 7929annotation_editor_update_region_metadata(img_index_list, region_id, pid.attr_id, p.value, p.checked).then( function(update_count) { 7930annotation_editor_on_metadata_update_done('region', pid.attr_id, update_count); 7931}, function(err) { 7932show_message('Failed to update region attributes! '); 7933}); 7934return; 7935} 7936} 7937 7938function annotation_editor_on_metadata_update_done(type, attr_id, update_count) { 7939show_message('Updated ' + type + ' attributes of ' + update_count + ' ' + type + 's'); 7940// check if the updated attribute is one of the group variables 7941var i, n, type, attr_id; 7942n = _via_image_grid_group_var.length; 7943var clear_all_group = false; 7944for ( i = 0; i < n; ++i ) { 7945if ( _via_image_grid_group_var[i].type === type && 7946_via_image_grid_group_var[i].name === attr_id ) { 7947clear_all_group = true; 7948break; 7949} 7950} 7951_via_regions_group_color_init(); 7952_via_redraw_reg_canvas(); 7953 7954// @todo: it is wasteful to cancel the full set of groups. 7955// we should only cancel the groups that are affected by this update. 7956if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 7957if ( clear_all_group ) { 7958image_grid_show_all_project_images(); 7959} 7960} 7961} 7962 7963function annotation_editor_update_file_metadata(img_index_list, attr_id, new_value, new_checked) { 7964return new Promise( function(ok_callback, err_callback) { 7965var i, n, img_id, img_index; 7966n = img_index_list.length; 7967var update_count = 0; 7968for ( i = 0; i < n; ++i ) { 7969img_index = img_index_list[i]; 7970img_id = _via_image_id_list[img_index]; 7971 7972switch( _via_attributes['file'][attr_id].type ) { 7973case 'text': // fallback 7974case 'radio': // fallback 7975case 'dropdown': // fallback 7976case 'image': 7977_via_img_metadata[img_id].file_attributes[attr_id] = new_value; 7978update_count += 1; 7979break; 7980 7981case 'checkbox': 7982var option_id = new_value; 7983if ( _via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_id) ) { 7984if ( typeof(_via_img_metadata[img_id].file_attributes[attr_id]) !== 'object' ) { 7985var old_value = _via_img_metadata[img_id].file_attributes[attr_id]; 7986_via_img_metadata[img_id].file_attributes[attr_id] = {}; 7987if ( Object.keys(_via_attributes['file'][attr_id]['options']).includes(old_value) ) { 7988// transform existing value as checkbox option 7989_via_img_metadata[img_id].file_attributes[attr_id] = {}; 7990_via_img_metadata[img_id].file_attributes[attr_id][old_value] = true; 7991} 7992} 7993} else { 7994_via_img_metadata[img_id].file_attributes[attr_id] = {}; 7995} 7996if ( new_checked ) { 7997_via_img_metadata[img_id].file_attributes[attr_id][option_id] = true; 7998} else { 7999// false option values are not stored 8000delete _via_img_metadata[img_id].file_attributes[attr_id][option_id]; 8001} 8002update_count += 1; 8003break; 8004} 8005} 8006ok_callback(update_count); 8007}); 8008} 8009 8010function annotation_editor_update_region_metadata(img_index_list, region_id, attr_id, new_value, new_checked) { 8011return new Promise( function(ok_callback, err_callback) { 8012var i, n, img_id, img_index; 8013n = img_index_list.length; 8014var update_count = 0; 8015var region_list = []; 8016var j, m; 8017 8018if ( region_id === -1 ) { 8019// update all regions on a file (for image grid view) 8020for ( i = 0; i < n; ++i ) { 8021img_index = img_index_list[i]; 8022img_id = _via_image_id_list[img_index]; 8023 8024m = _via_img_metadata[img_id].regions.length; 8025for ( j = 0; j < m; ++j ) { 8026if ( ! image_grid_is_region_in_current_group( _via_img_metadata[img_id].regions[j].region_attributes ) ) { 8027continue; 8028} 8029 8030switch( _via_attributes['region'][attr_id].type ) { 8031case 'text': // fallback 8032case 'dropdown': // fallback 8033case 'radio': // fallback 8034case 'image': 8035_via_img_metadata[img_id].regions[j].region_attributes[attr_id] = new_value; 8036update_count += 1; 8037break; 8038case 'checkbox': 8039var option_id = new_value; 8040if ( _via_img_metadata[img_id].regions[j].region_attributes.hasOwnProperty(attr_id) ) { 8041if ( typeof(_via_img_metadata[img_id].regions[j].region_attributes[attr_id]) !== 'object' ) { 8042var old_value = _via_img_metadata[img_id].regions[j].region_attributes[attr_id]; 8043_via_img_metadata[img_id].regions[j].region_attributes[attr_id] = {} 8044if ( Object.keys(_via_attributes['region'][attr_id]['options']).includes(old_value) ) { 8045// transform existing value as checkbox option 8046_via_img_metadata[img_id].regions[j].region_attributes[attr_id][old_value] = true; 8047} 8048} 8049} else { 8050_via_img_metadata[img_id].regions[j].region_attributes[attr_id] = {}; 8051} 8052 8053if ( new_checked ) { 8054_via_img_metadata[img_id].regions[j].region_attributes[attr_id][option_id] = true; 8055} else { 8056// false option values are not stored 8057delete _via_img_metadata[img_id].regions[j].region_attributes[attr_id][option_id]; 8058} 8059update_count += 1; 8060break; 8061} 8062} 8063} 8064} else { 8065// update a single region in a file (for single image view) 8066// update all regions on a file (for image grid view) 8067for ( i = 0; i < n; ++i ) { 8068img_index = img_index_list[i]; 8069img_id = _via_image_id_list[img_index]; 8070 8071switch( _via_attributes['region'][attr_id].type ) { 8072case 'text': // fallback 8073case 'dropdown': // fallback 8074case 'radio': // fallback 8075case 'image': 8076_via_img_metadata[img_id].regions[region_id].region_attributes[attr_id] = new_value; 8077update_count += 1; 8078break; 8079case 'checkbox': 8080var option_id = new_value; 8081 8082if ( _via_img_metadata[img_id].regions[region_id].region_attributes.hasOwnProperty(attr_id) ) { 8083if ( typeof(_via_img_metadata[img_id].regions[region_id].region_attributes[attr_id]) !== 'object' ) { 8084var old_value = _via_img_metadata[img_id].regions[region_id].region_attributes[attr_id]; 8085_via_img_metadata[img_id].regions[region_id].region_attributes[attr_id] = {}; 8086if ( Object.keys(_via_attributes['region'][attr_id]['options']).includes(old_value) ) { 8087// transform existing value as checkbox option 8088_via_img_metadata[img_id].regions[region_id].region_attributes[attr_id][old_value] = true; 8089} 8090} 8091} else { 8092_via_img_metadata[img_id].regions[region_id].region_attributes[attr_id] = {}; 8093} 8094 8095if ( new_checked ) { 8096_via_img_metadata[img_id].regions[region_id].region_attributes[attr_id][option_id] = true; 8097} else { 8098// false option values are not stored 8099delete _via_img_metadata[img_id].regions[region_id].region_attributes[attr_id][option_id]; 8100} 8101update_count += 1; 8102break; 8103} 8104} 8105} 8106ok_callback(update_count); 8107}); 8108} 8109 8110function set_region_annotations_to_default_value(rid) { 8111var attr_id; 8112for ( attr_id in _via_attributes['region'] ) { 8113var attr_type = _via_attributes['region'][attr_id].type; 8114switch( attr_type ) { 8115case 'text': 8116var default_value = _via_attributes['region'][attr_id].default_value; 8117if ( typeof(default_value) !== 'undefined' ) { 8118_via_img_metadata[_via_image_id].regions[rid].region_attributes[attr_id] = default_value; 8119} 8120break; 8121case 'image': // fallback 8122case 'dropdown': // fallback 8123case 'radio': 8124_via_img_metadata[_via_image_id].regions[rid].region_attributes[attr_id] = ''; 8125var default_options = _via_attributes['region'][attr_id].default_options; 8126if ( typeof(default_options) !== 'undefined' ) { 8127_via_img_metadata[_via_image_id].regions[rid].region_attributes[attr_id] = Object.keys(default_options)[0]; 8128} 8129break; 8130 8131case 'checkbox': 8132_via_img_metadata[_via_image_id].regions[rid].region_attributes[attr_id] = {}; 8133var default_options = _via_attributes['region'][attr_id].default_options; 8134if ( typeof(default_options) !== 'underfined' ) { 8135var option_id; 8136for ( option_id in default_options ) { 8137var default_value = default_options[option_id]; 8138if ( typeof(default_value) !== 'underfined' ) { 8139_via_img_metadata[_via_image_id].regions[rid].region_attributes[attr_id][option_id] = default_value; 8140} 8141} 8142} 8143break; 8144} 8145} 8146} 8147 8148function set_file_annotations_to_default_value(image_id) { 8149var attr_id; 8150for ( attr_id in _via_attributes['file'] ) { 8151var attr_type = _via_attributes['file'][attr_id].type; 8152switch( attr_type ) { 8153case 'text': 8154var default_value = _via_attributes['file'][attr_id].default_value; 8155_via_img_metadata[image_id].file_attributes[attr_id] = default_value; 8156break; 8157case 'image': // fallback 8158case 'dropdown': // fallback 8159case 'radio': 8160_via_img_metadata[image_id].file_attributes[attr_id] = ''; 8161var default_options = _via_attributes['file'][attr_id].default_options; 8162_via_img_metadata[image_id].file_attributes[attr_id] = Object.keys(default_options)[0]; 8163break; 8164case 'checkbox': 8165_via_img_metadata[image_id].file_attributes[attr_id] = {}; 8166var default_options = _via_attributes['file'][attr_id].default_options; 8167var option_id; 8168for ( option_id in default_options ) { 8169var default_value = default_options[option_id]; 8170_via_img_metadata[image_id].file_attributes[attr_id][option_id] = default_value; 8171} 8172break; 8173} 8174} 8175} 8176 8177function annotation_editor_increase_panel_height() { 8178var p = document.getElementById('annotation_editor_panel'); 8179if ( _via_settings.ui.annotation_editor_height < 95 ) { 8180_via_settings.ui.annotation_editor_height += VIA_ANNOTATION_EDITOR_HEIGHT_CHANGE; 8181p.style.height = _via_settings.ui.annotation_editor_height + '%'; 8182} 8183} 8184 8185function annotation_editor_decrease_panel_height() { 8186var p = document.getElementById('annotation_editor_panel'); 8187if ( _via_settings.ui.annotation_editor_height > 10 ) { 8188_via_settings.ui.annotation_editor_height -= VIA_ANNOTATION_EDITOR_HEIGHT_CHANGE; 8189p.style.height = _via_settings.ui.annotation_editor_height + '%'; 8190} 8191} 8192 8193function annotation_editor_increase_content_size() { 8194var p = document.getElementById('annotation_editor_panel'); 8195if ( _via_settings.ui.annotation_editor_fontsize < 1.6 ) { 8196_via_settings.ui.annotation_editor_fontsize += VIA_ANNOTATION_EDITOR_FONTSIZE_CHANGE; 8197p.style.fontSize = _via_settings.ui.annotation_editor_fontsize + 'rem'; 8198} 8199} 8200 8201function annotation_editor_decrease_content_size() { 8202var p = document.getElementById('annotation_editor_panel'); 8203if ( _via_settings.ui.annotation_editor_fontsize > 0.4 ) { 8204_via_settings.ui.annotation_editor_fontsize -= VIA_ANNOTATION_EDITOR_FONTSIZE_CHANGE; 8205p.style.fontSize = _via_settings.ui.annotation_editor_fontsize + 'rem'; 8206} 8207} 8208 8209// 8210// via project 8211// 8212function project_set_name(name) { 8213_via_settings.project.name = name; 8214 8215var p = document.getElementById('project_name'); 8216p.value = _via_settings.project.name; 8217} 8218 8219function project_init_default_project() { 8220if ( ! _via_settings.hasOwnProperty('project') ) { 8221_via_settings.project = {}; 8222} 8223 8224project_set_name( project_get_default_project_name() ); 8225} 8226 8227function project_on_name_update(p) { 8228project_set_name(p.value); 8229} 8230 8231function project_get_default_project_name() { 8232const now = new Date(); 8233var MONTH_SHORT_NAME = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 8234var ts = now.getDate() + MONTH_SHORT_NAME[now.getMonth()] + now.getFullYear() + 8235'_' + now.getHours() + 'h' + now.getMinutes() + 'm'; 8236 8237var project_name = 'via_project_' + ts; 8238return project_name; 8239} 8240 8241function project_save_with_confirm() { 8242var config = {'title':'Save Project' }; 8243var input = { 'project_name': { type:'text', name:'Project Name', value:_via_settings.project.name, disabled:false, size:30 }, 8244'save_annotations':{ type:'checkbox', name:'Save region and file annotations (i.e. manual annotations)', checked:true, disabled:false}, 8245'save_attributes':{ type:'checkbox', name:'Save region and file attributes.', checked:true}, 8246'save_via_settings':{ type:'checkbox', name:'Save VIA application settings', checked:true}, 8247// 'save_base64_data':{ type:'checkbox', name:'Save base64 data of images (if present)', checked:false}, 8248// 'save_images':{type:'checkbox', 'name':'Save images <span class="warning">(WARNING: only recommended for projects containing small number of images)</span>', value:false}, 8249}; 8250 8251invoke_with_user_inputs(project_save_confirmed, input, config); 8252} 8253 8254function project_save_confirmed(input) { 8255if ( input.project_name.value !== _via_settings.project.name ) { 8256project_set_name(input.project_name.value); 8257} 8258 8259// via project 8260var _via_project = { '_via_settings': _via_settings, 8261'_via_img_metadata': _via_img_metadata, 8262'_via_attributes': _via_attributes, 8263'_via_data_format_version': '2.0.10', 8264'_via_image_id_list': _via_image_id_list 8265}; 8266 8267var filename = input.project_name.value + '.json'; 8268var data_blob = new Blob( [JSON.stringify(_via_project)], 8269{type: 'text/json;charset=utf-8'}); 8270 8271save_data_to_local_file(data_blob, filename); 8272 8273user_input_default_cancel_handler(); 8274} 8275 8276function project_open_select_project_file() { 8277if (invisible_file_input) { 8278invisible_file_input.accept = '.json'; 8279invisible_file_input.onchange = project_open; 8280invisible_file_input.removeAttribute('multiple'); 8281invisible_file_input.click(); 8282} 8283} 8284 8285function project_open(event) { 8286var selected_file = event.target.files[0]; 8287load_text_file(selected_file, project_open_parse_json_file); 8288} 8289 8290function project_open_parse_json_file(project_file_data) { 8291var d = JSON.parse(project_file_data); 8292if ( d['_via_settings'] && d['_via_img_metadata'] && d['_via_attributes'] ) { 8293// import settings 8294project_import_settings(d['_via_settings']); 8295 8296// clear existing data (if any) 8297_via_image_id_list = []; 8298_via_image_filename_list = []; 8299_via_img_count = 0; 8300_via_img_metadata = {}; 8301_via_img_fileref = {}; 8302_via_img_src = {}; 8303_via_attributes = { 'region':{}, 'file':{} }; 8304_via_buffer_remove_all(); 8305 8306// import image metadata 8307_via_img_metadata = {}; 8308for ( var img_id in d['_via_img_metadata'] ) { 8309if('filename' in d['_via_img_metadata'][img_id] && 8310'size' in d['_via_img_metadata'][img_id] && 8311'regions' in d['_via_img_metadata'][img_id] && 8312'file_attributes' in d['_via_img_metadata'][img_id]) { 8313if( !d.hasOwnProperty('_via_image_id_list') ) { 8314_via_image_id_list.push(img_id); 8315_via_image_filename_list.push( d['_via_img_metadata'][img_id].filename ); 8316} 8317 8318set_file_annotations_to_default_value(img_id); 8319_via_img_metadata[img_id] = d['_via_img_metadata'][img_id]; 8320_via_img_count += 1; 8321} else { 8322console.log('discarding malformed entry for ' + img_id + 8323': ' + JSON.stringify(d['_via_img_metadata'][img_id])); 8324} 8325} 8326 8327 8328// import image_id_list which records the order of images 8329if( d.hasOwnProperty('_via_image_id_list') ) { 8330_via_image_id_list = d['_via_image_id_list']; 8331for(var img_id_index in d['_via_image_id_list']) { 8332var img_id = d['_via_image_id_list'][img_id_index]; 8333_via_image_filename_list.push(_via_img_metadata[img_id]['filename']); 8334} 8335} 8336 8337// import attributes 8338_via_attributes = d['_via_attributes']; 8339project_parse_via_attributes_from_img_metadata(); 8340var fattr_id_list = Object.keys(_via_attributes['file']); 8341var rattr_id_list = Object.keys(_via_attributes['region']); 8342if ( rattr_id_list.length ) { 8343_via_attribute_being_updated = 'region'; 8344_via_current_attribute_id = rattr_id_list[0]; 8345} else { 8346if ( fattr_id_list.length ) { 8347_via_attribute_being_updated = 'file'; 8348_via_current_attribute_id = fattr_id_list[0]; 8349} 8350} 8351 8352if ( _via_settings.core.default_filepath !== '' ) { 8353_via_file_resolve_all_to_default_filepath(); 8354} 8355 8356show_message('Imported project [' + _via_settings['project'].name + '] with ' + _via_img_count + ' files.'); 8357 8358if ( _via_img_count > 0 ) { 8359_via_show_img(0); 8360update_img_fn_list(); 8361_via_reload_img_fn_list_table = true; 8362} 8363} else { 8364show_message('Cannot import project from a corrupt file!'); 8365} 8366} 8367 8368function project_parse_via_attributes_from_img_metadata() { 8369// parse _via_img_metadata to populate _via_attributes 8370var img_id, fa, ra; 8371 8372if ( ! _via_attributes.hasOwnProperty('file') ) { 8373_via_attributes['file'] = {}; 8374} 8375if ( ! _via_attributes.hasOwnProperty('region') ) { 8376_via_attributes['region'] = {}; 8377} 8378 8379for ( img_id in _via_img_metadata ) { 8380// file attributes 8381for ( fa in _via_img_metadata[img_id].file_attributes ) { 8382if ( ! _via_attributes['file'].hasOwnProperty(fa) ) { 8383_via_attributes['file'][fa] = {}; 8384_via_attributes['file'][fa]['type'] = 'text'; 8385} 8386} 8387// region attributes 8388var ri; 8389for ( ri = 0; ri < _via_img_metadata[img_id].regions.length; ++ri ) { 8390for ( ra in _via_img_metadata[img_id].regions[ri].region_attributes ) { 8391if ( ! _via_attributes['region'].hasOwnProperty(ra) ) { 8392_via_attributes['region'][ra] = {}; 8393_via_attributes['region'][ra]['type'] = 'text'; 8394} 8395} 8396} 8397} 8398} 8399 8400function project_import_settings(s) { 8401// @todo find a generic way to import into _via_settings 8402// only the components present in s (and not overwrite everything) 8403var k1; 8404for ( k1 in s ) { 8405if ( typeof( s[k1] ) === 'object' ) { 8406var k2; 8407for ( k2 in s[k1] ) { 8408if ( typeof( s[k1][k2] ) === 'object' ) { 8409var k3; 8410for ( k3 in s[k1][k2] ) { 8411_via_settings[k1][k2][k3] = s[k1][k2][k3]; 8412} 8413} else { 8414_via_settings[k1][k2] = s[k1][k2]; 8415} 8416} 8417} else { 8418_via_settings[k1] = s[k1]; 8419} 8420} 8421} 8422 8423function project_file_remove_with_confirm() { 8424var img_id = _via_image_id_list[_via_image_index]; 8425var filename = _via_img_metadata[img_id].filename; 8426var region_count = _via_img_metadata[img_id].regions.length; 8427 8428var config = {'title':'Remove File from Project' }; 8429var input = { 'img_index': { type:'text', name:'File Id', value:(_via_image_index+1), disabled:true, size:8 }, 8430'filename':{ type:'text', name:'Filename', value:filename, disabled:true, size:30}, 8431'region_count':{ type:'text', name:'Number of regions', disabled:true, value:region_count, size:8} 8432}; 8433 8434invoke_with_user_inputs(project_file_remove_confirmed, input, config); 8435} 8436 8437function project_file_remove_confirmed(input) { 8438var img_index = input.img_index.value - 1; 8439project_remove_file(img_index); 8440 8441if ( img_index === _via_img_count ) { 8442if ( _via_img_count === 0 ) { 8443_via_current_image_loaded = false; 8444show_home_panel(); 8445} else { 8446_via_show_img(img_index - 1); 8447} 8448} else { 8449_via_show_img(img_index); 8450} 8451_via_reload_img_fn_list_table = true; 8452update_img_fn_list(); 8453show_message('Removed file [' + input.filename.value + '] from project'); 8454user_input_default_cancel_handler(); 8455} 8456 8457 8458function project_remove_file(img_index) { 8459if ( img_index < 0 || img_index >= _via_img_count ) { 8460console.log('project_remove_file(): invalid img_index ' + img_index); 8461return; 8462} 8463var img_id = _via_image_id_list[img_index]; 8464 8465// remove img_index from all array 8466// this invalidates all image_index > img_index 8467_via_image_id_list.splice( img_index, 1 ); 8468_via_image_filename_list.splice( img_index, 1 ); 8469 8470var img_fn_list_index = _via_img_fn_list_img_index_list.indexOf(img_index); 8471if ( img_fn_list_index !== -1 ) { 8472_via_img_fn_list_img_index_list.splice( img_fn_list_index, 1 ); 8473} 8474 8475// clear all buffer 8476// @todo: it is wasteful to clear all the buffer instead of removing a single image 8477_via_buffer_remove_all(); 8478img_fn_list_clear_css_classname('buffered'); 8479 8480_via_clear_reg_canvas(); 8481delete _via_img_metadata[img_id]; 8482delete _via_img_src[img_id]; 8483delete _via_img_fileref[img_id]; 8484 8485_via_img_count -= 1; 8486} 8487 8488function project_add_new_file(filename, size, file_id) { 8489var img_id = file_id; 8490if ( typeof(img_id) === 'undefined' ) { 8491if ( typeof(size) === 'undefined' ) { 8492size = -1; 8493} 8494img_id = _via_get_image_id(filename, size); 8495} 8496 8497if ( ! _via_img_metadata.hasOwnProperty(img_id) ) { 8498_via_img_metadata[img_id] = new file_metadata(filename, size); 8499_via_image_id_list.push(img_id); 8500_via_image_filename_list.push(filename); 8501_via_img_count += 1; 8502} 8503return img_id; 8504} 8505 8506function project_file_add_local(event) { 8507var user_selected_images = event.target.files; 8508var original_image_count = _via_img_count; 8509 8510var new_img_index_list = []; 8511var discarded_file_count = 0; 8512for ( var i = 0; i < user_selected_images.length; ++i ) { 8513var filetype = user_selected_images[i].type.substr(0, 5); 8514if ( filetype === 'image' ) { 8515// check which filename in project matches the user selected file 8516var img_index = _via_image_filename_list.indexOf(user_selected_images[i].name); 8517if( img_index === -1) { 8518// a new file was added to project 8519var new_img_id = project_add_new_file(user_selected_images[i].name, 8520user_selected_images[i].size); 8521_via_img_fileref[new_img_id] = user_selected_images[i]; 8522set_file_annotations_to_default_value(new_img_id); 8523new_img_index_list.push( _via_image_id_list.indexOf(new_img_id) ); 8524} else { 8525// an existing file was resolved using browser's file selector 8526var img_id = _via_image_id_list[img_index]; 8527_via_img_fileref[img_id] = user_selected_images[i]; 8528_via_img_metadata[img_id]['size'] = user_selected_images[i].size; 8529} 8530} else { 8531discarded_file_count += 1; 8532} 8533} 8534 8535if ( _via_img_metadata ) { 8536var status_msg = 'Loaded ' + new_img_index_list.length + ' images.'; 8537if ( discarded_file_count ) { 8538status_msg += ' ( Discarded ' + discarded_file_count + ' non-image files! )'; 8539} 8540show_message(status_msg); 8541 8542if ( new_img_index_list.length ) { 8543// show first of newly added image 8544_via_show_img( new_img_index_list[0] ); 8545} else { 8546// show original image 8547_via_show_img ( _via_image_index ); 8548} 8549update_img_fn_list(); 8550} else { 8551show_message("Please upload some image files!"); 8552} 8553} 8554 8555function project_file_add_abs_path_with_input() { 8556var config = {'title':'Add File using Absolute Path' }; 8557var input = { 'absolute_path': { type:'text', name:'add one absolute path', placeholder:'/home/abhishek/image1.jpg', disabled:false, size:50 }, 8558'absolute_path_list': { type:'textarea', name:'or, add multiple paths (one path per line)', placeholder:'/home/abhishek/image1.jpg\n/home/abhishek/image2.jpg\n/home/abhishek/image3.png', disabled:false, rows:5, cols:80 } 8559}; 8560 8561invoke_with_user_inputs(project_file_add_abs_path_input_done, input, config); 8562} 8563 8564function project_file_add_abs_path_input_done(input) { 8565if ( input.absolute_path.value !== '' ) { 8566var abs_path = input.absolute_path.value.trim(); 8567var img_id = project_file_add_url(abs_path); 8568var img_index = _via_image_id_list.indexOf(img_id); 8569_via_show_img(img_index); 8570show_message('Added file at absolute path [' + abs_path + ']'); 8571update_img_fn_list(); 8572user_input_default_cancel_handler(); 8573} else { 8574if ( input.absolute_path_list.value !== '' ) { 8575var absolute_path_list_str = input.absolute_path_list.value; 8576import_files_url_from_csv(absolute_path_list_str); 8577} 8578} 8579} 8580 8581function project_file_add_url_with_input() { 8582var config = {'title':'Add File using URL' }; 8583var input = { 'url': { type:'text', name:'add one URL', placeholder:'http://www.robots.ox.ac.uk/~vgg/software/via/images/swan.jpg', disabled:false, size:50 }, 8584'url_list': { type:'textarea', name:'or, add multiple URL (one url per line)', placeholder:'http://www.example.com/image1.jpg\nhttp://www.example.com/image2.jpg\nhttp://www.example.com/image3.png', disabled:false, rows:5, cols:80 } 8585}; 8586 8587invoke_with_user_inputs(project_file_add_url_input_done, input, config); 8588} 8589 8590function project_file_add_url_input_done(input) { 8591if ( input.url.value !== '' ) { 8592var url = input.url.value.trim(); 8593var img_id = project_file_add_url(url); 8594var img_index = _via_image_id_list.indexOf(img_id); 8595show_message('Added file at url [' + url + ']'); 8596update_img_fn_list(); 8597_via_show_img(img_index); 8598user_input_default_cancel_handler(); 8599} else { 8600if ( input.url_list.value !== '' ) { 8601var url_list_str = input.url_list.value; 8602import_files_url_from_csv(url_list_str); 8603} 8604} 8605} 8606 8607function project_file_add_url(url) { 8608if ( url !== '' ) { 8609var size = -1; // convention: files added using url have size = -1 8610var img_id = _via_get_image_id(url, size); 8611 8612if ( ! _via_img_metadata.hasOwnProperty(img_id) ) { 8613img_id = project_add_new_file(url); 8614_via_img_src[img_id] = _via_img_metadata[img_id].filename; 8615set_file_annotations_to_default_value(img_id); 8616return img_id; 8617} 8618} 8619} 8620 8621function project_file_add_base64(filename, base64) { 8622var size = -1; // convention: files added using url have size = -1 8623var img_id = _via_get_image_id(filename, size); 8624 8625if ( ! _via_img_metadata.hasOwnProperty(img_id) ) { 8626img_id = project_add_new_file(filename, size); 8627_via_img_src[img_id] = base64; 8628set_file_annotations_to_default_value(img_id); 8629} 8630} 8631 8632function project_file_load_on_fail(img_index) { 8633var img_id = _via_image_id_list[img_index]; 8634_via_img_src[img_id] = ''; 8635_via_image_load_error[img_index] = true; 8636img_fn_list_ith_entry_error(img_index, true); 8637} 8638 8639function project_file_load_on_success(img_index) { 8640_via_image_load_error[img_index] = false; 8641img_fn_list_ith_entry_error(img_index, false); 8642} 8643 8644function project_settings_toggle() { 8645if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.SETTINGS ) { 8646show_single_image_view(); 8647} else { 8648project_settings_show(); 8649} 8650} 8651 8652function project_settings_show() { 8653set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.SETTINGS); 8654} 8655 8656function project_filepath_add_from_input(p, button) { 8657var new_path = document.getElementById(p).value.trim(); 8658var img_index = parseInt(button.getAttribute('value')); 8659project_filepath_add(new_path); 8660_via_show_img(img_index); 8661} 8662 8663function project_filepath_add(new_path) { 8664if ( path === '' ) { 8665return; 8666} 8667 8668if ( _via_settings.core.filepath.hasOwnProperty(new_path) ) { 8669return; 8670} else { 8671var largest_order = 0; 8672var path; 8673for ( path in _via_settings.core.filepath ) { 8674if ( _via_settings.core.filepath[path] > largest_order ) { 8675largest_order = _via_settings.core.filepath[path]; 8676} 8677} 8678_via_settings.core.filepath[new_path] = largest_order + 1; 8679 8680} 8681} 8682 8683function project_filepath_del(path) { 8684if ( _via_settings.core.filepath.hasOwnProperty(path) ) { 8685delete _via_settings.core.filepath[path]; 8686} 8687} 8688 8689function project_save_attributes() { 8690var blob_attr = {type: 'application/json;charset=utf-8'}; 8691var all_region_data_blob = new Blob( [ JSON.stringify(_via_attributes) ], blob_attr); 8692 8693save_data_to_local_file(all_region_data_blob, _via_settings.project.name + '_attributes.json'); 8694} 8695 8696function project_import_attributes_from_file(event) { 8697var selected_files = event.target.files; 8698for ( var i = 0; i < selected_files.length; ++i ) { 8699var file = selected_files[i]; 8700load_text_file(file, project_import_attributes_from_json); 8701} 8702} 8703 8704function project_import_attributes_from_json(data) { 8705try { 8706var d = JSON.parse(data); 8707var attr; 8708var fattr_count = 0; 8709var rattr_count = 0; 8710// process file attributes 8711for ( attr in d['file'] ) { 8712_via_attributes['file'][attr] = JSON.parse( JSON.stringify( d['file'][attr] ) ); 8713fattr_count += 1; 8714} 8715 8716// process region attributes 8717for ( attr in d['region'] ) { 8718_via_attributes['region'][attr] = JSON.parse( JSON.stringify( d['region'][attr] ) ); 8719rattr_count += 1; 8720} 8721 8722if ( fattr_count > 0 || rattr_count > 0 ) { 8723var fattr_id_list = Object.keys(_via_attributes['file']); 8724var rattr_id_list = Object.keys(_via_attributes['region']); 8725if ( rattr_id_list.length ) { 8726_via_attribute_being_updated = 'region'; 8727_via_current_attribute_id = rattr_id_list[0]; 8728} else { 8729if ( fattr_id_list.length ) { 8730_via_attribute_being_updated = 'file'; 8731_via_current_attribute_id = fattr_id_list[0]; 8732} 8733} 8734attribute_update_panel_set_active_button(); 8735update_attributes_update_panel(); 8736annotation_editor_update_content(); 8737} 8738show_message('Imported ' + fattr_count + ' file attributes and ' 8739+ rattr_count + ' region attributes'); 8740} catch (error) { 8741show_message('Failed to import attributes: [' + error + ']'); 8742} 8743} 8744 8745// 8746// image grid 8747// 8748function image_grid_init() { 8749var p = document.getElementById('image_grid_content'); 8750p.focus(); 8751p.addEventListener('mousedown', image_grid_mousedown_handler, false); 8752p.addEventListener('mouseup', image_grid_mouseup_handler, false); 8753p.addEventListener('dblclick', image_grid_dblclick_handler, false); 8754 8755image_grid_set_content_panel_height_fixed(); 8756 8757// select policy as defined in settings 8758var i, option; 8759var p = document.getElementById('image_grid_show_image_policy'); 8760var n = p.options.length; 8761for ( i = 0; i < n; ++i ) { 8762if ( p.options[i].value === _via_settings.ui.image_grid.show_image_policy ) { 8763p.selectedIndex = i; 8764break; 8765} 8766} 8767} 8768 8769function image_grid_update() { 8770if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 8771image_grid_set_content( _via_image_grid_img_index_list ); 8772} 8773} 8774 8775function image_grid_toggle() { 8776var p = document.getElementById('toolbar_image_grid_toggle'); 8777if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) { 8778image_grid_clear_all_groups(); 8779show_single_image_view(); 8780} else { 8781show_image_grid_view(); 8782} 8783} 8784 8785function image_grid_show_all_project_images() { 8786var all_img_index_list = []; 8787var i, n; 8788//n = _via_image_id_list.length; 8789n = _via_img_fn_list_img_index_list.length; 8790for ( i = 0; i < n; ++i ) { 8791all_img_index_list.push( _via_img_fn_list_img_index_list[i] ); 8792} 8793image_grid_clear_all_groups(); 8794 8795var p = document.getElementById('image_grid_toolbar_group_by_select'); 8796p.selectedIndex = 0; 8797 8798image_grid_set_content(all_img_index_list); 8799} 8800 8801function image_grid_clear_all_groups() { 8802var i, n; 8803n = _via_image_grid_group_var.length; 8804for ( i = 0; i < n; ++i ) { 8805image_grid_remove_html_group_panel( _via_image_grid_group_var[i] ); 8806image_grid_group_by_select_set_disabled( _via_image_grid_group_var[i].type, 8807_via_image_grid_group_var[i].name, 8808false); 8809} 8810_via_image_grid_group = {}; 8811_via_image_grid_group_var = []; 8812 8813} 8814 8815function image_grid_set_content(img_index_list) { 8816if ( img_index_list.length === 0 ) { 8817return; 8818} 8819if ( _via_image_grid_load_ongoing ) { 8820return; 8821} 8822 8823_via_image_grid_img_index_list = img_index_list.slice(0); 8824_via_image_grid_selected_img_index_list = img_index_list.slice(0); 8825 8826document.getElementById('image_grid_group_by_img_count').innerHTML = _via_image_grid_img_index_list.length; 8827 8828_via_image_grid_page_first_index = 0; 8829_via_image_grid_page_last_index = null; 8830_via_image_grid_stack_prev_page = []; 8831_via_image_grid_page_img_index_list = []; 8832 8833image_grid_clear_content(); 8834image_grid_set_content_panel_height_fixed(); 8835_via_image_grid_load_ongoing = true; 8836 8837var n = _via_image_grid_img_index_list.length; 8838switch ( _via_settings.ui.image_grid.show_image_policy ) { 8839case 'all': 8840_via_image_grid_page_img_index_list = _via_image_grid_img_index_list.slice(0); 8841break; 8842case 'first_mid_last': 8843if ( n < 3 ) { 8844var i; 8845for ( i = 0; i < n; ++i ) { 8846_via_image_grid_page_img_index_list.push( _via_image_grid_img_index_list[i] ); 8847} 8848} else { 8849_via_image_grid_page_img_index_list.push( _via_image_grid_img_index_list[0] ); 8850_via_image_grid_page_img_index_list.push( _via_image_grid_img_index_list[ Math.floor(n/2) ] ); 8851_via_image_grid_page_img_index_list.push( _via_image_grid_img_index_list[n-1] ); 8852} 8853break; 8854case 'even_indexed': 8855var i; 8856for ( i = 0; i < n; ++i ) { 8857if ( i % 2 !== 0 ) { // since the user views (i+1) based indexing 8858_via_image_grid_page_img_index_list.push( _via_image_grid_img_index_list[i] ); 8859} 8860} 8861break; 8862case 'odd_indexed': 8863var i; 8864for ( i = 0; i < n; ++i ) { 8865if ( i % 2 === 0 ) { // since the user views (i+1) based indexing 8866_via_image_grid_page_img_index_list.push( _via_image_grid_img_index_list[i] ); 8867} 8868} 8869break; 8870case 'gap5': // fallback 8871case 'gap25': // fallback 8872case 'gap50': // fallback 8873var del = parseInt( _via_settings.ui.image_grid.show_image_policy.substr( 'gap'.length ) ); 8874var i; 8875for ( i = 0; i < n; i = i + del ) { 8876_via_image_grid_page_img_index_list.push( _via_image_grid_img_index_list[i] ); 8877} 8878break; 8879} 8880 8881_via_image_grid_visible_img_index_list = []; 8882 8883image_grid_update_sel_count_html(); 8884annotation_editor_update_content(); 8885 8886image_grid_content_append_img( _via_image_grid_page_first_index ); 8887 8888show_message('[Click] toggles selection, ' + 8889'[Shift + Click] selects everything a image, ' + 8890'[Click] or [Ctrl + Click] removes selection of all subsequent or preceeding images.'); 8891} 8892 8893function image_grid_clear_content() { 8894var img_container = document.getElementById('image_grid_content_img'); 8895var img_rshape = document.getElementById('image_grid_content_rshape'); 8896img_container.innerHTML = ''; 8897img_rshape.innerHTML = ''; 8898_via_image_grid_visible_img_index_list = []; 8899} 8900 8901function image_grid_set_content_panel_height_fixed() { 8902var pc = document.getElementById('image_grid_content'); 8903var de = document.documentElement; 8904pc.style.height = (de.clientHeight - 5.5*ui_top_panel.offsetHeight) + 'px'; 8905} 8906 8907// We do not know how many images will fit in the display area. 8908// Therefore, we add images one-by-one until overflow of parent 8909// container is detected. 8910function image_grid_content_append_img( img_grid_index ) { 8911var img_index = _via_image_grid_page_img_index_list[img_grid_index]; 8912var html_img_id = image_grid_get_html_img_id(img_index); 8913var img_id = _via_image_id_list[img_index]; 8914var e = document.createElement('img'); 8915if ( _via_img_fileref[img_id] instanceof File ) { 8916var img_reader = new FileReader(); 8917img_reader.addEventListener( "error", function() { 8918//@todo 8919}, false); 8920img_reader.addEventListener( "load", function() { 8921e.src = img_reader.result; 8922}, false); 8923img_reader.readAsDataURL( _via_img_fileref[img_id] ); 8924} else { 8925e.src = _via_img_src[img_id]; 8926} 8927e.setAttribute('id', html_img_id); 8928e.setAttribute('height', _via_settings.ui.image_grid.img_height + 'px'); 8929e.setAttribute('title', '[' + (img_index+1) + '] ' + _via_img_metadata[img_id].filename); 8930 8931e.addEventListener('load', image_grid_on_img_load, false); 8932e.addEventListener('error', image_grid_on_img_error, false); 8933document.getElementById('image_grid_content_img').appendChild(e); 8934} 8935 8936function image_grid_on_img_load(e) { 8937var img = e.target; 8938var img_index = image_grid_parse_html_img_id(img.id); 8939project_file_load_on_success(img_index); 8940 8941image_grid_add_img_if_possible(img); 8942} 8943 8944function image_grid_on_img_error(e) { 8945var img = e.target; 8946var img_index = image_grid_parse_html_img_id(img.id); 8947project_file_load_on_fail(img_index); 8948image_grid_add_img_if_possible(img); 8949} 8950 8951function image_grid_add_img_if_possible(img) { 8952var img_index = image_grid_parse_html_img_id(img.id); 8953 8954var p = document.getElementById('image_grid_content_img'); 8955var img_bottom_right_corner = parseInt(img.offsetTop) + parseInt(img.height); 8956if ( p.clientHeight < img_bottom_right_corner ) { 8957// stop as addition of this image caused overflow of parent container 8958var img_container = document.getElementById('image_grid_content_img'); 8959img_container.removeChild(img); 8960 8961if ( _via_settings.ui.image_grid.show_region_shape ) { 8962image_grid_page_show_all_regions(); 8963} 8964_via_image_grid_load_ongoing = false; 8965 8966var index = _via_image_grid_page_img_index_list.indexOf(img_index); 8967_via_image_grid_page_last_index = index; 8968 8969// setup prev, next navigation 8970var info = document.getElementById('image_grid_nav'); 8971var html = []; 8972var first_index = _via_image_grid_page_first_index; 8973var last_index = _via_image_grid_page_last_index - 1; 8974html.push('<span>Showing ' + (first_index + 1) + 8975' to ' + (last_index + 1) + ' :</span>'); 8976if ( _via_image_grid_stack_prev_page.length ) { 8977html.push('<span class="text_button" onclick="image_grid_page_prev()">Prev</span>'); 8978} else { 8979html.push('<span>Prev</span>'); 8980} 8981html.push('<span class="text_button" onclick="image_grid_page_next()">Next</span'); 8982info.innerHTML = html.join(''); 8983} else { 8984// process this image and trigger addition of next image in sequence 8985var img_fn_list_index = _via_image_grid_page_img_index_list.indexOf(img_index); 8986var next_img_fn_list_index = img_fn_list_index + 1; 8987 8988_via_image_grid_visible_img_index_list.push( img_index ); 8989var is_selected = ( _via_image_grid_selected_img_index_list.indexOf(img_index) !== -1 ); 8990if ( ! is_selected ) { 8991image_grid_update_img_select(img_index, 'unselect'); 8992} 8993 8994if ( next_img_fn_list_index !== _via_image_grid_page_img_index_list.length ) { 8995if ( _via_image_grid_load_ongoing ) { 8996image_grid_content_append_img( img_fn_list_index + 1 ); 8997} else { 8998// image grid load operation was cancelled 8999_via_image_grid_page_last_index = _via_image_grid_page_first_index; // load this page again 9000 9001var info = document.getElementById('image_grid_nav'); 9002var html = []; 9003html.push('<span>Cancelled :</span>'); 9004if ( _via_image_grid_stack_prev_page.length ) { 9005html.push('<span class="text_button" onclick="image_grid_page_prev()">Prev</span>'); 9006} else { 9007html.push('<span>Prev</span>'); 9008} 9009html.push('<span class="text_button" onclick="image_grid_page_next()">Next</span'); 9010info.innerHTML = html.join(''); 9011} 9012} else { 9013// last page 9014var index = _via_image_grid_page_img_index_list.indexOf(img_index); 9015_via_image_grid_page_last_index = index; 9016 9017if ( _via_settings.ui.image_grid.show_region_shape ) { 9018image_grid_page_show_all_regions(); 9019} 9020_via_image_grid_load_ongoing = false; 9021 9022// setup prev, next navigation 9023var info = document.getElementById('image_grid_nav'); 9024var html = []; 9025var first_index = _via_image_grid_page_first_index; 9026var last_index = _via_image_grid_page_last_index; 9027html.push('<span>Showing ' + (first_index + 1) + 9028' to ' + (last_index + 1) + ' (end) </span>'); 9029if ( _via_image_grid_stack_prev_page.length ) { 9030html.push('<span class="text_button" onclick="image_grid_page_prev()">Prev</span>'); 9031} else { 9032html.push('<span>Prev</span>'); 9033} 9034html.push('<span>Next</span'); 9035 9036info.innerHTML = html.join(''); 9037} 9038} 9039} 9040 9041function image_grid_onchange_show_image_policy(p) { 9042_via_settings.ui.image_grid.show_image_policy = p.options[p.selectedIndex].value; 9043image_grid_set_content(_via_image_grid_img_index_list); 9044} 9045 9046function image_grid_page_show_all_regions() { 9047var all_promises = []; 9048if ( _via_settings.ui.image_grid.show_region_shape ) { 9049var p = document.getElementById('image_grid_content_img'); 9050var n = p.childNodes.length; 9051var i; 9052for ( i = 0; i < n; ++i ) { 9053// draw region shape into global canvas for image grid 9054var img_index = image_grid_parse_html_img_id( p.childNodes[i].id ); 9055var img_param = []; // [width, height, originalWidth, originalHeight, x, y] 9056img_param.push( parseInt(p.childNodes[i].width) ); 9057img_param.push( parseInt(p.childNodes[i].height) ); 9058img_param.push( parseInt(p.childNodes[i].naturalWidth) ); 9059img_param.push( parseInt(p.childNodes[i].naturalHeight) ); 9060img_param.push( parseInt(p.childNodes[i].offsetLeft) + parseInt(p.childNodes[i].clientLeft) ); 9061img_param.push( parseInt(p.childNodes[i].offsetTop) + parseInt(p.childNodes[i].clientTop) ); 9062var promise = image_grid_show_region_shape( img_index, img_param ); 9063all_promises.push( promise ); 9064} 9065// @todo: ensure that all promises are fulfilled 9066} 9067} 9068 9069function image_grid_is_region_in_current_group(r) { 9070var i, n; 9071n = _via_image_grid_group_var.length; 9072if ( n === 0 ) { 9073return true; 9074} 9075 9076for ( i = 0; i < n; ++i ) { 9077if ( _via_image_grid_group_var[i].type === 'region' ) { 9078var group_value = _via_image_grid_group_var[i].values[ _via_image_grid_group_var[i].current_value_index ]; 9079if ( r[_via_image_grid_group_var[i].name] != group_value ) { 9080return false; 9081} 9082} 9083} 9084return true; 9085} 9086 9087function image_grid_show_region_shape(img_index, img_param) { 9088return new Promise( function(ok_callback, err_callback) { 9089var i; 9090var img_id = _via_image_id_list[img_index]; 9091var html_img_id = image_grid_get_html_img_id(img_index); 9092var n = _via_img_metadata[img_id].regions.length; 9093var is_in_group = false; 9094for ( i = 0; i < n; ++i ) { 9095if ( ! image_grid_is_region_in_current_group( _via_img_metadata[img_id].regions[i].region_attributes ) ) { 9096// skip drawing this region which is not in current group 9097continue; 9098} 9099 9100var r = _via_img_metadata[img_id].regions[i].shape_attributes; 9101var dimg; // region coordinates in original image space 9102switch( r.name ) { 9103case VIA_REGION_SHAPE.RECT: 9104dimg = [ r['x'], r['y'], r['x']+r['width'], r['y']+r['height'] ]; 9105break; 9106case VIA_REGION_SHAPE.CIRCLE: 9107dimg = [ r['cx'], r['cy'], r['cx']+r['r'], r['cy']+r['r'] ]; 9108break; 9109case VIA_REGION_SHAPE.ELLIPSE: 9110dimg = [ r['cx'], r['cy'], r['cx']+r['rx'], r['cy']+r['ry'] ]; 9111break; 9112case VIA_REGION_SHAPE.POLYLINE: // handled by POLYGON 9113case VIA_REGION_SHAPE.POLYGON: 9114var j; 9115dimg = []; 9116for ( j = 0; j < r['all_points_x'].length; ++j ) { 9117dimg.push( r['all_points_x'][j] ); 9118dimg.push( r['all_points_y'][j] ); 9119} 9120break; 9121case VIA_REGION_SHAPE.POINT: 9122dimg = [ r['cx'], r['cy'] ]; 9123break; 9124} 9125var scale_factor = img_param[1] / img_param[3]; // new_height / original height 9126var offset_x = img_param[4]; 9127var offset_y = img_param[5]; 9128var r2 = new _via_region( r.name, i, dimg, scale_factor, offset_x, offset_y); 9129var r2_svg = r2.get_svg_element(); 9130r2_svg.setAttribute('id', image_grid_get_html_region_id(img_index, i)); 9131r2_svg.setAttribute('class', html_img_id); 9132r2_svg.setAttribute('fill', _via_settings.ui.image_grid.rshape_fill); 9133//r2_svg.setAttribute('fill-opacity', _via_settings.ui.image_grid.rshape_fill_opacity); 9134r2_svg.setAttribute('stroke', _via_settings.ui.image_grid.rshape_stroke); 9135r2_svg.setAttribute('stroke-width', _via_settings.ui.image_grid.rshape_stroke_width); 9136 9137document.getElementById('image_grid_content_rshape').appendChild(r2_svg); 9138} 9139}); 9140} 9141 9142function image_grid_image_size_increase() { 9143var new_img_height = _via_settings.ui.image_grid.img_height + VIA_IMAGE_GRID_IMG_HEIGHT_CHANGE; 9144_via_settings.ui.image_grid.img_height = new_img_height; 9145 9146_via_image_grid_page_last_index = null; 9147image_grid_update(); 9148} 9149 9150function image_grid_image_size_decrease() { 9151var new_img_height = _via_settings.ui.image_grid.img_height - VIA_IMAGE_GRID_IMG_HEIGHT_CHANGE; 9152if ( new_img_height > 1 ) { 9153_via_settings.ui.image_grid.img_height = new_img_height; 9154_via_image_grid_page_last_index = null; 9155image_grid_update(); 9156} 9157} 9158 9159function image_grid_image_size_reset() { 9160var new_img_height = _via_settings.ui.image_grid.img_height; 9161if ( new_img_height > 1 ) { 9162_via_settings.ui.image_grid.img_height = new_img_height; 9163_via_image_grid_page_last_index = null; 9164image_grid_update(); 9165} 9166} 9167 9168function image_grid_mousedown_handler(e) { 9169e.preventDefault(); 9170_via_image_grid_mousedown_img_index = image_grid_parse_html_img_id(e.target.id); 9171} 9172 9173function image_grid_mouseup_handler(e) { 9174e.preventDefault(); 9175var last_mouseup_img_index = _via_image_grid_mouseup_img_index; 9176_via_image_grid_mouseup_img_index = image_grid_parse_html_img_id(e.target.id); 9177if ( isNaN(_via_image_grid_mousedown_img_index) || 9178isNaN(_via_image_grid_mouseup_img_index)) { 9179last_mouseup_img_index = _via_image_grid_img_index_list[0]; 9180image_grid_group_select_none(); 9181return; 9182} 9183 9184var mousedown_img_arr_index = _via_image_grid_img_index_list.indexOf(_via_image_grid_mousedown_img_index); 9185var mouseup_img_arr_index = _via_image_grid_img_index_list.indexOf(_via_image_grid_mouseup_img_index); 9186 9187var start = -1; 9188var end = -1; 9189var operation = 'select'; // {'select', 'unselect', 'toggle'} 9190if ( mousedown_img_arr_index === mouseup_img_arr_index ) { 9191if ( e.shiftKey ) { 9192// select all elements until this element 9193start = _via_image_grid_img_index_list.indexOf(last_mouseup_img_index) + 1; 9194end = mouseup_img_arr_index + 1; 9195} else { 9196// toggle selection of single image 9197start = mousedown_img_arr_index; 9198end = start + 1; 9199operation = 'toggle'; 9200} 9201} else { 9202if ( mousedown_img_arr_index < mouseup_img_arr_index ) { 9203start = mousedown_img_arr_index; 9204end = mouseup_img_arr_index + 1; 9205} else { 9206start = mouseup_img_arr_index + 1; 9207end = mousedown_img_arr_index; 9208} 9209operation = 'toggle'; 9210} 9211 9212if ( start > end ) { 9213return; 9214} 9215 9216var i, img_index; 9217for ( i = start; i < end; ++i ) { 9218img_index = _via_image_grid_img_index_list[i]; 9219image_grid_update_img_select(img_index, operation); 9220} 9221image_grid_update_sel_count_html(); 9222annotation_editor_update_content(); 9223} 9224 9225function image_grid_update_sel_count_html() { 9226document.getElementById('image_grid_group_by_sel_img_count').innerHTML = _via_image_grid_selected_img_index_list.length; 9227} 9228 9229// state \in {'select', 'unselect', 'toggle'} 9230function image_grid_update_img_select(img_index, state) { 9231var html_img_id = image_grid_get_html_img_id(img_index); 9232var is_selected = ( _via_image_grid_selected_img_index_list.indexOf(img_index) !== -1 ); 9233if (state === 'toggle' ) { 9234if ( is_selected ) { 9235state = 'unselect'; 9236} else { 9237state = 'select'; 9238} 9239} 9240 9241switch(state) { 9242case 'select': 9243if ( ! is_selected ) { 9244_via_image_grid_selected_img_index_list.push(img_index); 9245} 9246if ( _via_image_grid_visible_img_index_list.indexOf(img_index) !== -1 ) { 9247document.getElementById(html_img_id).classList.remove('not_sel'); 9248} 9249break; 9250case 'unselect': 9251if ( is_selected ) { 9252var arr_index = _via_image_grid_selected_img_index_list.indexOf(img_index); 9253_via_image_grid_selected_img_index_list.splice(arr_index, 1); 9254} 9255if ( _via_image_grid_visible_img_index_list.indexOf(img_index) !== -1 ) { 9256document.getElementById(html_img_id).classList.add('not_sel'); 9257} 9258break; 9259} 9260} 9261 9262function image_grid_group_select_all() { 9263image_grid_group_set_all_selection_state('select'); 9264image_grid_update_sel_count_html(); 9265annotation_editor_update_content(); 9266show_message('Selected all images in the current group'); 9267} 9268 9269function image_grid_group_select_none() { 9270image_grid_group_set_all_selection_state('unselect'); 9271image_grid_update_sel_count_html(); 9272annotation_editor_update_content(); 9273show_message('Removed selection of all images in the current group'); 9274} 9275 9276function image_grid_group_set_all_selection_state(state) { 9277var i, img_index; 9278for ( i = 0; i < _via_image_grid_img_index_list.length; ++i ) { 9279img_index = _via_image_grid_img_index_list[i]; 9280image_grid_update_img_select(img_index, state); 9281} 9282} 9283 9284function image_grid_group_toggle_select_all() { 9285if ( _via_image_grid_selected_img_index_list.length === _via_image_grid_img_index_list.length ) { 9286image_grid_group_select_none(); 9287} else { 9288image_grid_group_select_all(); 9289} 9290} 9291 9292function image_grid_parse_html_img_id(html_img_id) { 9293var img_index = html_img_id.substr(2); 9294return parseInt(img_index); 9295} 9296 9297function image_grid_get_html_img_id(img_index) { 9298return 'im' + img_index; 9299} 9300 9301function image_grid_parse_html_region_id(html_region_id) { 9302var chunks = html_region_id.split('_'); 9303if ( chunks.length === 2 ) { 9304var img_index = parseInt(chunks[0].substr(2)); 9305var region_id = parseInt(chunks[1].substr(2)); 9306return {'img_index':img_index, 'region_id':region_id}; 9307} else { 9308console.log('image_grid_parse_html_region_id(): invalid html_region_id'); 9309return {}; 9310} 9311} 9312 9313function image_grid_get_html_region_id(img_index, region_id) { 9314return image_grid_get_html_img_id(img_index) + '_rs' + region_id; 9315} 9316 9317function image_grid_dblclick_handler(e) { 9318_via_image_index = image_grid_parse_html_img_id(e.target.id); 9319show_single_image_view(); 9320} 9321 9322function image_grid_toolbar_update_group_by_select() { 9323var p = document.getElementById('image_grid_toolbar_group_by_select'); 9324p.innerHTML = ''; 9325 9326var o = document.createElement('option'); 9327o.setAttribute('value', ''); 9328o.setAttribute('selected', 'selected'); 9329o.innerHTML = 'All Images'; 9330p.appendChild(o); 9331 9332// add file attributes 9333var fattr; 9334for ( fattr in _via_attributes['file'] ) { 9335var o = document.createElement('option'); 9336o.setAttribute('value', image_grid_toolbar_group_by_select_get_html_id('file', fattr)); 9337o.innerHTML = '[file] ' + fattr; 9338p.appendChild(o); 9339} 9340 9341// add region attributes 9342var rattr; 9343for ( rattr in _via_attributes['region'] ) { 9344var o = document.createElement('option'); 9345o.setAttribute('value', image_grid_toolbar_group_by_select_get_html_id('region', rattr)); 9346o.innerHTML = '[region] ' + rattr; 9347p.appendChild(o); 9348} 9349} 9350 9351function image_grid_toolbar_group_by_select_get_html_id(type, name) { 9352if ( type === 'file' ) { 9353return 'f_' + name; 9354} 9355if ( type === 'region' ) { 9356return 'r_' + name; 9357} 9358} 9359 9360function image_grid_toolbar_group_by_select_parse_html_id(id) { 9361if ( id.startsWith('f_') ) { 9362return { 'attr_type':'file', 'attr_name':id.substr(2) }; 9363} 9364if ( id.startsWith('r_') ) { 9365return { 'attr_type':'region', 'attr_name':id.substr(2) }; 9366} 9367} 9368 9369function image_grid_toolbar_onchange_group_by_select(p) { 9370if ( p.options[p.selectedIndex].value === '' ) { 9371image_grid_show_all_project_images(); 9372return; 9373} 9374 9375var v = image_grid_toolbar_group_by_select_parse_html_id( p.options[p.selectedIndex].value ); 9376var attr_type = v.attr_type; 9377var attr_name = v.attr_name; 9378image_grid_group_by(attr_type, attr_name); 9379 9380image_grid_group_by_select_set_disabled(attr_type, attr_name, true); 9381p.blur(); // to avoid adding new groups using keyboard keys as dropdown is still in focus 9382} 9383 9384function image_grid_remove_html_group_panel(d) { 9385var p = document.getElementById('group_toolbar_' + d.group_index); 9386document.getElementById('image_grid_group_panel').removeChild(p); 9387} 9388 9389function image_grid_add_html_group_panel(d) { 9390var p = document.createElement('div'); 9391p.classList.add('image_grid_group_toolbar'); 9392p.setAttribute('id', 'group_toolbar_' + d.group_index); 9393 9394var del = document.createElement('span'); 9395del.classList.add('text_button'); 9396del.setAttribute('onclick', 'image_grid_remove_group_by(this)'); 9397del.innerHTML = '×'; 9398p.appendChild(del); 9399 9400var prev = document.createElement('button'); 9401prev.innerHTML = '<'; 9402prev.setAttribute('value', d.group_index); 9403prev.setAttribute('onclick', 'image_grid_group_prev(this)'); 9404p.appendChild(prev); 9405 9406var sel = document.createElement('select'); 9407sel.setAttribute('id', image_grid_group_select_get_html_id(d.group_index)); 9408sel.setAttribute('onchange', 'image_grid_group_value_onchange(this)'); 9409var i, value; 9410var n = d.values.length; 9411var current_value = d.values[ d.current_value_index ]; 9412for ( i = 0; i < n; ++i ) { 9413value = d.values[i]; 9414var o = document.createElement('option'); 9415o.setAttribute('value', value); 9416o.innerHTML = (i+1) + '/' + n + ': ' + d.name + ' = ' + value; 9417if ( value === current_value ) { 9418o.setAttribute('selected', 'selected'); 9419} 9420 9421sel.appendChild(o); 9422} 9423p.appendChild(sel); 9424 9425var next = document.createElement('button'); 9426next.innerHTML = '>'; 9427next.setAttribute('value', d.group_index); 9428next.setAttribute('onclick', 'image_grid_group_next(this)'); 9429p.appendChild(next); 9430 9431document.getElementById('image_grid_group_panel').appendChild(p); 9432} 9433 9434function image_grid_group_panel_set_selected_value(group_index) { 9435var sel = document.getElementById(image_grid_group_select_get_html_id(group_index)); 9436sel.selectedIndex = _via_image_grid_group_var[group_index].current_value_index; 9437} 9438 9439function image_grid_group_panel_set_options(group_index) { 9440var sel = document.getElementById(image_grid_group_select_get_html_id(group_index)); 9441sel.innerHTML = ''; 9442 9443var i, value; 9444var n = _via_image_grid_group_var[group_index].values.length; 9445var name = _via_image_grid_group_var[group_index].name; 9446var current_value = _via_image_grid_group_var[group_index].values[ _via_image_grid_group_var[group_index].current_value_index ] 9447for ( i = 0; i < n; ++i ) { 9448value = _via_image_grid_group_var[group_index].values[i]; 9449var o = document.createElement('option'); 9450o.setAttribute('value', value); 9451o.innerHTML = (i+1) + '/' + n + ': ' + name + ' = ' + value; 9452if ( value === current_value ) { 9453o.setAttribute('selected', 'selected'); 9454} 9455sel.appendChild(o); 9456} 9457} 9458 9459function image_grid_group_select_get_html_id(group_index) { 9460return 'gi_' + group_index; 9461} 9462 9463function image_grid_group_select_parse_html_id(id) { 9464return parseInt(id.substr(3)); 9465} 9466 9467function image_grid_group_by_select_set_disabled(type, name, is_disabled) { 9468var p = document.getElementById('image_grid_toolbar_group_by_select'); 9469var sel_option_value = image_grid_toolbar_group_by_select_get_html_id(type, name); 9470 9471var n = p.options.length; 9472var option_value; 9473var i; 9474for ( i = 0; i < n; ++i ) { 9475if ( sel_option_value === p.options[i].value ) { 9476if ( is_disabled ) { 9477p.options[i].setAttribute('disabled', 'disabled'); 9478} else { 9479p.options[i].removeAttribute('disabled'); 9480} 9481break; 9482} 9483} 9484} 9485 9486function image_grid_remove_group_by(p) { 9487var prefix = 'group_toolbar_'; 9488var group_index = parseInt( p.parentNode.id.substr( prefix.length ) ); 9489 9490if ( group_index === 0 ) { 9491image_grid_show_all_project_images(); 9492} else { 9493// merge all groups that are child of group_index 9494image_grid_group_by_merge(_via_image_grid_group, 0, group_index); 9495 9496var n = _via_image_grid_group_var.length; 9497var p = document.getElementById('image_grid_group_panel'); 9498var group_panel_id; 9499var i; 9500for ( i = group_index; i < n; ++i ) { 9501image_grid_remove_html_group_panel( _via_image_grid_group_var[i] ); 9502image_grid_group_by_select_set_disabled( _via_image_grid_group_var[i].type, 9503_via_image_grid_group_var[i].name, 9504false); 9505} 9506_via_image_grid_group_var.splice(group_index); 9507 9508image_grid_set_content_to_current_group(); 9509} 9510} 9511 9512function image_grid_group_by(type, name) { 9513if ( Object.keys(_via_image_grid_group).length === 0 ) { 9514// first group 9515var img_index_array = []; 9516var n = _via_img_fn_list_img_index_list.length; 9517var i; 9518for ( i = 0; i < n; ++i ) { 9519img_index_array.push( _via_img_fn_list_img_index_list[i] ); 9520} 9521 9522_via_image_grid_group = image_grid_split_array_to_group(img_index_array, type, name); 9523var new_group_values = Object.keys(_via_image_grid_group); 9524_via_image_grid_group_var = []; 9525_via_image_grid_group_var.push( { 'type':type, 'name':name, 'current_value_index':0, 'values':new_group_values, 'group_index':0 } ); 9526 9527image_grid_add_html_group_panel(_via_image_grid_group_var[0]); 9528} else { 9529image_grid_group_split_all_arrays( _via_image_grid_group, type, name ); 9530 9531var i, n, value; 9532var current_group_value = _via_image_grid_group; 9533n = _via_image_grid_group_var.length; 9534 9535for ( i = 0; i < n; ++i ) { 9536value = _via_image_grid_group_var[i].values[ _via_image_grid_group_var[i].current_value_index ]; 9537current_group_value = current_group_value[ value ]; 9538} 9539var new_group_values = Object.keys(current_group_value); 9540var group_var_index = _via_image_grid_group_var.length; 9541_via_image_grid_group_var.push( { 'type':type, 'name':name, 'current_value_index':0, 'values':new_group_values, 'group_index':group_var_index } ); 9542image_grid_add_html_group_panel( _via_image_grid_group_var[group_var_index] ); 9543} 9544 9545image_grid_set_content_to_current_group(); 9546} 9547 9548function image_grid_group_by_merge(group, current_level, target_level) { 9549var child_value; 9550var group_data = []; 9551if ( current_level === target_level ) { 9552return image_grid_group_by_collapse(group); 9553} else { 9554for ( child_value in group ) { 9555group[child_value] = image_grid_group_by_merge(group[child_value], current_level + 1, target_level); 9556} 9557} 9558} 9559 9560function image_grid_group_by_collapse(group) { 9561var child_value; 9562var child_collapsed_value; 9563var group_data = []; 9564for ( child_value in group ) { 9565if ( Array.isArray(group[child_value]) ) { 9566group_data = group_data.concat(group[child_value]); 9567} else { 9568group_data = group_data.concat(image_grid_group_by_collapse(group[child_value])); 9569} 9570} 9571return group_data; 9572} 9573 9574// recursively collapse all arrays to list 9575function image_grid_group_split_all_arrays(group, type, name) { 9576if ( Array.isArray(group) ) { 9577return image_grid_split_array_to_group(group, type, name); 9578} else { 9579var group_value; 9580for ( group_value in group ) { 9581if ( Array.isArray( group[group_value] ) ) { 9582group[group_value] = image_grid_split_array_to_group(group[group_value], type, name); 9583} else { 9584image_grid_group_split_all_arrays(group[group_value], type, name); 9585} 9586} 9587} 9588} 9589 9590function image_grid_split_array_to_group(img_index_array, attr_type, attr_name) { 9591var grp = {}; 9592var img_index, img_id, i; 9593var n = img_index_array.length; 9594var attr_value; 9595 9596switch(attr_type) { 9597case 'file': 9598for ( i = 0; i < n; ++i ) { 9599img_index = img_index_array[i]; 9600img_id = _via_image_id_list[img_index]; 9601if ( _via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_name) ) { 9602attr_value = _via_img_metadata[img_id].file_attributes[attr_name]; 9603 9604if ( ! grp.hasOwnProperty(attr_value) ) { 9605grp[attr_value] = []; 9606} 9607grp[attr_value].push(img_index); 9608} 9609} 9610break; 9611case 'region': 9612var j; 9613var region_count; 9614for ( i = 0; i < n; ++i ) { 9615img_index = img_index_array[i]; 9616img_id = _via_image_id_list[img_index]; 9617region_count = _via_img_metadata[img_id].regions.length; 9618for ( j = 0; j < region_count; ++j ) { 9619if ( _via_img_metadata[img_id].regions[j].region_attributes.hasOwnProperty(attr_name) ) { 9620attr_value = _via_img_metadata[img_id].regions[j].region_attributes[attr_name]; 9621 9622if ( ! grp.hasOwnProperty(attr_value) ) { 9623grp[attr_value] = []; 9624} 9625if ( grp[attr_value].includes(img_index) ) { 9626continue; 9627} else { 9628grp[attr_value].push(img_index); 9629} 9630} 9631} 9632} 9633break; 9634} 9635return grp; 9636} 9637 9638function image_grid_group_next(p) { 9639var group_index = parseInt( p.value ); 9640var group_value_list = _via_image_grid_group_var[group_index].values; 9641var n = group_value_list.length; 9642var current_index = _via_image_grid_group_var[group_index].current_value_index; 9643var next_index = current_index + 1; 9644if ( next_index >= n ) { 9645if ( group_index === 0 ) { 9646next_index = next_index - n; 9647image_grid_jump_to_group(group_index, next_index); 9648} else { 9649// next of parent group 9650var parent_group_index = group_index - 1; 9651var parent_current_val_index = _via_image_grid_group_var[parent_group_index].current_value_index; 9652var parent_next_val_index = parent_current_val_index + 1; 9653while ( parent_group_index !== 0 ) { 9654if ( parent_next_val_index >= _via_image_grid_group_var[parent_group_index].values.length ) { 9655parent_group_index = group_index - 1; 9656parent_current_val_index = _via_image_grid_group_var[parent_group_index].current_value_index; 9657parent_next_val_index = parent_current_val_index + 1; 9658} else { 9659break; 9660} 9661} 9662 9663if ( parent_next_val_index >= _via_image_grid_group_var[parent_group_index].values.length ) { 9664parent_next_val_index = 0; 9665} 9666image_grid_jump_to_group(parent_group_index, parent_next_val_index); 9667} 9668} else { 9669image_grid_jump_to_group(group_index, next_index); 9670} 9671image_grid_set_content_to_current_group(); 9672} 9673 9674function image_grid_group_prev(p) { 9675var group_index = parseInt( p.value ); 9676var group_value_list = _via_image_grid_group_var[group_index].values; 9677var n = group_value_list.length; 9678var current_index = _via_image_grid_group_var[group_index].current_value_index; 9679var prev_index = current_index - 1; 9680if ( prev_index < 0 ) { 9681if ( group_index === 0 ) { 9682prev_index = n + prev_index; 9683image_grid_jump_to_group(group_index, prev_index); 9684} else { 9685// prev of parent group 9686var parent_group_index = group_index - 1; 9687var parent_current_val_index = _via_image_grid_group_var[parent_group_index].current_value_index; 9688var parent_prev_val_index = parent_current_val_index - 1; 9689while ( parent_group_index !== 0 ) { 9690if ( parent_prev_val_index < 0 ) { 9691parent_group_index = group_index - 1; 9692parent_current_val_index = _via_image_grid_group_var[parent_group_index].current_value_index; 9693parent_prev_val_index = parent_current_val_index - 1; 9694} else { 9695break; 9696} 9697} 9698 9699if ( parent_prev_val_index < 0 ) { 9700parent_prev_val_index = _via_image_grid_group_var[parent_group_index].values.length - 1; 9701} 9702image_grid_jump_to_group(parent_group_index, parent_prev_val_index); 9703} 9704} else { 9705image_grid_jump_to_group(group_index, prev_index); 9706} 9707image_grid_set_content_to_current_group(); 9708} 9709 9710 9711function image_grid_group_value_onchange(p) { 9712var group_index = image_grid_group_select_parse_html_id(p.id); 9713image_grid_jump_to_group(group_index, p.selectedIndex); 9714image_grid_set_content_to_current_group(); 9715} 9716 9717function image_grid_jump_to_group(group_index, value_index) { 9718var n = _via_image_grid_group_var[group_index].values.length; 9719if ( value_index >=n || value_index < 0 ) { 9720return; 9721} 9722 9723_via_image_grid_group_var[group_index].current_value_index = value_index; 9724image_grid_group_panel_set_selected_value( group_index ); 9725 9726// reset the value of lower groups 9727var i, value; 9728if ( group_index + 1 < _via_image_grid_group_var.length ) { 9729var e = _via_image_grid_group; 9730for ( i = 0; i <= group_index; ++i ) { 9731value = _via_image_grid_group_var[i].values[ _via_image_grid_group_var[i].current_value_index ]; 9732e = e[ value ]; 9733} 9734 9735for ( i = group_index + 1; i < _via_image_grid_group_var.length; ++i ) { 9736_via_image_grid_group_var[i].values = Object.keys(e); 9737if ( _via_image_grid_group_var[i].values.length === 0 ) { 9738_via_image_grid_group_var[i].current_value_index = -1; 9739_via_image_grid_group_var.splice(i); 9740image_grid_group_panel_set_options(i); 9741break; 9742} else { 9743_via_image_grid_group_var[i].current_value_index = 0; 9744value = _via_image_grid_group_var[i].values[0] 9745e = e[value]; 9746image_grid_group_panel_set_options(i); 9747} 9748} 9749} 9750} 9751 9752function image_grid_set_content_to_current_group() { 9753var n = _via_image_grid_group_var.length; 9754 9755if ( n === 0 ) { 9756image_grid_show_all_project_images(); 9757} else { 9758var group_img_index_list = []; 9759var img_index_list = _via_image_grid_group; 9760var i, n, value, current_value_index; 9761for ( i = 0; i < n; ++i ) { 9762value = _via_image_grid_group_var[i].values[ _via_image_grid_group_var[i].current_value_index ]; 9763img_index_list = img_index_list[ value ]; 9764} 9765 9766if ( Array.isArray(img_index_list) ) { 9767image_grid_set_content(img_index_list); 9768} else { 9769console.log('Error: image_grid_set_content_to_current_group(): expected array while got ' + typeof(img_index_list)); 9770} 9771} 9772} 9773 9774function image_grid_page_next() { 9775_via_image_grid_stack_prev_page.push(_via_image_grid_page_first_index); 9776_via_image_grid_page_first_index = _via_image_grid_page_last_index; 9777 9778image_grid_clear_content(); 9779_via_image_grid_load_ongoing = true; 9780image_grid_page_nav_show_cancel(); 9781image_grid_content_append_img( _via_image_grid_page_first_index ); 9782} 9783 9784function image_grid_page_prev() { 9785_via_image_grid_page_first_index = _via_image_grid_stack_prev_page.pop(); 9786_via_image_grid_page_last_index = -1; 9787 9788image_grid_clear_content(); 9789_via_image_grid_load_ongoing = true; 9790image_grid_page_nav_show_cancel(); 9791image_grid_content_append_img( _via_image_grid_page_first_index ); 9792} 9793 9794function image_grid_page_nav_show_cancel() { 9795var info = document.getElementById('image_grid_nav'); 9796var html = []; 9797html.push('<span>Loading images ... </span>'); 9798html.push('<span class="text_button" onclick="image_grid_cancel_load_ongoing()">Cancel</span>'); 9799info.innerHTML = html.join(''); 9800} 9801 9802function image_grid_cancel_load_ongoing() { 9803_via_image_grid_load_ongoing = false; 9804} 9805 9806 9807// everything to do with image zooming 9808function image_zoom_init() { 9809 9810} 9811 9812// 9813// hooks for sub-modules 9814// implemented by sub-modules 9815// 9816//function _via_hook_next_image() {} 9817//function _via_hook_prev_image() {} 9818 9819 9820//////////////////////////////////////////////////////////////////////////////// 9821// 9822// Code borrowed from via2 branch 9823// - in future, the <canvas> based reigon shape drawing will be replaced by <svg> 9824// because svg allows independent manipulation of individual regions without 9825// requiring to clear the canvas every time some region is updated. 9826// 9827//////////////////////////////////////////////////////////////////////////////// 9828 9829//////////////////////////////////////////////////////////////////////////////// 9830// 9831// @file _via_region.js 9832// @description Implementation of region shapes like rectangle, circle, etc. 9833// @author Abhishek Dutta <adutta@robots.ox.ac.uk> 9834// @date 17 June 2017 9835// 9836//////////////////////////////////////////////////////////////////////////////// 9837 9838function _via_region( shape, id, data_img_space, view_scale_factor, view_offset_x, view_offset_y) { 9839// Note the following terminology: 9840// view space : 9841// - corresponds to the x-y plane on which the scaled version of original image is shown to the user 9842// - all the region query operations like is_inside(), is_on_edge(), etc are performed in view space 9843// - all svg draw operations like get_svg() are also in view space 9844// 9845// image space : 9846// - corresponds to the x-y plane which corresponds to the spatial space of the original image 9847// - region save, export, git push operations are performed in image space 9848// - to avoid any rounding issues (caused by floating scale factor), 9849// * user drawn regions in view space is first converted to image space 9850// * this region in image space is now used to initialize region in view space 9851// 9852// The two spaces are related by _via_model.now.tform.scale which is computed by the method 9853// _via_ctrl.compute_view_panel_to_nowfile_tform() 9854// and applied as follows: 9855// x coordinate in image space = scale_factor * x coordinate in view space 9856// 9857// shape : {rect, circle, ellipse, line, polyline, polygon, point} 9858// id : unique region-id 9859// d[] : (in view space) data whose meaning depend on region shape as follows: 9860// rect : d[x1,y1,x2,y2] or d[corner1_x, corner1_y, corner2_x, corner2_y] 9861// circle : d[x1,y1,x2,y2] or d[center_x, center_y, circumference_x, circumference_y] 9862// ellipse : d[x1,y1,x2,y2,transform] 9863// line : d[x1,y1,x2,y2] 9864// polyline : d[x1,y1,...,xn,yn] 9865// polygon : d[x1,y1,...,xn,yn] 9866// point : d[cx,cy] 9867// scale_factor : for conversion from view space to image space 9868// 9869// Note: no svg data are stored with prefix "_". For example: _scale_factor, _x2 9870this.shape = shape; 9871this.id = id; 9872this.scale_factor = view_scale_factor; 9873this.offset_x = view_offset_x; 9874this.offset_y = view_offset_y; 9875this.recompute_svg = false; 9876this.attributes = {}; 9877 9878var n = data_img_space.length; 9879var i; 9880this.dview = new Array(n); 9881this.dimg = new Array(n); 9882 9883if ( n !== 0 ) { 9884// IMPORTANT: 9885// to avoid any rounding issues (caused by floating scale factor), we stick to 9886// the principal that image space coordinates are the ground truth for every region. 9887// Hence, we proceed as: 9888// * user drawn regions in view space is first converted to image space 9889// * this region in image space is now used to initialize region in view space 9890for ( i = 0; i < n; i++ ) { 9891this.dimg[i] = data_img_space[i]; 9892 9893var offset = this.offset_x; 9894if ( i % 2 !== 0 ) { 9895// y coordinate 9896offset = this.offset_y; 9897} 9898this.dview[i] = Math.round( this.dimg[i] * this.scale_factor ) + offset; 9899} 9900} 9901 9902// set svg attributes for each shape 9903switch( this.shape ) { 9904case "rect": 9905_via_region_rect.call( this ); 9906this.svg_attributes = ['x', 'y', 'width', 'height']; 9907break; 9908case "circle": 9909_via_region_circle.call( this ); 9910this.svg_attributes = ['cx', 'cy', 'r']; 9911break; 9912case "ellipse": 9913_via_region_ellipse.call( this ); 9914this.svg_attributes = ['cx', 'cy', 'rx', 'ry','transform']; 9915break; 9916case "line": 9917_via_region_line.call( this ); 9918this.svg_attributes = ['x1', 'y1', 'x2', 'y2']; 9919break; 9920case "polyline": 9921_via_region_polyline.call( this ); 9922this.svg_attributes = ['points']; 9923break; 9924case "polygon": 9925_via_region_polygon.call( this ); 9926this.svg_attributes = ['points']; 9927break; 9928case "point": 9929_via_region_point.call( this ); 9930// point is a special circle with minimal radius required for visualization 9931this.shape = 'circle'; 9932this.svg_attributes = ['cx', 'cy', 'r']; 9933break; 9934} 9935 9936this.initialize(); 9937} 9938 9939 9940_via_region.prototype.prepare_svg_element = function() { 9941var _VIA_SVG_NS = "http://www.w3.org/2000/svg"; 9942this.svg_element = document.createElementNS(_VIA_SVG_NS, this.shape); 9943this.svg_string = '<' + this.shape; 9944this.svg_element.setAttributeNS(null, 'id', this.id); 9945 9946var n = this.svg_attributes.length; 9947for ( var i = 0; i < n; i++ ) { 9948this.svg_element.setAttributeNS(null, this.svg_attributes[i], this[this.svg_attributes[i]]); 9949this.svg_string += ' ' + this.svg_attributes[i] + '="' + this[this.svg_attributes[i]] + '"'; 9950} 9951this.svg_string += '/>'; 9952} 9953 9954_via_region.prototype.get_svg_element = function() { 9955if ( this.recompute_svg ) { 9956this.prepare_svg_element(); 9957this.recompute_svg = false; 9958} 9959return this.svg_element; 9960} 9961 9962_via_region.prototype.get_svg_string = function() { 9963if ( this.recompute_svg ) { 9964this.prepare_svg_element(); 9965this.recompute_svg = false; 9966} 9967return this.svg_string; 9968} 9969 9970/// 9971/// Region shape : rectangle 9972/// 9973function _via_region_rect() { 9974this.is_inside = _via_region_rect.prototype.is_inside; 9975this.is_on_edge = _via_region_rect.prototype.is_on_edge; 9976this.move = _via_region_rect.prototype.move; 9977this.resize = _via_region_rect.prototype.resize; 9978this.initialize = _via_region_rect.prototype.initialize; 9979this.dist_to_nearest_edge = _via_region_rect.prototype.dist_to_nearest_edge; 9980} 9981 9982_via_region_rect.prototype.initialize = function() { 9983// ensure that this.(x,y) corresponds to top-left corner of rectangle 9984// Note: this.(x2,y2) is defined for convenience in calculations 9985if ( this.dview[0] < this.dview[2] ) { 9986this.x = this.dview[0]; 9987this.x2 = this.dview[2]; 9988} else { 9989this.x = this.dview[2]; 9990this.x2 = this.dview[0]; 9991} 9992if ( this.dview[1] < this.dview[3] ) { 9993this.y = this.dview[1]; 9994this.y2 = this.dview[3]; 9995} else { 9996this.y = this.dview[3]; 9997this.y2 = this.dview[1]; 9998} 9999this.width = this.x2 - this.x; 10000this.height = this.y2 - this.y; 10001this.recompute_svg = true; 10002} 10003 10004/// 10005/// Region shape : circle 10006/// 10007function _via_region_circle() { 10008this.is_inside = _via_region_circle.prototype.is_inside; 10009this.is_on_edge = _via_region_circle.prototype.is_on_edge; 10010this.move = _via_region_circle.prototype.move; 10011this.resize = _via_region_circle.prototype.resize; 10012this.initialize = _via_region_circle.prototype.initialize; 10013this.dist_to_nearest_edge = _via_region_circle.prototype.dist_to_nearest_edge; 10014} 10015 10016_via_region_circle.prototype.initialize = function() { 10017this.cx = this.dview[0]; 10018this.cy = this.dview[1]; 10019var dx = this.dview[2] - this.dview[0]; 10020var dy = this.dview[3] - this.dview[1]; 10021this.r = Math.round( Math.sqrt(dx * dx + dy * dy) ); 10022this.r2 = this.r * this.r; 10023this.recompute_svg = true; 10024} 10025 10026 10027/// 10028/// Region shape : ellipse 10029/// 10030function _via_region_ellipse() { 10031this.is_inside = _via_region_ellipse.prototype.is_inside; 10032this.is_on_edge = _via_region_ellipse.prototype.is_on_edge; 10033this.move = _via_region_ellipse.prototype.move; 10034this.resize = _via_region_ellipse.prototype.resize; 10035this.initialize = _via_region_ellipse.prototype.initialize; 10036this.dist_to_nearest_edge = _via_region_ellipse.prototype.dist_to_nearest_edge; 10037} 10038 10039_via_region_ellipse.prototype.initialize = function() { 10040this.cx = this.dview[0]; 10041this.cy = this.dview[1]; 10042this.rx = Math.abs(this.dview[2] - this.dview[0]); 10043this.ry = Math.abs(this.dview[3] - this.dview[1]); 10044 10045this.inv_rx2 = 1 / (this.rx * this.rx); 10046this.inv_ry2 = 1 / (this.ry * this.ry); 10047 10048this.recompute_svg = true; 10049} 10050 10051 10052 10053/// 10054/// Region shape : line 10055/// 10056function _via_region_line() { 10057this.is_inside = _via_region_line.prototype.is_inside; 10058this.is_on_edge = _via_region_line.prototype.is_on_edge; 10059this.move = _via_region_line.prototype.move; 10060this.resize = _via_region_line.prototype.resize; 10061this.initialize = _via_region_line.prototype.initialize; 10062this.dist_to_nearest_edge = _via_region_line.prototype.dist_to_nearest_edge; 10063} 10064 10065_via_region_line.prototype.initialize = function() { 10066this.x1 = this.dview[0]; 10067this.y1 = this.dview[1]; 10068this.x2 = this.dview[2]; 10069this.y2 = this.dview[3]; 10070this.dx = this.x1 - this.x2; 10071this.dy = this.y1 - this.y2; 10072this.mconst = (this.x1 * this.y2) - (this.x2 * this.y1); 10073 10074this.recompute_svg = true; 10075} 10076 10077 10078/// 10079/// Region shape : polyline 10080/// 10081function _via_region_polyline() { 10082this.is_inside = _via_region_polyline.prototype.is_inside; 10083this.is_on_edge = _via_region_polyline.prototype.is_on_edge; 10084this.move = _via_region_polyline.prototype.move; 10085this.resize = _via_region_polyline.prototype.resize; 10086this.initialize = _via_region_polyline.prototype.initialize; 10087this.dist_to_nearest_edge = _via_region_polyline.prototype.dist_to_nearest_edge; 10088} 10089 10090_via_region_polyline.prototype.initialize = function() { 10091var n = this.dview.length; 10092var points = new Array(n/2); 10093var points_index = 0; 10094for ( var i = 0; i < n; i += 2 ) { 10095points[points_index] = ( this.dview[i] + ' ' + this.dview[i+1] ); 10096points_index++; 10097} 10098this.points = points.join(','); 10099this.recompute_svg = true; 10100} 10101 10102 10103/// 10104/// Region shape : polygon 10105/// 10106function _via_region_polygon() { 10107this.is_inside = _via_region_polygon.prototype.is_inside; 10108this.is_on_edge = _via_region_polygon.prototype.is_on_edge; 10109this.move = _via_region_polygon.prototype.move; 10110this.resize = _via_region_polygon.prototype.resize; 10111this.initialize = _via_region_polygon.prototype.initialize; 10112this.dist_to_nearest_edge = _via_region_polygon.prototype.dist_to_nearest_edge; 10113} 10114 10115_via_region_polygon.prototype.initialize = function() { 10116var n = this.dview.length; 10117var points = new Array(n/2); 10118var points_index = 0; 10119for ( var i = 0; i < n; i += 2 ) { 10120points[points_index] = ( this.dview[i] + ' ' + this.dview[i+1] ); 10121points_index++; 10122} 10123this.points = points.join(','); 10124this.recompute_svg = true; 10125} 10126 10127 10128/// 10129/// Region shape : point 10130/// 10131function _via_region_point() { 10132this.is_inside = _via_region_point.prototype.is_inside; 10133this.is_on_edge = _via_region_point.prototype.is_on_edge; 10134this.move = _via_region_point.prototype.move; 10135this.resize = _via_region_point.prototype.resize 10136this.initialize = _via_region_point.prototype.initialize; 10137this.dist_to_nearest_edge = _via_region_point.prototype.dist_to_nearest_edge; 10138} 10139 10140_via_region_point.prototype.initialize = function() { 10141this.cx = this.dview[0]; 10142this.cy = this.dview[1]; 10143this.r = 2; 10144this.r2 = this.r * this.r; 10145this.recompute_svg = true; 10146} 10147 10148// 10149// image buffering 10150// 10151 10152function _via_cancel_current_image_loading() { 10153var panel = document.getElementById('project_panel_title'); 10154panel.innerHTML = 'Project'; 10155_via_is_loading_current_image = false; 10156} 10157 10158function _via_show_img(img_index) { 10159if ( _via_is_loading_current_image ) { 10160return; 10161} 10162 10163var img_id = _via_image_id_list[img_index]; 10164 10165if ( ! _via_img_metadata.hasOwnProperty(img_id) ) { 10166console.log('_via_show_img(): [' + img_index + '] ' + img_id + ' does not exist!') 10167show_message('The requested image does not exist!') 10168return; 10169} 10170 10171// file_resolve() is not necessary for files selected using browser's file selector 10172if ( typeof(_via_img_fileref[img_id]) === 'undefined' || ! _via_img_fileref[img_id] instanceof File ) { 10173// try preload from local file or url 10174if ( typeof(_via_img_src[img_id]) === 'undefined' || _via_img_src[img_id] === '' ) { 10175if ( is_url( _via_img_metadata[img_id].filename ) ) { 10176_via_img_src[img_id] = _via_img_metadata[img_id].filename; 10177_via_show_img(img_index); 10178return; 10179} else { 10180var search_path_list = _via_file_get_search_path_list(); 10181if ( search_path_list.length === 0 ) { 10182search_path_list.push(''); // search using just the filename 10183} 10184 10185_via_file_resolve(img_index, search_path_list).then( function(ok_file_index) { 10186_via_show_img(img_index); 10187}, function(err_file_index) { 10188show_page_404(img_index); 10189}); 10190return; 10191} 10192} 10193} 10194 10195if ( _via_buffer_img_index_list.includes(img_index) ) { 10196_via_current_image_loaded = false; 10197_via_show_img_from_buffer(img_index).then( function(ok_img_index) { 10198// trigger preload of images in buffer corresponding to img_index 10199// but, wait until all previous promises get cancelled 10200Promise.all(_via_preload_img_promise_list).then( function(values) { 10201_via_preload_img_promise_list = []; 10202var preload_promise = _via_img_buffer_start_preload( img_index, 0 ) 10203_via_preload_img_promise_list.push(preload_promise); 10204}); 10205}, function(err_img_index) { 10206console.log('_via_show_img_from_buffer() failed for file: ' + _via_image_filename_list[err_img_index]); 10207_via_current_image_loaded = false; 10208}); 10209} else { 10210// image not in buffer, so first add this image to buffer 10211_via_is_loading_current_image = true; 10212img_loading_spinbar(img_index, true); 10213_via_img_buffer_add_image(img_index).then( function(ok_img_index) { 10214_via_is_loading_current_image = false; 10215img_loading_spinbar(img_index, false); 10216_via_show_img(img_index); 10217}, function(err_img_index) { 10218_via_is_loading_current_image = false; 10219img_loading_spinbar(img_index, false); 10220show_page_404(img_index); 10221console.log('_via_img_buffer_add_image() failed for file: ' + _via_image_filename_list[err_img_index]); 10222}); 10223} 10224} 10225 10226function _via_buffer_hide_current_image() { 10227img_fn_list_ith_entry_selected(_via_image_index, false); 10228_via_clear_reg_canvas(); // clear old region shapes 10229if ( _via_current_image ) { 10230_via_current_image.classList.remove('visible'); 10231} 10232} 10233 10234function _via_show_img_from_buffer(img_index) { 10235return new Promise( function(ok_callback, err_callback) { 10236_via_buffer_hide_current_image(); 10237 10238var cimg_html_id = _via_img_buffer_get_html_id(img_index); 10239_via_current_image = document.getElementById(cimg_html_id); 10240if ( ! _via_current_image ) { 10241// the said image is not present in buffer, which could be because 10242// the image got removed from the buffer 10243err_callback(img_index); 10244return; 10245} 10246_via_current_image.classList.add('visible'); // now show the new image 10247 10248_via_image_index = img_index; 10249_via_image_id = _via_image_id_list[_via_image_index]; 10250_via_current_image_filename = _via_img_metadata[_via_image_id].filename; 10251_via_current_image_loaded = true; 10252 10253var arr_index = _via_buffer_img_index_list.indexOf(img_index); 10254_via_buffer_img_shown_timestamp[arr_index] = Date.now(); // update shown timestamp 10255 10256// update the current state of application 10257_via_click_x0 = 0; _via_click_y0 = 0; 10258_via_click_x1 = 0; _via_click_y1 = 0; 10259_via_is_user_drawing_region = false; 10260_via_is_window_resized = false; 10261_via_is_user_resizing_region = false; 10262_via_is_user_moving_region = false; 10263_via_is_user_drawing_polygon = false; 10264_via_is_region_selected = false; 10265_via_user_sel_region_id = -1; 10266_via_current_image_width = _via_current_image.naturalWidth; 10267_via_current_image_height = _via_current_image.naturalHeight; 10268 10269if ( _via_current_image_width === 0 || _via_current_image_height === 0 ) { 10270// for error image icon 10271_via_current_image_width = 640; 10272_via_current_image_height = 480; 10273} 10274 10275// set the size of canvas 10276// based on the current dimension of browser window 10277var de = document.documentElement; 10278var image_panel_width = de.clientWidth - leftsidebar.clientWidth - 20; 10279if ( leftsidebar.style.display === 'none' ) { 10280image_panel_width = de.clientWidth; 10281} 10282var image_panel_height = de.clientHeight - 2*ui_top_panel.offsetHeight; 10283 10284_via_canvas_width = _via_current_image_width; 10285_via_canvas_height = _via_current_image_height; 10286 10287if ( _via_canvas_width > image_panel_width ) { 10288// resize image to match the panel width 10289var scale_width = image_panel_width / _via_current_image.naturalWidth; 10290_via_canvas_width = image_panel_width; 10291_via_canvas_height = _via_current_image.naturalHeight * scale_width; 10292} 10293if ( _via_canvas_height > image_panel_height ) { 10294// resize further image if its height is larger than the image panel 10295var scale_height = image_panel_height / _via_canvas_height; 10296_via_canvas_height = image_panel_height; 10297_via_canvas_width = _via_canvas_width * scale_height; 10298} 10299_via_canvas_width = Math.round(_via_canvas_width); 10300_via_canvas_height = Math.round(_via_canvas_height); 10301_via_canvas_scale = _via_current_image.naturalWidth / _via_canvas_width; 10302_via_canvas_scale_without_zoom = _via_canvas_scale; 10303set_all_canvas_size(_via_canvas_width, _via_canvas_height); 10304//set_all_canvas_scale(_via_canvas_scale_without_zoom); 10305 10306// reset all regions to "not selected" state 10307toggle_all_regions_selection(false); 10308 10309// ensure that all the canvas are visible 10310set_display_area_content( VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE ); 10311 10312// update img_fn_list 10313img_fn_list_ith_entry_selected(_via_image_index, true); 10314img_fn_list_scroll_to_current_file(); 10315 10316// refresh the annotations panel 10317annotation_editor_update_content(); 10318 10319_via_load_canvas_regions(); // image to canvas space transform 10320_via_redraw_reg_canvas(); 10321_via_reg_canvas.focus(); 10322 10323// Preserve zoom level 10324if (_via_is_canvas_zoomed) { 10325set_zoom( _via_canvas_zoom_level_index ); 10326} 10327ok_callback(img_index); 10328}); 10329} 10330 10331function _via_img_buffer_add_image(img_index) { 10332return new Promise( function(ok_callback, err_callback) { 10333if ( _via_buffer_img_index_list.includes(img_index) ) { 10334//console.log('_via_img_buffer_add_image(): image ' + img_index + ' already exists in buffer!') 10335ok_callback(img_index); 10336return; 10337} 10338 10339var img_id = _via_image_id_list[img_index]; 10340var img_filename = _via_img_metadata[img_id].filename; 10341if ( !_via_img_metadata.hasOwnProperty(img_id)) { 10342err_callback(img_index); 10343return; 10344} 10345 10346// check if user has given access to local file using 10347// browser's file selector 10348if ( _via_img_fileref[img_id] instanceof File ) { 10349var tmp_file_object_url = URL.createObjectURL(_via_img_fileref[img_id]); 10350var img_id = _via_image_id_list[img_index]; 10351var bimg = document.createElement('img'); 10352bimg.setAttribute('id', _via_img_buffer_get_html_id(img_index)); 10353bimg.setAttribute('src', tmp_file_object_url); 10354bimg.setAttribute('alt', 'Image loaded from base64 data of a local file selected by user.'); 10355bimg.addEventListener('error', function() { 10356URL.revokeObjectURL(tmp_file_object_url); 10357project_file_load_on_fail(img_index); 10358err_callback(img_index); 10359}); 10360bimg.addEventListener('load', function() { 10361URL.revokeObjectURL(tmp_file_object_url); 10362img_stat_set(img_index, [bimg.naturalWidth, bimg.naturalHeight]); 10363_via_img_panel.insertBefore(bimg, _via_reg_canvas); 10364project_file_load_on_success(img_index); 10365img_fn_list_ith_entry_add_css_class(img_index, 'buffered') 10366// add timestamp so that we can apply Least Recently Used (LRU) 10367// scheme to remove elements when buffer is full 10368var arr_index = _via_buffer_img_index_list.length; 10369_via_buffer_img_index_list.push(img_index); 10370_via_buffer_img_shown_timestamp[arr_index] = Date.now(); // though, not seen yet 10371ok_callback(img_index); 10372}); 10373return; 10374} 10375 10376if ( typeof(_via_img_src[img_id]) === 'undefined' || _via_img_src[img_id] === '' ) { 10377err_callback(img_index); 10378} else { 10379var img_id = _via_image_id_list[img_index]; 10380 10381var bimg = document.createElement('img'); 10382bimg.setAttribute('id', _via_img_buffer_get_html_id(img_index)); 10383_via_img_src[img_id] = _via_img_src[img_id].replace('#', '%23'); 10384bimg.setAttribute('src', _via_img_src[img_id]); 10385if ( _via_img_src[img_id].startsWith('data:image') ) { 10386bimg.setAttribute('alt', 'Source: image data in base64 format'); 10387} else { 10388bimg.setAttribute('alt', 'Source: ' + _via_img_src[img_id]); 10389} 10390 10391bimg.addEventListener('abort', function() { 10392project_file_load_on_fail(img_index); 10393err_callback(img_index); 10394}); 10395bimg.addEventListener('error', function() { 10396project_file_load_on_fail(img_index); 10397err_callback(img_index); 10398}); 10399 10400// Note: _via_current_image.{naturalWidth,naturalHeight} is only accessible after 10401// the "load" event. Therefore, all processing must happen inside this event handler. 10402bimg.addEventListener('load', function() { 10403img_stat_set(img_index, [bimg.naturalWidth, bimg.naturalHeight]); 10404_via_img_panel.insertBefore(bimg, _via_reg_canvas); 10405 10406project_file_load_on_success(img_index); 10407img_fn_list_ith_entry_add_css_class(img_index, 'buffered') 10408// add timestamp so that we can apply Least Recently Used (LRU) 10409// scheme to remove elements when buffer is full 10410var arr_index = _via_buffer_img_index_list.length; 10411_via_buffer_img_index_list.push(img_index); 10412_via_buffer_img_shown_timestamp[arr_index] = Date.now(); // though, not seen yet 10413ok_callback(img_index); 10414}, false); 10415} 10416}, false); 10417} 10418 10419function _via_img_buffer_get_html_id(img_index) { 10420return 'bim' + img_index; 10421} 10422 10423function _via_img_buffer_parse_html_id(html_id) { 10424return parseInt( html_id.substr(3) ); 10425} 10426 10427function _via_img_buffer_start_preload(img_index, preload_index) { 10428return new Promise( function(ok_callback, err_callback) { 10429_via_buffer_preload_img_index = img_index; 10430_via_img_buffer_preload_img(_via_buffer_preload_img_index, 0).then( function(ok_img_index_list) { 10431ok_callback(ok_img_index_list); 10432}); 10433}); 10434} 10435 10436function _via_img_buffer_preload_img(img_index, preload_index) { 10437return new Promise( function(ok_callback, err_callback) { 10438var preload_img_index = _via_img_buffer_get_preload_img_index(img_index, preload_index); 10439 10440if ( _via_buffer_preload_img_index !== _via_image_index ) { 10441ok_callback([]); 10442return; 10443} 10444 10445// ensure that there is sufficient buffer space left for preloading image 10446if ( _via_buffer_img_index_list.length > _via_settings.core.buffer_size ) { 10447while( _via_buffer_img_index_list.length > _via_settings.core.buffer_size ) { 10448_via_img_buffer_remove_least_useful_img(); 10449if ( _via_image_index !== _via_buffer_preload_img_index ) { 10450// current image has changed therefore, we need to cancel this preload operation 10451ok_callback([]); 10452return; 10453} 10454} 10455} 10456 10457_via_img_buffer_add_image(preload_img_index).then( function(ok_img_index) { 10458if ( _via_image_index !== _via_buffer_preload_img_index ) { 10459ok_callback( [ok_img_index] ); 10460return; 10461} 10462 10463var next_preload_index = preload_index + 1; 10464if ( next_preload_index !== VIA_IMG_PRELOAD_COUNT ) { 10465_via_img_buffer_preload_img(img_index, next_preload_index).then( function(ok_img_index_list) { 10466ok_img_index_list.push( ok_img_index ) 10467ok_callback( ok_img_index_list ); 10468}); 10469} else { 10470ok_callback( [ok_img_index] ); 10471} 10472}, function(err_img_index) { 10473// continue with preload of other images in sequence 10474var next_preload_index = preload_index + 1; 10475if ( next_preload_index !== VIA_IMG_PRELOAD_COUNT ) { 10476_via_img_buffer_preload_img(img_index, next_preload_index).then( function(ok_img_index_list) { 10477ok_callback( ok_img_index_list ); 10478}); 10479} else { 10480ok_callback([]); 10481} 10482}); 10483}); 10484} 10485 10486function _via_img_buffer_get_preload_img_index(img_index, preload_index) { 10487var preload_img_index = img_index + VIA_IMG_PRELOAD_INDICES[preload_index]; 10488if ( (preload_img_index < 0) || (preload_img_index >= _via_img_count) ) { 10489if ( preload_img_index < 0 ) { 10490preload_img_index = _via_img_count + preload_img_index; 10491} else { 10492preload_img_index = preload_img_index - _via_img_count; 10493} 10494} 10495return preload_img_index; 10496} 10497 10498// the least useful image is, one with the following properties: 10499// - preload list for current image will always get loaded, so there is no point in removing them from buffer 10500// - all the other images in buffer were seen more recently by the image 10501// - all the other images are closer (in terms of their image index) to the image currently being shown 10502function _via_img_buffer_remove_least_useful_img() { 10503var not_in_preload_list = _via_buffer_img_not_in_preload_list(); 10504var oldest_buffer_index = _via_buffer_get_oldest_in_list(not_in_preload_list); 10505 10506if ( _via_buffer_img_index_list[oldest_buffer_index] !== _via_image_index ) { 10507//console.log('removing oldest_buffer index: ' + oldest_buffer_index); 10508_via_buffer_remove(oldest_buffer_index); 10509} else { 10510var furthest_buffer_index = _via_buffer_get_buffer_furthest_from_current_img(); 10511_via_buffer_remove(furthest_buffer_index); 10512} 10513} 10514 10515function _via_buffer_remove( buffer_index ) { 10516var img_index = _via_buffer_img_index_list[buffer_index]; 10517var bimg_html_id = _via_img_buffer_get_html_id(img_index); 10518var bimg = document.getElementById(bimg_html_id); 10519if ( bimg ) { 10520_via_buffer_img_index_list.splice(buffer_index, 1); 10521_via_buffer_img_shown_timestamp.splice(buffer_index, 1); 10522_via_img_panel.removeChild(bimg); 10523 10524img_fn_list_ith_entry_remove_css_class(img_index, 'buffered') 10525} 10526} 10527 10528function _via_buffer_remove_all() { 10529var i, n; 10530n = _via_buffer_img_index_list.length; 10531for ( i = 0 ; i < n; ++i ) { 10532var img_index = _via_buffer_img_index_list[i]; 10533var bimg_html_id = _via_img_buffer_get_html_id(img_index); 10534var bimg = document.getElementById(bimg_html_id); 10535if ( bimg ) { 10536_via_img_panel.removeChild(bimg); 10537} 10538} 10539_via_buffer_img_index_list = []; 10540_via_buffer_img_shown_timestamp = []; 10541} 10542 10543function _via_buffer_get_oldest_in_list(not_in_preload_list) { 10544var i; 10545var n = not_in_preload_list.length; 10546var oldest_buffer_index = -1; 10547var oldest_buffer_timestamp = Date.now(); 10548 10549for ( i = 0; i < n; ++i ) { 10550var _via_buffer_index = not_in_preload_list[i]; 10551if ( _via_buffer_img_shown_timestamp[_via_buffer_index] < oldest_buffer_timestamp ) { 10552oldest_buffer_timestamp = _via_buffer_img_shown_timestamp[i]; 10553oldest_buffer_index = i; 10554} 10555} 10556return oldest_buffer_index; 10557} 10558 10559function _via_buffer_get_buffer_furthest_from_current_img() { 10560var now_img_index = _via_image_index; 10561var i, dist1, dist2, dist; 10562var n = _via_buffer_img_index_list.length; 10563var furthest_buffer_index = 0; 10564dist1 = Math.abs( _via_buffer_img_index_list[0] - now_img_index ); 10565dist2 = _via_img_count - dist1; // assuming the list is circular 10566var furthest_buffer_dist = Math.min(dist1, dist2); 10567 10568for ( i = 1; i < n; ++i ) { 10569dist1 = Math.abs( _via_buffer_img_index_list[i] - now_img_index ); 10570dist2 = _via_img_count - dist1; // assuming the list is circular 10571dist = Math.min(dist1, dist2); 10572// image has been seen by user at least once 10573if ( dist > furthest_buffer_dist ) { 10574furthest_buffer_dist = dist; 10575furthest_buffer_index = i; 10576} 10577} 10578return furthest_buffer_index; 10579} 10580 10581function _via_buffer_img_not_in_preload_list() { 10582var preload_list = _via_buffer_get_current_preload_list(); 10583var i; 10584var not_in_preload_list = []; 10585for ( i = 0; i < _via_buffer_img_index_list.length; ++i ) { 10586if ( ! preload_list.includes( _via_buffer_img_index_list[i] ) ) { 10587not_in_preload_list.push( i ); 10588} 10589} 10590return not_in_preload_list; 10591} 10592 10593function _via_buffer_get_current_preload_list() { 10594var i; 10595var preload_list = [_via_image_index]; 10596var img_index = _via_image_index; 10597for ( i = 0; i < VIA_IMG_PRELOAD_COUNT; ++i ) { 10598var preload_index = img_index + VIA_IMG_PRELOAD_INDICES[i]; 10599if ( preload_index < 0 ) { 10600preload_index = _via_img_count + preload_index; 10601} 10602if ( preload_index >= _via_img_count ) { 10603preload_index = preload_index - _via_img_count; 10604} 10605preload_list.push(preload_index); 10606} 10607return preload_list; 10608} 10609 10610// 10611// settings 10612// 10613function settings_panel_toggle() { 10614if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.SETTINGS ) { 10615if ( _via_display_area_content_name_prev !== '' ) { 10616set_display_area_content(_via_display_area_content_name_prev); 10617} else { 10618show_single_image_view(); 10619_via_redraw_rleg_canvas(); 10620} 10621} 10622else { 10623settings_init(); 10624set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.SETTINGS); 10625} 10626} 10627 10628function settings_init() { 10629settings_region_visualisation_update_options(); 10630settings_filepath_update_html(); 10631settings_show_current_value(); 10632} 10633 10634function settings_save() { 10635// check if default path was updated 10636var default_path_updated = false; 10637if ( document.getElementById('_via_settings.core.default_filepath').value !== _via_settings.core.default_filepath ) { 10638default_path_updated = true; 10639} 10640 10641var p = document.getElementById('settings_panel'); 10642var vl = p.getElementsByClassName('value'); 10643var n = vl.length; 10644var i; 10645for ( i = 0; i < n; ++i ) { 10646var s = vl[i].childNodes[1]; 10647var sid_parts = s.id.split('.'); 10648if ( sid_parts[0] === '_via_settings' ) { 10649var el = _via_settings; 10650var found = true; 10651var j; 10652for ( j = 1; j < sid_parts.length - 1; ++j ) { 10653if ( el.hasOwnProperty( sid_parts[j] ) ) { 10654el = el[ sid_parts[j] ]; 10655} else { 10656// unrecognized setting 10657found = false; 10658break; 10659} 10660} 10661if ( found ) { 10662var param = sid_parts[ sid_parts.length - 1 ]; 10663if ( s.value !== '' || typeof(s.value) !== 'undefined' ) { 10664el[param] = s.value; 10665} 10666} 10667} 10668} 10669 10670// non-standard settings 10671var p; 10672p = document.getElementById('settings_input_new_filepath'); 10673if ( p.value !== '' ) { 10674project_filepath_add(p.value.trim()); 10675} 10676p = document.getElementById('project_name'); 10677if ( p.value !== _via_settings.project.name ) { 10678p.value = _via_settings.project.name; 10679} 10680 10681if ( default_path_updated ) { 10682_via_file_resolve_all_to_default_filepath(); 10683_via_show_img(_via_image_index); 10684} 10685 10686show_message('Settings saved.'); 10687settings_panel_toggle(); 10688} 10689 10690function settings_show_current_value() { 10691var p = document.getElementById('settings_panel'); 10692var vl = p.getElementsByClassName('value'); 10693var n = vl.length; 10694var i; 10695for ( i = 0; i < n; ++i ) { 10696var s = vl[i].childNodes[1]; 10697var sid_parts = s.id.split('.'); 10698if ( sid_parts[0] === '_via_settings' ) { 10699var el = _via_settings; 10700var found = true; 10701var j; 10702for ( j = 1; j < sid_parts.length; ++j ) { 10703if ( el.hasOwnProperty( sid_parts[j] ) ) { 10704el = el[ sid_parts[j] ]; 10705} else { 10706// unrecognized setting 10707found = false; 10708break; 10709} 10710} 10711 10712if ( found ) { 10713s.value = el; 10714} 10715} 10716} 10717} 10718 10719function settings_region_visualisation_update_options() { 10720var region_setting_list = {'region_label': { 10721'default_option':'__via_region_id__', 10722'default_label':'Region id (1, 2, ...)', 10723'label_prefix':'Show value of region attribute: ', 10724}, 'region_color': { 10725'default_option':'__via_default_region_color__', 10726'default_label':'Default Region Colour', 10727'label_prefix':'Based on value of region attribute: ', 10728}}; 10729 10730for ( var setting in region_setting_list ) { 10731var select = document.getElementById('_via_settings.ui.image.' + setting); 10732select.innerHTML = ''; 10733var default_option = document.createElement('option'); 10734default_option.setAttribute('value', region_setting_list[setting]['default_option']); 10735if ( _via_settings.ui.image[setting] === region_setting_list[setting]['default_option'] ) { 10736default_option.setAttribute('selected', 'selected'); 10737} 10738default_option.innerHTML = region_setting_list[setting]['default_label']; 10739select.appendChild(default_option); 10740 10741// options: add region attributes 10742var rattr; 10743for ( rattr in _via_attributes['region'] ) { 10744var o = document.createElement('option'); 10745o.setAttribute('value', rattr); 10746o.innerHTML = region_setting_list[setting]['label_prefix'] + rattr; 10747if ( _via_settings.ui.image.region_label === rattr ) { 10748o.setAttribute('selected', 'selected'); 10749} 10750select.appendChild(o); 10751} 10752} 10753} 10754 10755function settings_filepath_update_html() { 10756var p = document.getElementById('_via_settings.core.filepath'); 10757p.innerHTML = ''; 10758var i, path, order; 10759for ( path in _via_settings.core.filepath ) { 10760order = _via_settings.core.filepath[path] 10761if ( order !== 0 ) { 10762var li = document.createElement('li'); 10763li.innerHTML = path + '<span class="text_button" title="Delete image path" onclick="project_filepath_del(\"' + path + '\"); settings_filepath_update_html();">×</span>'; 10764p.appendChild(li); 10765} 10766} 10767} 10768 10769// 10770// find location of file 10771// 10772 10773function _via_file_resolve_all_to_default_filepath() { 10774var img_id; 10775for ( img_id in _via_img_metadata ) { 10776if ( _via_img_metadata.hasOwnProperty(img_id) ) { 10777_via_file_resolve_file_to_default_filepath(img_id); 10778} 10779} 10780} 10781 10782function _via_file_resolve_file_to_default_filepath(img_id) { 10783if ( _via_img_metadata.hasOwnProperty(img_id) ) { 10784if ( typeof(_via_img_fileref[img_id]) === 'undefined' || ! _via_img_fileref[img_id] instanceof File ) { 10785if ( is_url( _via_img_metadata[img_id].filename ) ) { 10786_via_img_src[img_id] = _via_img_metadata[img_id].filename; 10787} else { 10788_via_img_src[img_id] = _via_settings.core.default_filepath + _via_img_metadata[img_id].filename; 10789} 10790} 10791} 10792} 10793 10794function _via_file_resolve_all() { 10795return new Promise( function(ok_callback, err_callback) { 10796var all_promises = []; 10797 10798var search_path_list = _via_file_get_search_path_list(); 10799var i, img_id; 10800for ( i = 0; i < _via_img_count; ++i ) { 10801img_id = _via_image_id_list[i]; 10802if ( typeof(_via_img_src[img_id]) === 'undefined' || _via_img_src[img_id] === '' ) { 10803var p = _via_file_resolve(i, search_path_list); 10804all_promises.push(p); 10805} 10806} 10807 10808Promise.all( all_promises ).then( function(ok_file_index_list) { 10809console.log(ok_file_index_list); 10810ok_callback(); 10811//project_file_load_on_success(ok_file_index); 10812}, function(err_file_index_list) { 10813console.log(err_file_index_list); 10814err_callback(); 10815//project_file_load_on_fail(err_file_index); 10816}); 10817 10818}); 10819} 10820 10821function _via_file_get_search_path_list() { 10822var search_path_list = []; 10823var path; 10824for ( path in _via_settings.core.filepath ) { 10825if ( _via_settings.core.filepath[path] !== 0 ) { 10826search_path_list.push(path); 10827} 10828} 10829return search_path_list; 10830} 10831 10832function _via_file_resolve(file_index, search_path_list) { 10833return new Promise( function(ok_callback, err_callback) { 10834var path_index = 0; 10835var p = _via_file_resolve_check_path(file_index, path_index, search_path_list).then(function(ok) { 10836ok_callback(ok); 10837}, function(err) { 10838err_callback(err); 10839}); 10840}, false); 10841} 10842 10843function _via_file_resolve_check_path(file_index, path_index, search_path_list) { 10844return new Promise( function(ok_callback, err_callback) { 10845var img_id = _via_image_id_list[file_index]; 10846var img = new Image(0,0); 10847 10848var img_path = search_path_list[path_index] + _via_img_metadata[img_id].filename; 10849if ( is_url( _via_img_metadata[img_id].filename ) ) { 10850if ( search_path_list[path_index] !== '' ) { 10851// we search for the the image filename pointed by URL in local search paths 10852img_path = search_path_list[path_index] + get_filename_from_url( _via_img_metadata[img_id].filename ); 10853} 10854} 10855 10856img.setAttribute('src', img_path); 10857 10858img.addEventListener('load', function() { 10859_via_img_src[img_id] = img_path; 10860ok_callback(file_index); 10861}, false); 10862img.addEventListener('abort', function() { 10863err_callback(file_index); 10864}); 10865img.addEventListener('error', function() { 10866var new_path_index = path_index + 1; 10867if ( new_path_index < search_path_list.length ) { 10868_via_file_resolve_check_path(file_index, new_path_index, search_path_list).then( function(ok) { 10869ok_callback(file_index); 10870}, function(err) { 10871err_callback(file_index); 10872}); 10873} else { 10874err_callback(file_index); 10875} 10876}, false); 10877}, false); 10878} 10879 10880// 10881// page 404 (file not found) 10882// 10883function show_page_404(img_index) { 10884_via_buffer_hide_current_image(); 10885 10886set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_404); 10887 10888_via_image_index = img_index; 10889_via_image_id = _via_image_id_list[_via_image_index]; 10890_via_current_image_loaded = false; 10891img_fn_list_ith_entry_selected(_via_image_index, true); 10892 10893document.getElementById('page_404_filename').innerHTML = '[' + (_via_image_index+1) + ']' + _via_img_metadata[_via_image_id].filename; 10894} 10895 10896 10897// 10898// utils 10899// 10900 10901function is_url( s ) { 10902// @todo: ensure that this is sufficient to capture all image url 10903if ( s.startsWith('http://') || s.startsWith('https://') || s.startsWith('www.') ) { 10904return true; 10905} else { 10906return false; 10907} 10908} 10909 10910function get_filename_from_url( url ) { 10911return url.substring( url.lastIndexOf('/') + 1 ); 10912} 10913 10914function fixfloat(x) { 10915return parseFloat( x.toFixed(VIA_FLOAT_PRECISION) ); 10916} 10917 10918function shape_attribute_fixfloat(sa) { 10919for ( var attr in sa ) { 10920switch(attr) { 10921case 'x': 10922case 'y': 10923case 'width': 10924case 'height': 10925case 'r': 10926case 'rx': 10927case 'ry': 10928sa[attr] = fixfloat( sa[attr] ); 10929break; 10930case 'all_points_x': 10931case 'all_points_y': 10932for ( var i in sa[attr] ) { 10933sa[attr][i] = fixfloat( sa[attr][i] ); 10934} 10935} 10936} 10937} 10938 10939// start with the array having smallest number of elements 10940// check the remaining arrays if they all contain the elements of this shortest array 10941function array_intersect( array_list ) { 10942if ( array_list.length === 0 ) { 10943return []; 10944} 10945if ( array_list.length === 1 ) { 10946return array_list[0]; 10947} 10948 10949var shortest_array = array_list[0]; 10950var shortest_array_index = 0; 10951var i; 10952for ( i = 1; i < array_list.length; ++i ) { 10953if ( array_list[i].length < shortest_array.length ) { 10954shortest_array = array_list[i]; 10955shortest_array_index = i; 10956} 10957} 10958 10959var intersect = []; 10960var element_count = {}; 10961 10962var array_index_i; 10963for ( i = 0; i < array_list.length; ++i ) { 10964if ( i === 0 ) { 10965// in the first iteration, process the shortest element array 10966array_index_i = shortest_array_index; 10967} else { 10968array_index_i = i; 10969} 10970 10971var j; 10972for ( j = 0; j < array_list[array_index_i].length; ++j ) { 10973if ( element_count[ array_list[array_index_i][j] ] === (i-1) ) { 10974if ( i === array_list.length - 1 ) { 10975intersect.push( array_list[array_index_i][j] ); 10976element_count[ array_list[array_index_i][j] ] = 0; 10977} else { 10978element_count[ array_list[array_index_i][j] ] = i; 10979} 10980} else { 10981element_count[ array_list[array_index_i][j] ] = 0; 10982} 10983} 10984} 10985return intersect; 10986} 10987 10988function generate_img_index_list(input) { 10989var all_img_index_list = []; 10990 10991// condition: count format a,b 10992var count_format_img_index_list = []; 10993if ( input.prev_next_count.value !== '' ) { 10994var prev_next_split = input.prev_next_count.value.split(','); 10995if ( prev_next_split.length === 2 ) { 10996var prev = parseInt( prev_next_split[0] ); 10997var next = parseInt( prev_next_split[1] ); 10998var i; 10999for ( i = (_via_image_index - prev); i <= (_via_image_index + next); i++ ) { 11000count_format_img_index_list.push(i); 11001} 11002} 11003} 11004if ( count_format_img_index_list.length !== 0 ) { 11005all_img_index_list.push(count_format_img_index_list); 11006} 11007 11008//condition: image index list expression 11009var expr_img_index_list = []; 11010if ( input.img_index_list.value !== '' ) { 11011var img_index_expr = input.img_index_list.value.split(','); 11012if ( img_index_expr.length !== 0 ) { 11013var i; 11014for ( i = 0; i < img_index_expr.length; ++i ) { 11015if ( img_index_expr[i].includes('-') ) { 11016var ab = img_index_expr[i].split('-'); 11017var a = parseInt( ab[0] ) - 1; // 0 based indexing 11018var b = parseInt( ab[1] ) - 1; 11019var j; 11020for ( j = a; j <= b; ++j ) { 11021expr_img_index_list.push(j); 11022} 11023} else { 11024expr_img_index_list.push( parseInt(img_index_expr[i]) - 1 ); 11025} 11026} 11027} 11028} 11029if ( expr_img_index_list.length !== 0 ) { 11030all_img_index_list.push(expr_img_index_list); 11031} 11032 11033 11034// condition: regular expression 11035var regex_img_index_list = []; 11036if ( input.regex.value !== '' ) { 11037var regex = input.regex.value; 11038for ( var i=0; i < _via_image_filename_list.length; ++i ) { 11039var filename = _via_image_filename_list[i]; 11040if ( filename.match(regex) !== null ) { 11041regex_img_index_list.push(i); 11042} 11043} 11044} 11045if ( regex_img_index_list.length !== 0 ) { 11046all_img_index_list.push(regex_img_index_list); 11047} 11048 11049var intersect = array_intersect(all_img_index_list); 11050return intersect; 11051} 11052 11053if ( ! _via_is_debug_mode ) { 11054// warn user of possible loss of data 11055window.onbeforeunload = function (e) { 11056e = e || window.event; 11057 11058// For IE and Firefox prior to version 4 11059if (e) { 11060e.returnValue = 'Did you save your data?'; 11061} 11062 11063// For Safari 11064return 'Did you save your data?'; 11065}; 11066} 11067 11068// 11069// keep a record of image statistics (e.g. width, height, ...) 11070// 11071function img_stat_set(img_index, stat) { 11072if ( stat.length ) { 11073_via_img_stat[img_index] = stat; 11074} else { 11075delete _via_img_stat[img_index]; 11076} 11077} 11078 11079function img_stat_set_all() { 11080return new Promise( function(ok_callback, err_callback) { 11081var promise_list = []; 11082var img_id; 11083for ( var img_index in _via_image_id_list ) { 11084if ( ! _via_img_stat.hasOwnProperty(img_index) ) { 11085img_id = _via_image_id_list[img_index]; 11086if ( _via_img_metadata[img_id].file_attributes.hasOwnProperty('width') && 11087_via_img_metadata[img_id].file_attributes.hasOwnProperty('height') 11088) { 11089_via_img_stat[img_index] = [_via_img_metadata[img_id].file_attributes['width'], 11090_via_img_metadata[img_id].file_attributes['height'], 11091]; 11092} else { 11093promise_list.push( img_stat_get(img_index) ); 11094} 11095} 11096} 11097if ( promise_list.length ) { 11098Promise.all(promise_list).then( function(ok) { 11099ok_callback(); 11100}.bind(this), function(err) { 11101console.warn('Failed to read statistics of all images!'); 11102err_callback(); 11103}); 11104} else { 11105ok_callback(); 11106} 11107}.bind(this)); 11108} 11109 11110function img_stat_get(img_index) { 11111return new Promise( function(ok_callback, err_callback) { 11112var img_id = _via_image_id_list[img_index]; 11113var tmp_img = document.createElement('img'); 11114var tmp_file_object_url = null; 11115tmp_img.addEventListener('load', function() { 11116_via_img_stat[img_index] = [tmp_img.naturalWidth, tmp_img.naturalHeight]; 11117if ( tmp_file_object_url !== null ) { 11118URL.revokeObjectURL(tmp_file_object_url); 11119} 11120ok_callback(); 11121}.bind(this)); 11122tmp_img.addEventListener('error', function() { 11123_via_img_stat[img_index] = [-1, -1]; 11124if ( tmp_file_object_url !== null ) { 11125URL.revokeObjectURL(tmp_file_object_url); 11126} 11127ok_callback(); 11128}.bind(this)); 11129 11130if ( _via_img_fileref[img_id] instanceof File ) { 11131tmp_file_object_url = URL.createObjectURL(_via_img_fileref[img_id]); 11132tmp_img.src = tmp_file_object_url; 11133} else { 11134tmp_img.src = _via_img_src[img_id]; 11135} 11136}.bind(this)); 11137} 11138 11139 11140// pts = [x0,y0,x1,y1,....] 11141function polygon_to_bbox(pts) { 11142var xmin = +Infinity; 11143var xmax = -Infinity; 11144var ymin = +Infinity; 11145var ymax = -Infinity; 11146for ( var i = 0; i < pts.length; i = i + 2 ) { 11147if ( pts[i] > xmax ) { 11148xmax = pts[i]; 11149} 11150if ( pts[i] < xmin ) { 11151xmin = pts[i]; 11152} 11153if ( pts[i+1] > ymax ) { 11154ymax = pts[i+1]; 11155} 11156if ( pts[i+1] < ymin ) { 11157ymin = pts[i+1]; 11158} 11159} 11160return [xmin, ymin, xmax-xmin, ymax-ymin]; 11161} 11162</script> 11163<!-- END: Contents of file: via.js--> 11164</body> 11165</html> 11166