By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 via.html

View raw Download
text/html • 414.38 kiB
HTML document, ASCII text, with very long lines (738)
        
            
1
<!DOCTYPE html>
2
<html lang="en">
3
<head>
4
<meta charset="UTF-8">
5
<!--
6
VGG Image Annotator (via)
7
www.robots.ox.ac.uk/~vgg/software/via/
8
9
Copyright (c) 2016-2018, Abhishek Dutta, Visual Geometry Group, Oxford University and VIA Contributors..
10
All rights reserved.
11
12
Redistribution and use in source and binary forms, with or without
13
modification, are permitted provided that the following conditions are met:
14
15
Redistributions of source code must retain the above copyright notice, this
16
list of conditions and the following disclaimer.
17
Redistributions in binary form must reproduce the above copyright notice,
18
this list of conditions and the following disclaimer in the documentation
19
and/or other materials provided with the distribution.
20
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
POSSIBILITY 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
<!--
37
Development of VIA is supported by the EPSRC programme grant
38
Seebibyte: Visual Search for the Era of Big Data (EP/M013774/1).
39
Using Google Analytics, we record the usage of this application.
40
This data is used in reporting of this programme grant.
41
42
If you do not wish to share this data, you can safely remove the
43
javascript 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),
48
m=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');
50
ga('create', 'UA-20555581-2', 'auto');
51
ga('set', 'page', 'via-2.0.0.html');
52
ga('send', 'pageview');
53
</script>
54
55
<!-- START: Contents of file: via.css-->
56
<style>
57
/*
58
CSS style definitions for VIA 2.x.y
59
http://www.robots.ox.ac.uk/~vgg/software/via/
60
Author: Abhishek Dutta <adutta@robots.ox.ac.uk>
61
Date: moved from index.html to via.css on 13 June 2019
62
*/
63
64
body { min-width:200px; padding:0; margin:0; font-family:sans-serif; }
65
* { box-sizing: border-box; }
66
input[type=text] { border:1px solid #cccccc; margin:0.6rem 0; padding:0.2rem 0.4rem; }
67
a { text-decoration:none; }
68
textarea { 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 */
136
button.leftsidebar_accordion { font-size:large; background-color:#f2f2f2; cursor:pointer; padding:0.5em 0.5em; width:100%; text-align:left; border:0; outline:none; }
137
button.leftsidebar_accordion:focus { outline: none; }
138
button.leftsidebar_accordion.active, button.leftsidebar_accordion:hover { background-color: #e6e6e6; }
139
button.leftsidebar_accordion:after { content:'\02795'; color:#4d4d4d; float:right; }
140
button.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 */
222
ul.region_shape { font-size:xx-large; list-style-type:none; overflow:hidden; padding:0.4em 0; margin:0; }
223
ul.region_shape li { float:left; padding:0 0.2em; fill:#ffffff; stroke:#000000; }
224
ul.region_shape li:hover { cursor:pointer; fill:#ffffff; stroke:#ff0000; }
225
ul.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
<!--
281
SVG icon set definitions
282
Material 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">&times;</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">&uarr;</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">&darr;</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">&plus;</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">&minus;</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">&rtrif;</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">&larr;</span>
534
<span class="text_button" onclick="leftsidebar_increase_width()" title="Increase width of this toolbar panel">&rarr;</span>
535
<span class="text_button" onclick="leftsidebar_toggle()" title="Show/hide this toolbar panel">&ltrif;</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">&nbsp;</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">&plus;</span>
595
<span id="button_del_attribute" class="button" onclick="delete_existing_attribute_with_confirm()" title="Delete existing attribute">&minus;</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">&larr;</span>&nbsp;<span class="key">&uarr;</span>&nbsp;<span class="key">&rarr;</span>&nbsp;<span class="key">&darr;</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">&larr;</span>&nbsp;<span class="key">&rarr;</span></td>
659
<td>Move to next/previous image</td>
660
</tr>
661
<tr>
662
<td><span class="key">+</span>&nbsp;<span class="key">-</span>&nbsp;<span class="key">=</span></td>
663
<td>Zoom in/out/reset</td>
664
</tr>
665
<tr>
666
<td><span class="key">&uarr;</span></td>
667
<td>Update region label</td>
668
</tr>
669
<tr>
670
<td><span class="key">&darr;</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&nbsp; <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>&nbsp;<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 &rarr; 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 &rarr; 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 &rarr; 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 &rarr; 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 &rarr; 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 &rarr; 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 &rarr; 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 &lt; 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>&nbsp;</p>
965
<p>Copyright &copy; 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>
970
Copyright (c) 2016-2022, Abhishek Dutta, Visual Geometry Group, Oxford University and VIA Contributors.
971
All rights reserved.
972
973
Redistribution and use in source and binary forms, with or without
974
modification, are permitted provided that the following conditions are met:
975
976
Redistributions of source code must retain the above copyright notice, this
977
list of conditions and the following disclaimer.
978
Redistributions in binary form must reproduce the above copyright notice,
979
this list of conditions and the following disclaimer in the documentation
980
and/or other materials provided with the distribution.
981
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot;
982
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
983
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
984
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
985
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
986
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
987
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
988
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
989
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
990
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
991
POSSIBILITY 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
998
items 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
/*
1005
VGG Image Annotator (via)
1006
www.robots.ox.ac.uk/~vgg/software/via/
1007
1008
Copyright (c) 2016-2019, Abhishek Dutta, Visual Geometry Group, Oxford University and VIA Contributors.
1009
All rights reserved.
1010
1011
Redistribution and use in source and binary forms, with or without
1012
modification, are permitted provided that the following conditions are met:
1013
1014
Redistributions of source code must retain the above copyright notice, this
1015
list of conditions and the following disclaimer.
1016
Redistributions in binary form must reproduce the above copyright notice,
1017
this list of conditions and the following disclaimer in the documentation
1018
and/or other materials provided with the distribution.
1019
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
1020
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1021
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1022
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
1023
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
1024
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
1025
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
1026
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
1027
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
1028
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
1029
POSSIBILITY OF SUCH DAMAGE.
1030
*/
1031
1032
/*
1033
Links:
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
1038
This 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
1056
var VIA_VERSION = '2.0.12';
1057
var VIA_NAME = 'VGG Image Annotator';
1058
var VIA_SHORT_NAME = 'VIA';
1059
var VIA_REGION_SHAPE = { RECT:'rect',
1060
CIRCLE:'circle',
1061
ELLIPSE:'ellipse',
1062
POLYGON:'polygon',
1063
POINT:'point',
1064
POLYLINE:'polyline'
1065
};
1066
1067
var VIA_ATTRIBUTE_TYPE = { TEXT:'text',
1068
CHECKBOX:'checkbox',
1069
RADIO:'radio',
1070
IMAGE:'image',
1071
DROPDOWN:'dropdown'
1072
};
1073
1074
var VIA_DISPLAY_AREA_CONTENT_NAME = {IMAGE:'image_panel',
1075
IMAGE_GRID:'image_grid_panel',
1076
SETTINGS:'settings_panel',
1077
PAGE_404:'page_404',
1078
PAGE_GETTING_STARTED:'page_getting_started',
1079
PAGE_ABOUT:'page_about',
1080
PAGE_START_INFO:'page_start_info',
1081
PAGE_LICENSE:'page_license'
1082
};
1083
1084
var VIA_ANNOTATION_EDITOR_MODE = {SINGLE_REGION:'single_region',
1085
ALL_REGIONS:'all_regions'};
1086
var VIA_ANNOTATION_EDITOR_PLACEMENT = {NEAR_REGION:'NEAR_REGION',
1087
IMAGE_BOTTOM:'IMAGE_BOTTOM',
1088
DISABLE:'DISABLE'};
1089
1090
var VIA_REGION_EDGE_TOL = 5; // pixel
1091
var VIA_REGION_CONTROL_POINT_SIZE = 2;
1092
var VIA_POLYGON_VERTEX_MATCH_TOL = 10;
1093
var VIA_REGION_MIN_DIM = 3;
1094
var VIA_MOUSE_CLICK_TOL = 2;
1095
var VIA_ELLIPSE_EDGE_TOL = 0.2; // euclidean distance
1096
var VIA_THETA_TOL = Math.PI/18; // 10 degrees
1097
var VIA_POLYGON_RESIZE_VERTEX_OFFSET = 100;
1098
var VIA_CANVAS_DEFAULT_ZOOM_LEVEL_INDEX = 3;
1099
var 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];
1100
var VIA_REGION_COLOR_LIST = ["#E69F00", "#56B4E9", "#009E73", "#D55E00", "#CC79A7", "#F0E442", "#ffffff"];
1101
// radius of control points in all shapes
1102
var VIA_REGION_SHAPES_POINTS_RADIUS = 3;
1103
// radius of control points in a point
1104
var VIA_REGION_POINT_RADIUS = 3;
1105
var VIA_REGION_POINT_RADIUS_DEFAULT = 3;
1106
1107
var VIA_THEME_REGION_BOUNDARY_WIDTH = 3;
1108
var VIA_THEME_BOUNDARY_LINE_COLOR = "black";
1109
// var VIA_THEME_BOUNDARY_FILL_COLOR = "yellow";
1110
var VIA_THEME_BOUNDARY_FILL_COLOR = "magenta";
1111
var VIA_THEME_SEL_REGION_FILL_COLOR = "#808080";
1112
var VIA_THEME_SEL_REGION_FILL_BOUNDARY_COLOR = "yellow";
1113
var VIA_THEME_SEL_REGION_OPACITY = 0.5;
1114
var VIA_THEME_MESSAGE_TIMEOUT_MS = 6000;
1115
var VIA_THEME_CONTROL_POINT_COLOR = '#ff0000';
1116
1117
var VIA_CSV_SEP = ',';
1118
var VIA_CSV_QUOTE_CHAR = '"';
1119
var VIA_CSV_KEYVAL_SEP = ':';
1120
1121
var _via_img_metadata = {}; // data structure to store loaded images metadata
1122
var _via_img_src = {}; // image content {abs. path, url, base64 data, etc}
1123
var _via_img_fileref = {}; // reference to local images selected by using browser file selector
1124
var _via_img_count = 0; // count of the loaded images
1125
var _via_canvas_regions = []; // image regions spec. in canvas space
1126
var _via_canvas_scale = 1.0;// current scale of canvas image
1127
1128
var _via_image_id = ''; // id={filename+length} of current image
1129
var _via_image_index = -1; // index
1130
1131
var _via_current_image_filename;
1132
var _via_current_image;
1133
var _via_current_image_width;
1134
var _via_current_image_height;
1135
1136
// a record of image statistics (e.g. width, height)
1137
var _via_img_stat = {};
1138
var _via_is_all_img_stat_read_ongoing = false;
1139
var _via_img_stat_current_img_index = false;
1140
1141
// image canvas
1142
var _via_display_area = document.getElementById('display_area');
1143
var _via_img_panel = document.getElementById('image_panel');
1144
var _via_reg_canvas = document.getElementById('region_canvas');
1145
var _via_reg_ctx; // initialized in _via_init()
1146
var _via_canvas_width, _via_canvas_height;
1147
1148
// canvas zoom
1149
var _via_canvas_zoom_level_index = VIA_CANVAS_DEFAULT_ZOOM_LEVEL_INDEX; // 1.0
1150
var _via_canvas_scale_without_zoom = 1.0;
1151
1152
// state of the application
1153
var _via_is_user_drawing_region = false;
1154
var _via_current_image_loaded = false;
1155
var _via_is_window_resized = false;
1156
var _via_is_user_resizing_region = false;
1157
var _via_is_user_moving_region = false;
1158
var _via_is_user_drawing_polygon = false;
1159
var _via_is_region_selected = false;
1160
var _via_is_all_region_selected = false;
1161
var _via_is_loaded_img_list_visible = false;
1162
var _via_is_attributes_panel_visible = false;
1163
var _via_is_reg_attr_panel_visible = false;
1164
var _via_is_file_attr_panel_visible = false;
1165
var _via_is_canvas_zoomed = false;
1166
var _via_is_loading_current_image = false;
1167
var _via_is_region_id_visible = true;
1168
var _via_is_region_boundary_visible = true;
1169
var _via_is_region_info_visible = false;
1170
var _via_is_ctrl_pressed = false;
1171
var _via_is_debug_mode = false;
1172
var _via_is_message_visible = true;
1173
1174
// region
1175
var _via_current_shape = VIA_REGION_SHAPE.RECT;
1176
var _via_current_polygon_region_id = -1;
1177
var _via_user_sel_region_id = -1;
1178
var _via_click_x0 = 0; var _via_click_y0 = 0;
1179
var _via_click_x1 = 0; var _via_click_y1 = 0;
1180
var _via_region_click_x, _via_region_click_y;
1181
var _via_region_edge = [-1, -1];
1182
var _via_current_x = 0; var _via_current_y = 0;
1183
1184
// region copy/paste
1185
var _via_region_selected_flag = []; // region select flag for current image
1186
var _via_copied_image_regions = [];
1187
var _via_paste_to_multiple_images_input;
1188
1189
// message
1190
var _via_message_clear_timer;
1191
1192
// attributes
1193
var _via_attribute_being_updated = 'region'; // {region, file}
1194
var _via_attributes = { 'region':{}, 'file':{} };
1195
var _via_current_attribute_id = '';
1196
1197
// region group color
1198
var _via_canvas_regions_group_color = {}; // color of each region
1199
1200
// invoke a method after receiving user input
1201
var _via_user_input_ok_handler = null;
1202
var _via_user_input_cancel_handler = null;
1203
var _via_user_input_data = {};
1204
1205
// annotation editor
1206
var _via_annotaion_editor_panel = document.getElementById('annotation_editor_panel');
1207
var _via_metadata_being_updated = 'region'; // {region, file}
1208
var _via_annotation_editor_mode = VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION;
1209
1210
// persistence to local storage
1211
var _via_is_local_storage_available = false;
1212
var _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.
1221
var _via_image_id_list = []; // array of all image id (in order they were added by user)
1222
var _via_image_filename_list = []; // array of all image filename
1223
var _via_image_load_error = []; // {true, false}
1224
var _via_image_filepath_resolved = []; // {true, false}
1225
var _via_image_filepath_id_list = []; // path for each file
1226
1227
var _via_reload_img_fn_list_table = true;
1228
var _via_img_fn_list_img_index_list = []; // image index list of images show in img_fn_list
1229
var _via_img_fn_list_html = []; // html representation of image filename list
1230
1231
// image grid
1232
var image_grid_panel = document.getElementById('image_grid_panel');
1233
var _via_display_area_content_name = ''; // describes what is currently shown in display area
1234
var _via_display_area_content_name_prev = '';
1235
var _via_image_grid_requires_update = false;
1236
var _via_image_grid_content_overflow = false;
1237
var _via_image_grid_load_ongoing = false;
1238
var _via_image_grid_page_first_index = 0; // array index in _via_img_fn_list_img_index_list[]
1239
var _via_image_grid_page_last_index = -1;
1240
var _via_image_grid_selected_img_index_list = [];
1241
var _via_image_grid_page_img_index_list = []; // list of all image index in current page of image grid
1242
var _via_image_grid_visible_img_index_list = []; // list of images currently visible in grid
1243
var _via_image_grid_mousedown_img_index = -1;
1244
var _via_image_grid_mouseup_img_index = -1;
1245
var _via_image_grid_img_index_list = []; // list of all image index in the image grid
1246
var _via_image_grid_region_index_list = []; // list of all image index in the image grid
1247
var _via_image_grid_group = {}; // {'value':[image_index_list]}
1248
var _via_image_grid_group_var = []; // {type, name, value}
1249
var _via_image_grid_group_show_all = false;
1250
var _via_image_grid_stack_prev_page = []; // stack of first img index of every page navigated so far
1251
1252
// image buffer
1253
var VIA_IMG_PRELOAD_INDICES = [1, -1, 2, 3, -2, 4]; // for any image, preload previous 2 and next 4 images
1254
var VIA_IMG_PRELOAD_COUNT = 4;
1255
var _via_buffer_preload_img_index = -1;
1256
var _via_buffer_img_index_list = [];
1257
var _via_buffer_img_shown_timestamp = [];
1258
var _via_preload_img_promise_list = [];
1259
1260
// via settings
1261
var _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
1288
var invisible_file_input = document.getElementById("invisible_file_input");
1289
var display_area = document.getElementById("display_area");
1290
var ui_top_panel = document.getElementById("ui_top_panel");
1291
var image_panel = document.getElementById("image_panel");
1292
var img_buffer_now = document.getElementById("img_buffer_now");
1293
1294
var annotation_list_snippet = document.getElementById("annotation_list_snippet");
1295
var annotation_textarea = document.getElementById("annotation_textarea");
1296
1297
var img_fn_list_panel = document.getElementById('img_fn_list_panel');
1298
var img_fn_list = document.getElementById('img_fn_list');
1299
var attributes_panel = document.getElementById('attributes_panel');
1300
var leftsidebar = document.getElementById('leftsidebar');
1301
1302
var BBOX_LINE_WIDTH = 4;
1303
var BBOX_SELECTED_OPACITY = 0.3;
1304
var BBOX_BOUNDARY_FILL_COLOR_ANNOTATED = "#f2f2f2";
1305
var BBOX_BOUNDARY_FILL_COLOR_NEW = "#aaeeff";
1306
var BBOX_BOUNDARY_LINE_COLOR = "#1a1a1a";
1307
var BBOX_SELECTED_FILL_COLOR = "#ffffff";
1308
1309
var VIA_ANNOTATION_EDITOR_HEIGHT_CHANGE = 5; // in percent
1310
var VIA_ANNOTATION_EDITOR_FONTSIZE_CHANGE = 0.1; // in rem
1311
var VIA_IMAGE_GRID_IMG_HEIGHT_CHANGE = 20; // in percent
1312
var VIA_LEFTSIDEBAR_WIDTH_CHANGE = 1; // in rem
1313
var VIA_POLYGON_SEGMENT_SUBTENDED_ANGLE = 5; // in degree (used to approximate shapes using polygon)
1314
var VIA_FLOAT_PRECISION = 3; // number of decimal places to include in float values
1315
1316
// COCO Export
1317
var VIA_COCO_EXPORT_RSHAPE = ['rect', 'circle', 'ellipse', 'polygon', 'point'];
1318
var VIA_COCO_EXPORT_ATTRIBUTE_TYPE = [VIA_ATTRIBUTE_TYPE.DROPDOWN,
1319
VIA_ATTRIBUTE_TYPE.RADIO];
1320
//
1321
// Data structure to store metadata about file and regions
1322
//
1323
function file_metadata(filename, size) {
1324
this.filename = filename;
1325
this.size = size; // file size in bytes
1326
this.regions = []; // array of file_region()
1327
this.file_attributes = {}; // image attributes
1328
}
1329
1330
function file_region() {
1331
this.shape_attributes = {}; // region shape attributes
1332
this.region_attributes = {}; // region attributes
1333
}
1334
1335
//
1336
// Initialization routine
1337
//
1338
function _via_init() {
1339
console.log(VIA_NAME);
1340
show_message(VIA_NAME + ' (' + VIA_SHORT_NAME + ') version ' + VIA_VERSION +
1341
'. Ready !', 2*VIA_THEME_MESSAGE_TIMEOUT_MS);
1342
1343
if ( _via_is_debug_mode ) {
1344
document.getElementById('ui_top_panel').innerHTML += '<span>DEBUG MODE</span>';
1345
}
1346
1347
document.getElementById('img_fn_list').style.display = 'block';
1348
document.getElementById('leftsidebar').style.display = 'table-cell';
1349
1350
// initialize default project
1351
project_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
1362
image_grid_init();
1363
1364
show_single_image_view();
1365
init_leftsidebar_accordion();
1366
attribute_update_panel_set_active_button();
1367
annotation_editor_set_active_button();
1368
init_message_panel();
1369
1370
// run attached sub-modules (if any)
1371
// e.g. demo modules
1372
if (typeof _via_load_submodules === 'function') {
1373
console.log('Loading VIA submodule');
1374
setTimeout( async function() {
1375
await _via_load_submodules();
1376
}, 100);
1377
}
1378
1379
}
1380
1381
function _via_init_reg_canvas_context() {
1382
_via_reg_ctx = _via_reg_canvas.getContext('2d');
1383
}
1384
1385
function _via_init_keyboard_handlers() {
1386
window.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
1392
function _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
1410
function download_as_image() {
1411
if ( _via_display_area_content_name !== VIA_DISPLAY_AREA_CONTENT_NAME['IMAGE'] ) {
1412
show_message('This functionality is only available in single image view mode');
1413
return;
1414
} else {
1415
var c = document.createElement('canvas');
1416
1417
// ensures that downloaded image is scaled at current zoom level
1418
c.width = _via_reg_canvas.width;
1419
c.height = _via_reg_canvas.height;
1420
1421
var ct = c.getContext('2d');
1422
// draw current image
1423
ct.drawImage(_via_current_image, 0, 0, _via_reg_canvas.width, _via_reg_canvas.height);
1424
// draw current regions
1425
ct.drawImage(_via_reg_canvas, 0, 0);
1426
1427
var cur_img_mime = 'image/jpeg';
1428
if ( _via_current_image.src.startsWith('data:') ) {
1429
var c1 = _via_current_image.src.indexOf(':', 0);
1430
var c2 = _via_current_image.src.indexOf(';', c1);
1431
cur_img_mime = _via_current_image.src.substring(c1 + 1, c2);
1432
}
1433
1434
// extract image data from canvas
1435
var saved_img = c.toDataURL(cur_img_mime);
1436
saved_img.replace(cur_img_mime, "image/octet-stream");
1437
1438
// simulate user click to trigger download of image
1439
var a = document.createElement('a');
1440
a.href = saved_img;
1441
a.target = '_blank';
1442
a.download = _via_current_image_filename;
1443
1444
// simulate a mouse click event
1445
var event = new MouseEvent('click', {
1446
view: window,
1447
bubbles: true,
1448
cancelable: true
1449
});
1450
1451
a.dispatchEvent(event);
1452
}
1453
}
1454
1455
//
1456
// Display area content
1457
//
1458
function clear_display_area() {
1459
var panels = document.getElementsByClassName('display_area_content');
1460
var i;
1461
for ( i = 0; i < panels.length; ++i ) {
1462
panels[i].classList.add('display_none');
1463
}
1464
}
1465
1466
function is_content_name_valid(content_name) {
1467
var e;
1468
for ( e in VIA_DISPLAY_AREA_CONTENT_NAME ) {
1469
if ( VIA_DISPLAY_AREA_CONTENT_NAME[e] === content_name ) {
1470
return true;
1471
}
1472
}
1473
return false;
1474
}
1475
1476
function show_home_panel() {
1477
show_single_image_view();
1478
}
1479
1480
function set_display_area_content(content_name) {
1481
if ( is_content_name_valid(content_name) ) {
1482
_via_display_area_content_name_prev = _via_display_area_content_name;
1483
clear_display_area();
1484
var p = document.getElementById(content_name);
1485
p.classList.remove('display_none');
1486
_via_display_area_content_name = content_name;
1487
}
1488
}
1489
1490
function show_single_image_view() {
1491
if (_via_current_image_loaded) {
1492
img_fn_list_clear_all_style();
1493
_via_show_img(_via_image_index);
1494
set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE);
1495
annotation_editor_update_content();
1496
1497
var p = document.getElementById('toolbar_image_grid_toggle');
1498
p.firstChild.setAttribute('xlink:href', '#icon_gridon');
1499
p.childNodes[1].innerHTML = 'Switch to Image Grid View';
1500
} else {
1501
set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_START_INFO);
1502
}
1503
}
1504
1505
function show_image_grid_view() {
1506
if (_via_current_image_loaded) {
1507
img_fn_list_clear_all_style();
1508
set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID);
1509
image_grid_toolbar_update_group_by_select();
1510
1511
if ( _via_image_grid_group_var.length === 0 ) {
1512
image_grid_show_all_project_images();
1513
}
1514
annotation_editor_update_content();
1515
1516
var p = document.getElementById('toolbar_image_grid_toggle');
1517
p.firstChild.setAttribute('xlink:href', '#icon_gridoff');
1518
p.childNodes[1].innerHTML = 'Switch to Single Image View';
1519
1520
//edit_file_metadata_in_annotation_editor();
1521
} else {
1522
set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_START_INFO);
1523
}
1524
}
1525
1526
//
1527
// Handlers for top navigation bar
1528
//
1529
function sel_local_images() {
1530
// source: https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications
1531
if (invisible_file_input) {
1532
invisible_file_input.setAttribute('multiple', 'multiple');
1533
invisible_file_input.accept = '.jpg,.jpeg,.png,.bmp';
1534
invisible_file_input.onchange = project_file_add_local;
1535
invisible_file_input.click();
1536
}
1537
}
1538
1539
// invoked by menu-item buttons in HTML UI
1540
function download_all_region_data(type, file_extension) {
1541
if ( typeof(file_extension) === 'undefined' ) {
1542
file_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
1546
pack_via_metadata(type).then( function(data) {
1547
var blob_attr = {type: 'text/'+file_extension+';charset=utf-8'};
1548
var all_region_data_blob = new Blob(data, blob_attr);
1549
1550
var filename = 'via_export';
1551
if(typeof(_via_settings) !== 'undefined' &&
1552
_via_settings.hasOwnProperty('project') &&
1553
_via_settings['project']['name'] !== '') {
1554
filename = _via_settings['project']['name'];
1555
}
1556
if ( file_extension !== 'csv' || file_extension !== 'json' ) {
1557
filename += '_' + type + '.' + file_extension;
1558
}
1559
save_data_to_local_file(all_region_data_blob, filename);
1560
}.bind(this), function(err) {
1561
show_message('Failed to download data: [' + err + ']');
1562
}.bind(this));
1563
}
1564
1565
function sel_local_data_file(type) {
1566
if (invisible_file_input) {
1567
switch(type) {
1568
case 'annotations':
1569
invisible_file_input.accept='.csv,.json';
1570
invisible_file_input.onchange = import_annotations_from_file;
1571
break;
1572
1573
case 'annotations_coco':
1574
invisible_file_input.accept='.json';
1575
invisible_file_input.onchange = load_coco_annotations_json_file;
1576
break;
1577
1578
case 'files_url':
1579
invisible_file_input.accept='';
1580
invisible_file_input.onchange = import_files_url_from_file;
1581
break;
1582
1583
case 'attributes':
1584
invisible_file_input.accept='json';
1585
invisible_file_input.onchange = project_import_attributes_from_file;
1586
break;
1587
1588
default:
1589
console.log('sel_local_data_file() : unknown type ' + type);
1590
return;
1591
}
1592
invisible_file_input.removeAttribute('multiple');
1593
invisible_file_input.click();
1594
}
1595
}
1596
1597
//
1598
// Data Importer
1599
//
1600
function import_files_url_from_file(event) {
1601
var selected_files = event.target.files;
1602
var i, file;
1603
for ( i = 0; i < selected_files.length; ++i ) {
1604
file = selected_files[i];
1605
load_text_file(file, import_files_url_from_csv);
1606
}
1607
}
1608
1609
function import_annotations_from_file(event) {
1610
var selected_files = event.target.files;
1611
var i, file;
1612
for ( i = 0; i < selected_files.length; ++i ) {
1613
file = selected_files[i];
1614
switch ( file.type ) {
1615
case '': // Fall-through // Windows 10: Firefox and Chrome do not report filetype
1616
show_message('File type for ' + file.name + ' cannot be determined! Assuming text/plain.');
1617
case 'text/plain': // Fall-through
1618
case 'application/vnd.ms-excel': // Fall-through // @todo: filetype of VIA csv annotations in Windows 10 , fix this (reported by @Eli Walker)
1619
case 'text/csv':
1620
load_text_file(file, import_annotations_from_csv);
1621
break;
1622
1623
case 'text/json': // Fall-through
1624
case 'application/json':
1625
load_text_file(file, import_annotations_from_json);
1626
break;
1627
1628
default:
1629
show_message('Annotations cannot be imported from file of type ' + file.type);
1630
break;
1631
}
1632
}
1633
}
1634
1635
function load_coco_annotations_json_file(event) {
1636
load_text_file(event.target.files[0], import_coco_annotations_from_json);
1637
}
1638
1639
function import_annotations_from_csv(data) {
1640
return new Promise( function(ok_callback, err_callback) {
1641
if ( data === '' || typeof(data) === 'undefined') {
1642
err_callback();
1643
}
1644
1645
var region_import_count = 0;
1646
var malformed_csv_lines_count = 0;
1647
var file_added_count = 0;
1648
1649
var line_split_regex = new RegExp('\n|\r|\r\n', 'g');
1650
var csvdata = data.split(line_split_regex);
1651
1652
var parsed_header = parse_csv_header_line(csvdata[0]);
1653
if ( ! parsed_header.is_header ) {
1654
show_message('Header line missing in the CSV file');
1655
err_callback();
1656
return;
1657
}
1658
1659
var n = csvdata.length;
1660
var i;
1661
var first_img_id = '';
1662
for ( i = 1; i < n; ++i ) {
1663
// ignore blank lines
1664
if (csvdata[i].charAt(0) === '\n' || csvdata[i].charAt(0) === '') {
1665
continue;
1666
}
1667
1668
var d = parse_csv_line(csvdata[i]);
1669
1670
// check if csv line was malformed
1671
if ( d.length !== parsed_header.csv_column_count ) {
1672
malformed_csv_lines_count += 1;
1673
continue;
1674
}
1675
1676
var filename = d[parsed_header.filename_index];
1677
var size = d[parsed_header.size_index];
1678
var img_id = _via_get_image_id(filename, size);
1679
1680
// check if file is already present in this project
1681
if ( ! _via_img_metadata.hasOwnProperty(img_id) ) {
1682
img_id = project_add_new_file(filename, size);
1683
if ( _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
}
1688
file_added_count += 1;
1689
1690
if ( first_img_id === '' ) {
1691
first_img_id = img_id;
1692
}
1693
}
1694
1695
// copy file attributes
1696
if ( d[parsed_header.file_attr_index] !== '"{}"') {
1697
var fattr = d[parsed_header.file_attr_index];
1698
fattr = remove_prefix_suffix_quotes( fattr );
1699
fattr = unescape_from_csv( fattr );
1700
1701
var m = json_str_to_map( fattr );
1702
for( var key in m ) {
1703
_via_img_metadata[img_id].file_attributes[key] = m[key];
1704
1705
// add this file attribute to _via_attributes
1706
if ( ! _via_attributes['file'].hasOwnProperty(key) ) {
1707
_via_attributes['file'][key] = { 'type':'text' };
1708
}
1709
}
1710
}
1711
1712
var region_i = new file_region();
1713
// copy regions shape attributes
1714
if ( d[parsed_header.region_shape_attr_index] !== '"{}"' ) {
1715
var sattr = d[parsed_header.region_shape_attr_index];
1716
sattr = remove_prefix_suffix_quotes( sattr );
1717
sattr = unescape_from_csv( sattr );
1718
1719
var m = json_str_to_map( sattr );
1720
for ( var key in m ) {
1721
region_i.shape_attributes[key] = m[key];
1722
}
1723
}
1724
1725
// copy region attributes
1726
if ( d[parsed_header.region_attr_index] !== '"{}"' ) {
1727
var rattr = d[parsed_header.region_attr_index];
1728
rattr = remove_prefix_suffix_quotes( rattr );
1729
rattr = unescape_from_csv( rattr );
1730
1731
var m = json_str_to_map( rattr );
1732
for ( var key in m ) {
1733
region_i.region_attributes[key] = m[key];
1734
1735
// add this region attribute to _via_attributes
1736
if ( ! _via_attributes['region'].hasOwnProperty(key) ) {
1737
_via_attributes['region'][key] = { 'type':'text' };
1738
}
1739
}
1740
}
1741
1742
// add regions only if they are present
1743
if (Object.keys(region_i.shape_attributes).length > 0 ||
1744
Object.keys(region_i.region_attributes).length > 0 ) {
1745
_via_img_metadata[img_id].regions.push(region_i);
1746
region_import_count += 1;
1747
}
1748
}
1749
show_message('Import Summary : [' + file_added_count + '] new files, ' +
1750
'[' + region_import_count + '] regions, ' +
1751
'[' + malformed_csv_lines_count + '] malformed csv lines.');
1752
1753
if ( file_added_count ) {
1754
update_img_fn_list();
1755
}
1756
1757
if ( _via_current_image_loaded ) {
1758
if ( region_import_count ) {
1759
update_attributes_update_panel();
1760
annotation_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 {
1766
if ( file_added_count ) {
1767
var first_img_index = _via_image_id_list.indexOf(first_img_id);
1768
_via_show_img( first_img_index );
1769
}
1770
}
1771
ok_callback([file_added_count, region_import_count, malformed_csv_lines_count]);
1772
});
1773
}
1774
1775
function parse_csv_header_line(line) {
1776
var header_via_10x = '#filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes'; // VIA versions 1.0.x
1777
var header_via_11x = 'filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes'; // VIA version 1.1.x
1778
1779
if ( line === header_via_10x || line === header_via_11x ) {
1780
return { '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 {
1789
return { 'is_header':false };
1790
}
1791
}
1792
1793
// see http://cocodataset.org/#format-data
1794
function import_coco_annotations_from_json(data_str) {
1795
return new Promise( function(ok_callback, err_callback) {
1796
if (data_str === '' || typeof(data_str) === 'undefined') {
1797
show_message('Empty file');
1798
return;
1799
}
1800
var coco = JSON.parse(data_str);
1801
if( !coco.hasOwnProperty('info') ||
1802
!coco.hasOwnProperty('categories') ||
1803
!coco.hasOwnProperty('annotations') ||
1804
!coco.hasOwnProperty('images') ) {
1805
show_message('File does not contain valid annotations in COCO format.');
1806
return;
1807
}
1808
1809
// create _via_attributes from coco['categories']
1810
var category_id_to_attribute_name = {};
1811
for( var i in coco['categories'] ) {
1812
var sc = coco['categories'][i]['supercategory'];
1813
var cid = coco['categories'][i]['id'];
1814
var cname = coco['categories'][i]['name'];
1815
if( !_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;
1823
category_id_to_attribute_name[cid] = sc;
1824
}
1825
// if more than 5 options, convert the attribute type to DROPDOWN
1826
for( var attr_name in _via_attributes['region'] ) {
1827
if( 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
1833
var image_id_to_annotation_index = {};
1834
for ( var annotation_index in coco['annotations'] ) {
1835
var coco_image_id = coco.annotations[annotation_index]['image_id'];
1836
if ( !image_id_to_annotation_index.hasOwnProperty(coco_image_id) ) {
1837
image_id_to_annotation_index[coco_image_id] = [];
1838
}
1839
image_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;
1847
var imported_file_count = 0;
1848
var imported_region_count = 0;
1849
for ( var coco_img_index in coco['images'] ) {
1850
var coco_img_id = coco['images'][coco_img_index]['id'];
1851
var filename;
1852
if ( coco.images[coco_img_index].hasOwnProperty('coco_url') &&
1853
coco.images[coco_img_index]['coco_url'] !== "") {
1854
filename = coco.images[coco_img_index]['coco_url'];
1855
} else {
1856
filename = 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
1871
if ( image_id_to_annotation_index.hasOwnProperty(coco_img_id) ) {
1872
for ( var i in image_id_to_annotation_index[coco_img_id] ) {
1873
var annotation_i = coco['annotations'][ image_id_to_annotation_index[coco_img_id][i] ];
1874
var bbox_from_polygon = polygon_to_bbox(annotation_i['segmentation'][0]);
1875
1876
// ensure rectangles get imported as rectangle (and not as polygon)
1877
var is_rectangle = true;
1878
for (var j = 0; j < annotation_i['bbox'].length; ++j) {
1879
if (annotation_i['bbox'][j] !== bbox_from_polygon[j]) {
1880
is_rectangle = false;
1881
break;
1882
}
1883
}
1884
1885
var region_i = { 'shape_attributes': {}, 'region_attributes': {} };
1886
var attribute_name = category_id_to_attribute_name[ annotation_i['category_id'] ];
1887
var attribute_value = annotation_i['category_id'].toString();
1888
region_i['region_attributes'][attribute_name] = attribute_value;
1889
1890
if ( annotation_i['segmentation'][0].length === 8 && is_rectangle ) {
1891
region_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 {
1897
region_i['shape_attributes'] = { 'name':'polygon',
1898
'all_points_x':[],
1899
'all_points_y':[]};
1900
for ( var j = 0; j < annotation_i['segmentation'][0].length; j = j + 2 ) {
1901
region_i['shape_attributes']['all_points_x'].push( annotation_i['segmentation'][0][j] );
1902
region_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);
1906
imported_region_count = imported_region_count + 1;
1907
}
1908
}
1909
}
1910
show_message('Import Summary : [' + _via_img_count + '] new files, ' +
1911
'[' + imported_region_count + '] regions.');
1912
1913
if(_via_img_count) {
1914
update_img_fn_list();
1915
}
1916
1917
if(_via_current_image_loaded) {
1918
if(imported_region_count) {
1919
update_attributes_update_panel();
1920
annotation_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 {
1926
if(_via_img_count) {
1927
_via_show_img(0);
1928
}
1929
}
1930
ok_callback([_via_img_count, imported_region_count, 0]);
1931
});
1932
}
1933
1934
function import_annotations_from_json(data_str) {
1935
return new Promise( function(ok_callback, err_callback) {
1936
if (data_str === '' || typeof(data_str) === 'undefined') {
1937
return;
1938
}
1939
1940
var d = JSON.parse(data_str);
1941
var region_import_count = 0;
1942
var file_added_count = 0;
1943
var malformed_entries_count = 0;
1944
for (var img_id in d) {
1945
if ( ! _via_img_metadata.hasOwnProperty(img_id) ) {
1946
project_add_new_file(d[img_id].filename, d[img_id].size, img_id);
1947
if ( _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
}
1952
file_added_count += 1;
1953
}
1954
1955
// copy file attributes
1956
for ( var key in d[img_id].file_attributes ) {
1957
if ( key === '' ) {
1958
continue;
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
1964
if ( ! _via_attributes['file'].hasOwnProperty(key) ) {
1965
_via_attributes['file'][key] = { 'type':'text' };
1966
}
1967
}
1968
1969
// copy regions
1970
var regions = d[img_id].regions;
1971
for ( var i in regions ) {
1972
var region_i = new file_region();
1973
for ( var sid in regions[i].shape_attributes ) {
1974
region_i.shape_attributes[sid] = regions[i].shape_attributes[sid];
1975
}
1976
for ( var rid in regions[i].region_attributes ) {
1977
if ( rid === '' ) {
1978
continue;
1979
}
1980
1981
region_i.region_attributes[rid] = regions[i].region_attributes[rid];
1982
1983
// add this region attribute to _via_attributes
1984
if ( ! _via_attributes['region'].hasOwnProperty(rid) ) {
1985
_via_attributes['region'][rid] = { 'type':'text' };
1986
}
1987
}
1988
1989
// add regions only if they are present
1990
if ( Object.keys(region_i.shape_attributes).length > 0 ||
1991
Object.keys(region_i.region_attributes).length > 0 ) {
1992
_via_img_metadata[img_id].regions.push(region_i);
1993
region_import_count += 1;
1994
}
1995
}
1996
}
1997
show_message('Import Summary : [' + file_added_count + '] new files, ' +
1998
'[' + region_import_count + '] regions, ' +
1999
'[' + malformed_entries_count + '] malformed entries.');
2000
2001
if ( file_added_count ) {
2002
update_img_fn_list();
2003
}
2004
2005
if ( _via_current_image_loaded ) {
2006
if ( region_import_count ) {
2007
update_attributes_update_panel();
2008
annotation_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 {
2014
if ( file_added_count ) {
2015
_via_show_img(0);
2016
}
2017
}
2018
2019
ok_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
2025
function parse_csv_line(s, field_separator) {
2026
if (typeof(s) === 'undefined' || s.length === 0 ) {
2027
return [];
2028
}
2029
2030
if (typeof(field_separator) === 'undefined') {
2031
field_separator = ',';
2032
}
2033
var double_quote_seen = false;
2034
var start = 0;
2035
var d = [];
2036
2037
var i = 0;
2038
while ( i < s.length) {
2039
if (s.charAt(i) === field_separator) {
2040
if (double_quote_seen) {
2041
// field separator inside double quote is ignored
2042
i = i + 1;
2043
} else {
2044
//var part = s.substr(start, i - start);
2045
d.push( s.substr(start, i - start) );
2046
start = i + 1;
2047
i = i + 1;
2048
}
2049
} else {
2050
if (s.charAt(i) === '"') {
2051
if (double_quote_seen) {
2052
if (s.charAt(i+1) === '"') {
2053
// ignore escaped double quotes
2054
i = i + 2;
2055
} else {
2056
// closing of double quote
2057
double_quote_seen = false;
2058
i = i + 1;
2059
}
2060
} else {
2061
double_quote_seen = true;
2062
start = i;
2063
i = i + 1;
2064
}
2065
} else {
2066
i = i + 1;
2067
}
2068
}
2069
2070
}
2071
// extract the last field (csv rows have no trailing comma)
2072
d.push( s.substr(start) );
2073
return d;
2074
}
2075
2076
// s = '{"name":"rect","x":188,"y":90,"width":243,"height":233}'
2077
function json_str_to_map(s) {
2078
if (typeof(s) === 'undefined' || s.length === 0 ) {
2079
return {};
2080
}
2081
2082
return 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
2087
function map_to_json(m) {
2088
var s = [];
2089
for ( var key in m ) {
2090
var v = m[key];
2091
var si = JSON.stringify(key);
2092
si += VIA_CSV_KEYVAL_SEP;
2093
si += JSON.stringify(v);
2094
s.push( si );
2095
}
2096
return '{' + s.join(VIA_CSV_SEP) + '}';
2097
}
2098
2099
function escape_for_csv(s) {
2100
return s.replace(/["]/g, '""');
2101
}
2102
2103
function unescape_from_csv(s) {
2104
return s.replace(/""/g, '"');
2105
}
2106
2107
function remove_prefix_suffix_quotes(s) {
2108
if ( s.charAt(0) === '"' && s.charAt(s.length-1) === '"' ) {
2109
return s.substr(1, s.length-2);
2110
} else {
2111
return s;
2112
}
2113
}
2114
2115
function clone_image_region(r0) {
2116
var r1 = new file_region();
2117
2118
// copy shape attributes
2119
for ( var key in r0.shape_attributes ) {
2120
r1.shape_attributes[key] = clone_value(r0.shape_attributes[key]);
2121
}
2122
2123
// copy region attributes
2124
for ( var key in r0.region_attributes ) {
2125
r1.region_attributes[key] = clone_value(r0.region_attributes[key]);
2126
}
2127
return r1;
2128
}
2129
2130
function clone_value(value) {
2131
if ( typeof(value) === 'object' ) {
2132
if ( Array.isArray(value) ) {
2133
return value.slice(0);
2134
} else {
2135
var copy = {};
2136
for ( var p in value ) {
2137
if ( value.hasOwnProperty(p) ) {
2138
copy[p] = clone_value(value[p]);
2139
}
2140
}
2141
return copy;
2142
}
2143
}
2144
return value;
2145
}
2146
2147
function _via_get_image_id(filename, size) {
2148
if ( typeof(size) === 'undefined' ) {
2149
return filename;
2150
} else {
2151
return filename + size;
2152
}
2153
}
2154
2155
function load_text_file(text_file, callback_function) {
2156
if (text_file) {
2157
var text_reader = new FileReader();
2158
text_reader.addEventListener( 'progress', function(e) {
2159
show_message('Loading data from file : ' + text_file.name + ' ... ');
2160
}, false);
2161
2162
text_reader.addEventListener( 'error', function() {
2163
show_message('Error loading data text file : ' + text_file.name + ' !');
2164
callback_function('');
2165
}, false);
2166
2167
text_reader.addEventListener( 'load', function() {
2168
callback_function(text_reader.result);
2169
}, false);
2170
text_reader.readAsText(text_file, 'utf-8');
2171
}
2172
}
2173
2174
function import_files_url_from_csv(data) {
2175
return new Promise( function(ok_callback, err_callback) {
2176
if ( data === '' || typeof(data) === 'undefined') {
2177
err_callback();
2178
}
2179
2180
var malformed_url_count = 0;
2181
var url_added_count = 0;
2182
2183
var line_split_regex = new RegExp('\n|\r|\r\n', 'g');
2184
var csvdata = data.split(line_split_regex);
2185
2186
var percent_completed = 0;
2187
var n = csvdata.length;
2188
var i;
2189
var img_id;
2190
var first_img_id = '';
2191
for ( i=0; i < n; ++i ) {
2192
// ignore blank lines
2193
if (csvdata[i].charAt(0) === '\n' || csvdata[i].charAt(0) === '') {
2194
malformed_url_count += 1;
2195
continue;
2196
} else {
2197
img_id = project_file_add_url(csvdata[i]);
2198
if ( first_img_id === '' ) {
2199
first_img_id = img_id;
2200
}
2201
url_added_count += 1;
2202
}
2203
}
2204
show_message('Added ' + url_added_count + ' files to project');
2205
if ( url_added_count ) {
2206
var first_img_index = _via_image_id_list.indexOf(first_img_id);
2207
_via_show_img(first_img_index);
2208
update_img_fn_list();
2209
}
2210
});
2211
}
2212
2213
//
2214
// Data Exporter
2215
//
2216
function pack_via_metadata(return_type) {
2217
return new Promise( function(ok_callback, err_callback) {
2218
if( return_type === 'csv' ) {
2219
var csvdata = [];
2220
var csvheader = 'filename,file_size,file_attributes,region_count,region_id,region_shape_attributes,region_attributes';
2221
csvdata.push(csvheader);
2222
2223
for ( var image_id in _via_img_metadata ) {
2224
var fattr = map_to_json( _via_img_metadata[image_id].file_attributes );
2225
fattr = escape_for_csv( fattr );
2226
2227
var prefix = '\n' + _via_img_metadata[image_id].filename;
2228
prefix += ',' + _via_img_metadata[image_id].size;
2229
prefix += ',"' + fattr + '"';
2230
2231
var r = _via_img_metadata[image_id].regions;
2232
2233
if ( r.length !==0 ) {
2234
for ( var i = 0; i < r.length; ++i ) {
2235
var csvline = [];
2236
csvline.push(prefix);
2237
csvline.push(r.length);
2238
csvline.push(i);
2239
2240
var sattr = map_to_json( r[i].shape_attributes );
2241
sattr = '"' + escape_for_csv( sattr ) + '"';
2242
csvline.push(sattr);
2243
2244
var rattr = map_to_json( r[i].region_attributes );
2245
rattr = '"' + escape_for_csv( rattr ) + '"';
2246
csvline.push(rattr);
2247
csvdata.push( csvline.join(VIA_CSV_SEP) );
2248
}
2249
} else {
2250
// @todo: reconsider this practice of adding an empty entry
2251
csvdata.push(prefix + ',0,0,"{}","{}"');
2252
}
2253
}
2254
ok_callback(csvdata);
2255
}
2256
2257
// see http://cocodataset.org/#format-data
2258
if( return_type === 'coco' ) {
2259
img_stat_set_all().then( function(ok) {
2260
var coco = export_project_to_coco_format();
2261
ok_callback( [ coco ] );
2262
}.bind(this), function(err) {
2263
err_callback(err);
2264
}.bind(this));
2265
} else {
2266
// default format is JSON
2267
ok_callback( [ JSON.stringify(_via_img_metadata) ] );
2268
}
2269
}.bind(this));
2270
}
2271
2272
function export_project_to_coco_format() {
2273
var coco = { 'info':{}, 'images':[], 'annotations':[], 'licenses':[], 'categories':[] };
2274
coco['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
};
2281
coco['licenses'] = [ {'id':0, 'name':'Unknown License', 'url':''} ]; // indicates that license is unknown
2282
2283
var 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
2290
var assign_unique_id = false;
2291
for(var img_id in _via_img_metadata) {
2292
if(Number.isNaN(parseInt(img_id))) {
2293
assign_unique_id = true; // since COCO only supports image_id of type integer, we cannot reuse the VIA's image-id
2294
break;
2295
}
2296
}
2297
if(assign_unique_id) {
2298
// check if all the options have unique id
2299
var attribute_option_id_list = [];
2300
for(var attr_name in _via_attributes) {
2301
if( !VIA_COCO_EXPORT_ATTRIBUTE_TYPE.includes(_via_attributes[attr_name]['type']) ) {
2302
continue; // skip this attribute as it will not be included in COCO export
2303
}
2304
2305
for(var attr_option_id in _via_attributes[attr_name]['options']) {
2306
if(attribute_option_id_list.includes(attr_option_id) ||
2307
Number.isNaN(parseInt(attr_option_id)) ) {
2308
assign_unique_id = true;
2309
break;
2310
} else {
2311
attribute_option_id_list.push(assign_unique_id);
2312
}
2313
}
2314
}
2315
}
2316
2317
// add categories
2318
var attr_option_id_to_category_id = {};
2319
var unique_category_id = 1;
2320
for(var attr_name in _via_attributes['region']) {
2321
if( VIA_COCO_EXPORT_ATTRIBUTE_TYPE.includes(_via_attributes['region'][attr_name]['type']) ) {
2322
for(var attr_option_id in _via_attributes['region'][attr_name]['options']) {
2323
var category_id;
2324
if(assign_unique_id) {
2325
category_id = unique_category_id;
2326
unique_category_id = unique_category_id + 1;
2327
} else {
2328
category_id = parseInt(attr_option_id);
2329
}
2330
coco['categories'].push({
2331
'supercategory':attr_name,
2332
'id':category_id,
2333
'name':_via_attributes['region'][attr_name]['options'][attr_option_id]
2334
});
2335
attr_option_id_to_category_id[attr_option_id] = category_id;
2336
}
2337
}
2338
}
2339
2340
// add files and all their associated annotations
2341
var annotation_id = 1;
2342
var unique_img_id = 1;
2343
for( var img_index in _via_image_id_list ) {
2344
var img_id = _via_image_id_list[img_index];
2345
var file_src = _via_settings['core']['default_filepath'] + _via_img_metadata[img_id].filename;
2346
if ( _via_img_fileref[img_id] instanceof File ) {
2347
file_src = _via_img_fileref[img_id].filename;
2348
}
2349
2350
var coco_img_id;
2351
if(assign_unique_id) {
2352
coco_img_id = unique_img_id;
2353
unique_img_id = unique_img_id + 1;
2354
} else {
2355
coco_img_id = parseInt(img_id);
2356
}
2357
2358
coco['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
2370
for( var rindex in _via_img_metadata[img_id].regions ) {
2371
var region = _via_img_metadata[img_id].regions[rindex];
2372
if( !VIA_COCO_EXPORT_RSHAPE.includes(region.shape_attributes['name']) ) {
2373
skipped_annotation_count = skipped_annotation_count + 1;
2374
continue; // skip this region as COCO does not allow it
2375
}
2376
2377
var coco_annotation = via_region_shape_to_coco_annotation(region.shape_attributes);
2378
coco_annotation['id'] = annotation_id;
2379
coco_annotation['image_id'] = coco_img_id;
2380
2381
var region_aid_list = Object.keys(region['region_attributes']);
2382
for(var region_attribute_id in region['region_attributes']) {
2383
var region_attribute_value = region['region_attributes'][region_attribute_id];
2384
if(attr_option_id_to_category_id.hasOwnProperty(region_attribute_value)) {
2385
coco_annotation['category_id'] = attr_option_id_to_category_id[region_attribute_value];
2386
coco['annotations'].push(coco_annotation);
2387
annotation_id = annotation_id + 1;
2388
} else {
2389
skipped_annotation_count = skipped_annotation_count + 1;
2390
continue; // skip attribute value not supported by COCO format
2391
}
2392
}
2393
}
2394
}
2395
2396
show_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));
2397
return [ JSON.stringify(coco) ];
2398
}
2399
2400
function via_region_shape_to_coco_annotation(shape_attributes) {
2401
var annotation = { 'segmentation':[[]], 'area':[], 'bbox':[], 'iscrowd':0 };
2402
2403
switch(shape_attributes['name']) {
2404
case 'rect':
2405
var x0 = shape_attributes['x'];
2406
var y0 = shape_attributes['y'];
2407
var w = parseInt(shape_attributes['width']);
2408
var h = parseInt(shape_attributes['height']);
2409
var x1 = x0 + w;
2410
var y1 = y0 + h;
2411
annotation['segmentation'][0] = [x0, y0, x1, y0, x1, y1, x0, y1];
2412
annotation['area'] = w * h ;
2413
2414
annotation['bbox'] = [x0, y0, w, h];
2415
break;
2416
2417
case 'point':
2418
var cx = shape_attributes['cx'];
2419
var cy = shape_attributes['cy'];
2420
// 2 is for visibility - currently set to always inside segmentation.
2421
// see Keypoint Detection: http://cocodataset.org/#format-data
2422
annotation['keypoints'] = [cx, cy, 2];
2423
annotation['num_keypoints'] = 1;
2424
break;
2425
2426
case 'circle':
2427
var a,b;
2428
a = shape_attributes['r'];
2429
b = shape_attributes['r'];
2430
var theta_to_radian = Math.PI/180;
2431
2432
for ( var theta = 0; theta < 360; theta = theta + VIA_POLYGON_SEGMENT_SUBTENDED_ANGLE ) {
2433
var theta_radian = theta * theta_to_radian;
2434
var x = shape_attributes['cx'] + a * Math.cos(theta_radian);
2435
var y = shape_attributes['cy'] + b * Math.sin(theta_radian);
2436
annotation['segmentation'][0].push( fixfloat(x), fixfloat(y) );
2437
}
2438
annotation['bbox'] = polygon_to_bbox(annotation['segmentation'][0]);
2439
annotation['area'] = annotation['bbox'][2] * annotation['bbox'][3];
2440
break;
2441
2442
case 'ellipse':
2443
var a,b;
2444
a = shape_attributes['rx'];
2445
b = shape_attributes['ry'];
2446
var rotation = 0;
2447
// older version of VIA2 did not support rotated ellipse and hence 'theta' attribute may not be available
2448
if( shape_attributes.hasOwnProperty('theta') ) {
2449
rotation = shape_attributes['theta'];
2450
}
2451
2452
var theta_to_radian = Math.PI/180;
2453
2454
for ( var theta = 0; theta < 360; theta = theta + VIA_POLYGON_SEGMENT_SUBTENDED_ANGLE ) {
2455
var theta_radian = theta * theta_to_radian;
2456
var x = shape_attributes['cx'] +
2457
( a * Math.cos(theta_radian) * Math.cos(rotation) ) -
2458
( b * Math.sin(theta_radian) * Math.sin(rotation) );
2459
var y = shape_attributes['cy'] +
2460
( a * Math.cos(theta_radian) * Math.sin(rotation) ) +
2461
( b * Math.sin(theta_radian) * Math.cos(rotation) );
2462
annotation['segmentation'][0].push( fixfloat(x), fixfloat(y) );
2463
}
2464
annotation['bbox'] = polygon_to_bbox(annotation['segmentation'][0]);
2465
annotation['area'] = annotation['bbox'][2] * annotation['bbox'][3];
2466
break;
2467
2468
case 'polygon':
2469
annotation['segmentation'][0] = [];
2470
var x0 = +Infinity;
2471
var y0 = +Infinity;
2472
var x1 = -Infinity;
2473
var y1 = -Infinity;
2474
for ( var i in shape_attributes['all_points_x'] ) {
2475
annotation['segmentation'][0].push( shape_attributes['all_points_x'][i] );
2476
annotation['segmentation'][0].push( shape_attributes['all_points_y'][i] );
2477
if ( shape_attributes['all_points_x'][i] < x0 ) {
2478
x0 = shape_attributes['all_points_x'][i];
2479
}
2480
if ( shape_attributes['all_points_y'][i] < y0 ) {
2481
y0 = shape_attributes['all_points_y'][i];
2482
}
2483
if ( shape_attributes['all_points_x'][i] > x1 ) {
2484
x1 = shape_attributes['all_points_x'][i];
2485
}
2486
if ( shape_attributes['all_points_y'][i] > y1 ) {
2487
y1 = shape_attributes['all_points_y'][i];
2488
}
2489
}
2490
var w = x1 - x0;
2491
var h = y1 - y0;
2492
annotation['bbox'] = [x0, y0, w, h];
2493
annotation['area'] = w * h; // approximate area
2494
}
2495
return annotation;
2496
}
2497
2498
function save_data_to_local_file(data, filename) {
2499
var a = document.createElement('a');
2500
a.href = URL.createObjectURL(data);
2501
a.download = filename;
2502
2503
// simulate a mouse click event
2504
var event = new MouseEvent('click', {
2505
view: window,
2506
bubbles: true,
2507
cancelable: true
2508
});
2509
a.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
2520
function init_message_panel() {
2521
var p = document.getElementById('message_panel');
2522
p.addEventListener('mousedown', function() {
2523
this.style.display = 'none';
2524
}, false);
2525
p.addEventListener('mouseover', function() {
2526
clearTimeout(_via_message_clear_timer); // stop any previous timeouts
2527
}, false);
2528
}
2529
2530
function toggle_message_visibility() {
2531
if(_via_is_message_visible) {
2532
show_message('Disabled status messages');
2533
_via_is_message_visible = false;
2534
} else {
2535
_via_is_message_visible = true;
2536
show_message('Status messages are now visible');
2537
}
2538
}
2539
2540
function show_message(msg, t) {
2541
if ( _via_message_clear_timer ) {
2542
clearTimeout(_via_message_clear_timer); // stop any previous timeouts
2543
}
2544
if ( !_via_is_message_visible ) {
2545
return;
2546
}
2547
2548
var timeout = t;
2549
if ( typeof t === 'undefined' ) {
2550
timeout = VIA_THEME_MESSAGE_TIMEOUT_MS;
2551
}
2552
document.getElementById('message_panel_content').innerHTML = msg;
2553
document.getElementById('message_panel').style.display = 'block';
2554
2555
_via_message_clear_timer = setTimeout( function() {
2556
document.getElementById('message_panel').style.display = 'none';
2557
}, timeout);
2558
}
2559
2560
function _via_regions_group_color_init() {
2561
_via_canvas_regions_group_color = {};
2562
var aid = _via_settings.ui.image.region_color;
2563
if ( aid !== '__via_default_region_color__' ) {
2564
var avalue;
2565
for ( var i = 0; i < _via_img_metadata[_via_image_id].regions.length; ++i ) {
2566
avalue = _via_img_metadata[_via_image_id].regions[i].region_attributes[aid];
2567
_via_canvas_regions_group_color[avalue] = 1;
2568
}
2569
var color_index = 0;
2570
for ( 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 ];
2572
color_index = color_index + 1;
2573
}
2574
}
2575
}
2576
2577
// transform regions in image space to canvas space
2578
function _via_load_canvas_regions() {
2579
_via_regions_group_color_init();
2580
2581
// load all existing annotations into _via_canvas_regions
2582
var regions = _via_img_metadata[_via_image_id].regions;
2583
_via_canvas_regions = [];
2584
for ( var i = 0; i < regions.length; ++i ) {
2585
var region_i = new file_region();
2586
for ( var key in regions[i].shape_attributes ) {
2587
region_i.shape_attributes[key] = regions[i].shape_attributes[key];
2588
}
2589
_via_canvas_regions.push(region_i);
2590
2591
switch(_via_canvas_regions[i].shape_attributes['name']) {
2592
case VIA_REGION_SHAPE.RECT:
2593
var x = regions[i].shape_attributes['x'] / _via_canvas_scale;
2594
var y = regions[i].shape_attributes['y'] / _via_canvas_scale;
2595
var width = regions[i].shape_attributes['width'] / _via_canvas_scale;
2596
var 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);
2602
break;
2603
2604
case VIA_REGION_SHAPE.CIRCLE:
2605
var cx = regions[i].shape_attributes['cx'] / _via_canvas_scale;
2606
var cy = regions[i].shape_attributes['cy'] / _via_canvas_scale;
2607
var 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);
2611
break;
2612
2613
case VIA_REGION_SHAPE.ELLIPSE:
2614
var cx = regions[i].shape_attributes['cx'] / _via_canvas_scale;
2615
var cy = regions[i].shape_attributes['cy'] / _via_canvas_scale;
2616
var rx = regions[i].shape_attributes['rx'] / _via_canvas_scale;
2617
var ry = regions[i].shape_attributes['ry'] / _via_canvas_scale;
2618
// rotation in radians
2619
var 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;
2625
break;
2626
2627
case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
2628
case VIA_REGION_SHAPE.POLYGON:
2629
var all_points_x = regions[i].shape_attributes['all_points_x'].slice(0);
2630
var all_points_y = regions[i].shape_attributes['all_points_y'].slice(0);
2631
for (var j=0; j<all_points_x.length; ++j) {
2632
all_points_x[j] = Math.round(all_points_x[j] / _via_canvas_scale);
2633
all_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;
2637
break;
2638
2639
case VIA_REGION_SHAPE.POINT:
2640
var cx = regions[i].shape_attributes['cx'] / _via_canvas_scale;
2641
var 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);
2645
break;
2646
}
2647
}
2648
}
2649
2650
// updates currently selected region shape
2651
function select_region_shape(sel_shape_name) {
2652
for ( var shape_name in VIA_REGION_SHAPE ) {
2653
var ui_element = document.getElementById('region_shape_' + VIA_REGION_SHAPE[shape_name]);
2654
ui_element.classList.remove('selected');
2655
}
2656
2657
_via_current_shape = sel_shape_name;
2658
var ui_element = document.getElementById('region_shape_' + _via_current_shape);
2659
ui_element.classList.add('selected');
2660
2661
switch(_via_current_shape) {
2662
case VIA_REGION_SHAPE.RECT: // Fall-through
2663
case VIA_REGION_SHAPE.CIRCLE: // Fall-through
2664
case VIA_REGION_SHAPE.ELLIPSE:
2665
show_message('Press single click and drag mouse to draw ' +
2666
_via_current_shape + ' region');
2667
break;
2668
2669
case VIA_REGION_SHAPE.POLYLINE:
2670
case VIA_REGION_SHAPE.POLYGON:
2671
_via_is_user_drawing_polygon = false;
2672
_via_current_polygon_region_id = -1;
2673
2674
show_message('[Single Click] to define polygon/polyline vertices, ' +
2675
'[Backspace] to delete last vertex, [Enter] to finish, [Esc] to cancel drawing.' );
2676
break;
2677
2678
case VIA_REGION_SHAPE.POINT:
2679
show_message('Press single click to define points (or landmarks)');
2680
break;
2681
2682
default:
2683
show_message('Unknown shape selected!');
2684
break;
2685
}
2686
}
2687
2688
function set_all_canvas_size(w, h) {
2689
_via_reg_canvas.height = h;
2690
_via_reg_canvas.width = w;
2691
2692
image_panel.style.height = h + 'px';
2693
image_panel.style.width = w + 'px';
2694
}
2695
2696
function set_all_canvas_scale(s) {
2697
_via_reg_ctx.scale(s, s);
2698
}
2699
2700
function show_all_canvas() {
2701
image_panel.style.display = 'inline-block';
2702
}
2703
2704
function hide_all_canvas() {
2705
image_panel.style.display = 'none';
2706
}
2707
2708
function jump_to_image(image_index) {
2709
if ( _via_img_count <= 0 ) {
2710
return;
2711
}
2712
2713
switch(_via_display_area_content_name) {
2714
case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:
2715
if ( image_index >= 0 && image_index < _via_img_count) {
2716
// @todo: jump to image grid page view with the given first image index
2717
show_single_image_view();
2718
_via_show_img(image_index);
2719
}
2720
break;
2721
default:
2722
if ( image_index >= 0 && image_index < _via_img_count) {
2723
_via_show_img(image_index);
2724
}
2725
break;
2726
}
2727
}
2728
2729
function count_missing_region_attr(img_id) {
2730
var miss_region_attr_count = 0;
2731
var attr_count = Object.keys(_via_region_attributes).length;
2732
for( var i=0; i < _via_img_metadata[img_id].regions.length; ++i ) {
2733
var set_attr_count = Object.keys(_via_img_metadata[img_id].regions[i].region_attributes).length;
2734
miss_region_attr_count += ( attr_count - set_attr_count );
2735
}
2736
return miss_region_attr_count;
2737
}
2738
2739
function count_missing_file_attr(img_id) {
2740
return Object.keys(_via_file_attributes).length - Object.keys(_via_img_metadata[img_id].file_attributes).length;
2741
}
2742
2743
function toggle_all_regions_selection(is_selected) {
2744
var n = _via_img_metadata[_via_image_id].regions.length;
2745
var i;
2746
_via_region_selected_flag = [];
2747
for ( i = 0; i < n; ++i) {
2748
_via_region_selected_flag[i] = is_selected;
2749
}
2750
_via_is_all_region_selected = is_selected;
2751
annotation_editor_hide();
2752
if ( _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS ) {
2753
annotation_editor_clear_row_highlight();
2754
}
2755
}
2756
2757
function select_only_region(region_id) {
2758
toggle_all_regions_selection(false);
2759
set_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
2765
function set_region_select_state(region_id, is_selected) {
2766
_via_region_selected_flag[region_id] = is_selected;
2767
}
2768
2769
function show_annotation_data() {
2770
pack_via_metadata('csv').then( function(data) {
2771
var hstr = '<pre>' + data.join('') + '</pre>';
2772
var window_features = 'toolbar=no,menubar=no,location=no,resizable=yes,scrollbars=yes,status=no';
2773
window_features += ',width=800,height=600';
2774
var annotation_data_window = window.open('', 'Annotations (preview) ', window_features);
2775
annotation_data_window.document.body.innerHTML = hstr;
2776
}.bind(this), function(err) {
2777
show_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
2786
function _via_reg_canvas_dblclick_handler(e) {
2787
e.stopPropagation();
2788
// @todo: use double click in future
2789
}
2790
2791
// user clicks on the canvas
2792
function _via_reg_canvas_mousedown_handler(e) {
2793
e.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);
2796
var region_id = is_inside_region(_via_click_x0, _via_click_y0);
2797
2798
if ( _via_is_region_selected ) {
2799
// check if user clicked on the region boundary
2800
if ( _via_region_edge[1] > 0 ) {
2801
if ( !_via_is_user_resizing_region ) {
2802
if ( _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 {
2809
var yes = is_inside_this_region(_via_click_x0,
2810
_via_click_y0,
2811
_via_user_sel_region_id);
2812
if (yes) {
2813
if( !_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
}
2819
if ( 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;
2825
toggle_all_regions_selection(false);
2826
}
2827
}
2828
} else {
2829
if ( region_id === -1 ) {
2830
// mousedown outside a region
2831
if (_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
2848
function _via_reg_canvas_mouseup_handler(e) {
2849
e.stopPropagation();
2850
_via_click_x1 = e.offsetX; _via_click_y1 = e.offsetY;
2851
2852
var click_dx = Math.abs(_via_click_x1 - _via_click_x0);
2853
var click_dy = Math.abs(_via_click_y1 - _via_click_y0);
2854
2855
// indicates that user has finished moving a region
2856
if ( _via_is_user_moving_region ) {
2857
_via_is_user_moving_region = false;
2858
_via_reg_canvas.style.cursor = "default";
2859
2860
var move_x = Math.round(_via_click_x1 - _via_region_click_x);
2861
var move_y = Math.round(_via_click_y1 - _via_region_click_y);
2862
2863
if (Math.abs(move_x) > VIA_MOUSE_CLICK_TOL ||
2864
Math.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
2876
var nested_region_id = is_inside_region(_via_click_x0, _via_click_y0, true);
2877
if (nested_region_id >= 0 &&
2878
nested_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
2884
if ( !e.shiftKey ) {
2885
toggle_all_regions_selection(false);
2886
}
2887
set_region_select_state(nested_region_id, true);
2888
annotation_editor_show();
2889
} else {
2890
// user clicking inside an already selected region
2891
// indicates that the user intends to draw a nested region
2892
toggle_all_regions_selection(false);
2893
_via_is_region_selected = false;
2894
2895
switch (_via_current_shape) {
2896
case VIA_REGION_SHAPE.POLYLINE: // handled by case for POLYGON
2897
case 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
2902
var canvas_polygon_region = new file_region();
2903
canvas_polygon_region.shape_attributes['name'] = _via_current_shape;
2904
canvas_polygon_region.shape_attributes['all_points_x'] = [Math.round(_via_click_x0)];
2905
canvas_polygon_region.shape_attributes['all_points_y'] = [Math.round(_via_click_y0)];
2906
var new_length = _via_canvas_regions.push(canvas_polygon_region);
2907
_via_current_polygon_region_id = new_length - 1;
2908
break;
2909
2910
case VIA_REGION_SHAPE.POINT:
2911
// user has marked a landmark point
2912
var point_region = new file_region();
2913
point_region.shape_attributes['name'] = VIA_REGION_SHAPE.POINT;
2914
point_region.shape_attributes['cx'] = Math.round(_via_click_x0 * _via_canvas_scale);
2915
point_region.shape_attributes['cy'] = Math.round(_via_click_y0 * _via_canvas_scale);
2916
_via_img_metadata[_via_image_id].regions.push(point_region);
2917
2918
var canvas_point_region = new file_region();
2919
canvas_point_region.shape_attributes['name'] = VIA_REGION_SHAPE.POINT;
2920
canvas_point_region.shape_attributes['cx'] = Math.round(_via_click_x0);
2921
canvas_point_region.shape_attributes['cy'] = Math.round(_via_click_y0);
2922
_via_canvas_regions.push(canvas_point_region);
2923
break;
2924
}
2925
annotation_editor_update_content();
2926
}
2927
}
2928
_via_redraw_reg_canvas();
2929
_via_reg_canvas.focus();
2930
return;
2931
}
2932
2933
// indicates that user has finished resizing a region
2934
if ( _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
2940
var region_id = _via_region_edge[0];
2941
var image_attr = _via_img_metadata[_via_image_id].regions[region_id].shape_attributes;
2942
var canvas_attr = _via_canvas_regions[region_id].shape_attributes;
2943
2944
switch (canvas_attr['name']) {
2945
case VIA_REGION_SHAPE.RECT:
2946
var d = [canvas_attr['x'], canvas_attr['y'], 0, 0];
2947
d[2] = d[0] + canvas_attr['width'];
2948
d[3] = d[1] + canvas_attr['height'];
2949
2950
var mx = _via_current_x;
2951
var my = _via_current_y;
2952
var preserve_aspect_ratio = false;
2953
2954
// constrain (mx,my) to lie on a line connecting a diagonal of rectangle
2955
if ( _via_is_ctrl_pressed ) {
2956
preserve_aspect_ratio = true;
2957
}
2958
2959
rect_update_corner(_via_region_edge[1], d, mx, my, preserve_aspect_ratio);
2960
rect_standardize_coordinates(d);
2961
2962
var w = Math.abs(d[2] - d[0]);
2963
var h = Math.abs(d[3] - d[1]);
2964
2965
image_attr['x'] = Math.round(d[0] * _via_canvas_scale);
2966
image_attr['y'] = Math.round(d[1] * _via_canvas_scale);
2967
image_attr['width'] = Math.round(w * _via_canvas_scale);
2968
image_attr['height'] = Math.round(h * _via_canvas_scale);
2969
2970
canvas_attr['x'] = Math.round( image_attr['x'] / _via_canvas_scale);
2971
canvas_attr['y'] = Math.round( image_attr['y'] / _via_canvas_scale);
2972
canvas_attr['width'] = Math.round( image_attr['width'] / _via_canvas_scale);
2973
canvas_attr['height'] = Math.round( image_attr['height'] / _via_canvas_scale);
2974
break;
2975
2976
case VIA_REGION_SHAPE.CIRCLE:
2977
var dx = Math.abs(canvas_attr['cx'] - _via_current_x);
2978
var dy = Math.abs(canvas_attr['cy'] - _via_current_y);
2979
var new_r = Math.sqrt( dx*dx + dy*dy );
2980
2981
image_attr['r'] = fixfloat(new_r * _via_canvas_scale);
2982
canvas_attr['r'] = Math.round( image_attr['r'] / _via_canvas_scale);
2983
break;
2984
2985
case VIA_REGION_SHAPE.ELLIPSE:
2986
var new_rx = canvas_attr['rx'];
2987
var new_ry = canvas_attr['ry'];
2988
var new_theta = canvas_attr['theta'];
2989
var dx = Math.abs(canvas_attr['cx'] - _via_current_x);
2990
var dy = Math.abs(canvas_attr['cy'] - _via_current_y);
2991
2992
switch(_via_region_edge[1]) {
2993
case 5:
2994
new_ry = Math.sqrt(dx*dx + dy*dy);
2995
new_theta = Math.atan2(- (_via_current_x - canvas_attr['cx']), (_via_current_y - canvas_attr['cy']));
2996
break;
2997
2998
case 6:
2999
new_rx = Math.sqrt(dx*dx + dy*dy);
3000
new_theta = Math.atan2((_via_current_y - canvas_attr['cy']), (_via_current_x - canvas_attr['cx']));
3001
break;
3002
3003
default:
3004
new_rx = dx;
3005
new_ry = dy;
3006
new_theta = 0;
3007
break;
3008
}
3009
3010
image_attr['rx'] = fixfloat(new_rx * _via_canvas_scale);
3011
image_attr['ry'] = fixfloat(new_ry * _via_canvas_scale);
3012
image_attr['theta'] = fixfloat(new_theta);
3013
3014
canvas_attr['rx'] = Math.round(image_attr['rx'] / _via_canvas_scale);
3015
canvas_attr['ry'] = Math.round(image_attr['ry'] / _via_canvas_scale);
3016
canvas_attr['theta'] = fixfloat(new_theta);
3017
break;
3018
3019
case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
3020
case VIA_REGION_SHAPE.POLYGON:
3021
var moved_vertex_id = _via_region_edge[1] - VIA_POLYGON_RESIZE_VERTEX_OFFSET;
3022
3023
if ( e.ctrlKey || e.metaKey ) {
3024
// if on vertex, delete it
3025
// if on edge, add a new vertex
3026
var r = _via_canvas_regions[_via_user_sel_region_id].shape_attributes;
3027
var shape = r.name;
3028
var is_on_vertex = is_on_polygon_vertex(r['all_points_x'], r['all_points_y'], _via_current_x, _via_current_y);
3029
3030
if ( is_on_vertex === _via_region_edge[1] ) {
3031
// click on vertex, hence delete vertex
3032
if ( _via_polygon_del_vertex(region_id, moved_vertex_id) ) {
3033
show_message('Deleted vertex ' + moved_vertex_id + ' from region');
3034
}
3035
} else {
3036
var is_on_edge = is_on_polygon_edge(r['all_points_x'], r['all_points_y'], _via_current_x, _via_current_y);
3037
if ( is_on_edge === _via_region_edge[1] ) {
3038
// click on edge, hence add new vertex
3039
var vertex_index = is_on_edge - VIA_POLYGON_RESIZE_VERTEX_OFFSET;
3040
var canvas_x0 = Math.round(_via_click_x1);
3041
var canvas_y0 = Math.round(_via_click_y1);
3042
var img_x0 = Math.round( canvas_x0 * _via_canvas_scale );
3043
var img_y0 = Math.round( canvas_y0 * _via_canvas_scale );
3044
canvas_x0 = Math.round( img_x0 / _via_canvas_scale );
3045
canvas_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
3052
show_message('Added 1 new vertex to ' + shape + ' region');
3053
}
3054
}
3055
} else {
3056
// update coordinate of vertex
3057
var imx = Math.round(_via_current_x * _via_canvas_scale);
3058
var imy = Math.round(_via_current_y * _via_canvas_scale);
3059
image_attr['all_points_x'][moved_vertex_id] = imx;
3060
image_attr['all_points_y'][moved_vertex_id] = imy;
3061
canvas_attr['all_points_x'][moved_vertex_id] = Math.round( imx / _via_canvas_scale );
3062
canvas_attr['all_points_y'][moved_vertex_id] = Math.round( imy / _via_canvas_scale );
3063
}
3064
break;
3065
} // end of switch()
3066
_via_redraw_reg_canvas();
3067
_via_reg_canvas.focus();
3068
return;
3069
}
3070
3071
// denotes a single click (= mouse down + mouse up)
3072
if ( click_dx < VIA_MOUSE_CLICK_TOL ||
3073
click_dy < VIA_MOUSE_CLICK_TOL ) {
3074
// if user is already drawing polygon, then each click adds a new point
3075
if ( _via_is_user_drawing_polygon ) {
3076
var canvas_x0 = Math.round(_via_click_x1);
3077
var canvas_y0 = Math.round(_via_click_y1);
3078
var n = _via_canvas_regions[_via_current_polygon_region_id].shape_attributes['all_points_x'].length;
3079
var last_x0 = _via_canvas_regions[_via_current_polygon_region_id].shape_attributes['all_points_x'][n-1];
3080
var 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
3082
if ( 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 {
3088
var region_id = is_inside_region(_via_click_x0, _via_click_y0);
3089
if ( 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
3097
if ( !e.shiftKey ) {
3098
annotation_editor_clear_row_highlight();
3099
toggle_all_regions_selection(false);
3100
}
3101
set_region_select_state(region_id, true);
3102
3103
// show annotation editor only when a single region is selected
3104
if ( !e.shiftKey ) {
3105
annotation_editor_show();
3106
} else {
3107
annotation_editor_hide();
3108
}
3109
3110
// show the region info
3111
if (_via_is_region_info_visible) {
3112
var canvas_attr = _via_canvas_regions[region_id].shape_attributes;
3113
3114
switch (canvas_attr['name']) {
3115
case VIA_REGION_SHAPE.RECT:
3116
break;
3117
3118
case VIA_REGION_SHAPE.CIRCLE:
3119
var rf = document.getElementById('region_info');
3120
var attr = _via_canvas_regions[_via_user_sel_region_id].shape_attributes;
3121
rf.innerHTML += ',' + ' Radius:' + attr['r'];
3122
break;
3123
3124
case VIA_REGION_SHAPE.ELLIPSE:
3125
var rf = document.getElementById('region_info');
3126
var attr = _via_canvas_regions[_via_user_sel_region_id].shape_attributes;
3127
rf.innerHTML += ',' + ' X-radius:' + attr['rx'] + ',' + ' Y-radius:' + attr['ry'];
3128
break;
3129
3130
case VIA_REGION_SHAPE.POLYLINE:
3131
case VIA_REGION_SHAPE.POLYGON:
3132
break;
3133
}
3134
}
3135
3136
show_message('Region selected. If you intended to draw a region, click again inside the selected region to start drawing a region.')
3137
} else {
3138
if ( _via_is_user_drawing_region ) {
3139
// clear all region selection
3140
_via_is_user_drawing_region = false;
3141
_via_is_region_selected = false;
3142
toggle_all_regions_selection(false);
3143
annotation_editor_hide();
3144
} else {
3145
switch (_via_current_shape) {
3146
case VIA_REGION_SHAPE.POLYLINE: // handled by case for POLYGON
3147
case 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
3152
var canvas_polygon_region = new file_region();
3153
canvas_polygon_region.shape_attributes['name'] = _via_current_shape;
3154
canvas_polygon_region.shape_attributes['all_points_x'] = [ Math.round(_via_click_x0) ];
3155
canvas_polygon_region.shape_attributes['all_points_y'] = [ Math.round(_via_click_y0)] ;
3156
3157
var new_length = _via_canvas_regions.push(canvas_polygon_region);
3158
_via_current_polygon_region_id = new_length - 1;
3159
break;
3160
3161
case VIA_REGION_SHAPE.POINT:
3162
// user has marked a landmark point
3163
var point_region = new file_region();
3164
point_region.shape_attributes['name'] = VIA_REGION_SHAPE.POINT;
3165
point_region.shape_attributes['cx'] = Math.round(_via_click_x0 * _via_canvas_scale);
3166
point_region.shape_attributes['cy'] = Math.round(_via_click_y0 * _via_canvas_scale);
3167
var region_count = _via_img_metadata[_via_image_id].regions.push(point_region);
3168
var new_region_id = region_count - 1;
3169
set_region_annotations_to_default_value( new_region_id );
3170
3171
var canvas_point_region = new file_region();
3172
canvas_point_region.shape_attributes['name'] = VIA_REGION_SHAPE.POINT;
3173
canvas_point_region.shape_attributes['cx'] = Math.round(_via_click_x0);
3174
canvas_point_region.shape_attributes['cy'] = Math.round(_via_click_y0);
3175
_via_canvas_regions.push(canvas_point_region);
3176
3177
annotation_editor_update_content();
3178
break;
3179
}
3180
}
3181
}
3182
}
3183
_via_redraw_reg_canvas();
3184
_via_reg_canvas.focus();
3185
return;
3186
}
3187
3188
// indicates that user has finished drawing a new region
3189
if ( _via_is_user_drawing_region ) {
3190
_via_is_user_drawing_region = false;
3191
var region_x0 = _via_click_x0;
3192
var region_y0 = _via_click_y0;
3193
var region_x1 = _via_click_x1;
3194
var region_y1 = _via_click_y1;
3195
3196
var original_img_region = new file_region();
3197
var canvas_img_region = new file_region();
3198
var region_dx = Math.abs(region_x1 - region_x0);
3199
var region_dy = Math.abs(region_y1 - region_y0);
3200
var new_region_added = false;
3201
3202
if ( region_dx > VIA_REGION_MIN_DIM && region_dy > VIA_REGION_MIN_DIM ) { // avoid regions with 0 dim
3203
switch(_via_current_shape) {
3204
case VIA_REGION_SHAPE.RECT:
3205
// ensure that (x0,y0) is top-left and (x1,y1) is bottom-right
3206
if ( _via_click_x0 < _via_click_x1 ) {
3207
region_x0 = _via_click_x0;
3208
region_x1 = _via_click_x1;
3209
} else {
3210
region_x0 = _via_click_x1;
3211
region_x1 = _via_click_x0;
3212
}
3213
3214
if ( _via_click_y0 < _via_click_y1 ) {
3215
region_y0 = _via_click_y0;
3216
region_y1 = _via_click_y1;
3217
} else {
3218
region_y0 = _via_click_y1;
3219
region_y1 = _via_click_y0;
3220
}
3221
3222
var x = Math.round(region_x0 * _via_canvas_scale);
3223
var y = Math.round(region_y0 * _via_canvas_scale);
3224
var width = Math.round(region_dx * _via_canvas_scale);
3225
var height = Math.round(region_dy * _via_canvas_scale);
3226
original_img_region.shape_attributes['name'] = 'rect';
3227
original_img_region.shape_attributes['x'] = x;
3228
original_img_region.shape_attributes['y'] = y;
3229
original_img_region.shape_attributes['width'] = width;
3230
original_img_region.shape_attributes['height'] = height;
3231
3232
canvas_img_region.shape_attributes['name'] = 'rect';
3233
canvas_img_region.shape_attributes['x'] = Math.round( x / _via_canvas_scale );
3234
canvas_img_region.shape_attributes['y'] = Math.round( y / _via_canvas_scale );
3235
canvas_img_region.shape_attributes['width'] = Math.round( width / _via_canvas_scale );
3236
canvas_img_region.shape_attributes['height'] = Math.round( height / _via_canvas_scale );
3237
3238
new_region_added = true;
3239
break;
3240
3241
case VIA_REGION_SHAPE.CIRCLE:
3242
var cx = Math.round(region_x0 * _via_canvas_scale);
3243
var cy = Math.round(region_y0 * _via_canvas_scale);
3244
var r = Math.round( Math.sqrt(region_dx*region_dx + region_dy*region_dy) * _via_canvas_scale );
3245
3246
original_img_region.shape_attributes['name'] = 'circle';
3247
original_img_region.shape_attributes['cx'] = cx;
3248
original_img_region.shape_attributes['cy'] = cy;
3249
original_img_region.shape_attributes['r'] = r;
3250
3251
canvas_img_region.shape_attributes['name'] = 'circle';
3252
canvas_img_region.shape_attributes['cx'] = Math.round( cx / _via_canvas_scale );
3253
canvas_img_region.shape_attributes['cy'] = Math.round( cy / _via_canvas_scale );
3254
canvas_img_region.shape_attributes['r'] = Math.round( r / _via_canvas_scale );
3255
3256
new_region_added = true;
3257
break;
3258
3259
case VIA_REGION_SHAPE.ELLIPSE:
3260
var cx = Math.round(region_x0 * _via_canvas_scale);
3261
var cy = Math.round(region_y0 * _via_canvas_scale);
3262
var rx = Math.round(region_dx * _via_canvas_scale);
3263
var ry = Math.round(region_dy * _via_canvas_scale);
3264
var theta = 0;
3265
3266
original_img_region.shape_attributes['name'] = 'ellipse';
3267
original_img_region.shape_attributes['cx'] = cx;
3268
original_img_region.shape_attributes['cy'] = cy;
3269
original_img_region.shape_attributes['rx'] = rx;
3270
original_img_region.shape_attributes['ry'] = ry;
3271
original_img_region.shape_attributes['theta'] = theta;
3272
3273
canvas_img_region.shape_attributes['name'] = 'ellipse';
3274
canvas_img_region.shape_attributes['cx'] = Math.round( cx / _via_canvas_scale );
3275
canvas_img_region.shape_attributes['cy'] = Math.round( cy / _via_canvas_scale );
3276
canvas_img_region.shape_attributes['rx'] = Math.round( rx / _via_canvas_scale );
3277
canvas_img_region.shape_attributes['ry'] = Math.round( ry / _via_canvas_scale );
3278
canvas_img_region.shape_attributes['theta'] = theta;
3279
3280
new_region_added = true;
3281
break;
3282
3283
case VIA_REGION_SHAPE.POINT: // handled by case VIA_REGION_SHAPE.POLYGON
3284
case VIA_REGION_SHAPE.POLYLINE: // handled by case VIA_REGION_SHAPE.POLYGON
3285
case VIA_REGION_SHAPE.POLYGON:
3286
// handled by _via_is_user_drawing_polygon
3287
break;
3288
} // end of switch
3289
3290
if ( new_region_added ) {
3291
var n1 = _via_img_metadata[_via_image_id].regions.push(original_img_region);
3292
var n2 = _via_canvas_regions.push(canvas_img_region);
3293
3294
if ( n1 !== n2 ) {
3295
console.log('_via_img_metadata.regions[' + n1 + '] and _via_canvas_regions[' + n2 + '] count mismatch');
3296
}
3297
var new_region_id = n1 - 1;
3298
3299
set_region_annotations_to_default_value( new_region_id );
3300
select_only_region(new_region_id);
3301
if ( _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS &&
3302
_via_metadata_being_updated === 'region' ) {
3303
annotation_editor_add_row( new_region_id );
3304
annotation_editor_scroll_to_row( new_region_id );
3305
annotation_editor_clear_row_highlight();
3306
annotation_editor_highlight_row( new_region_id );
3307
}
3308
annotation_editor_show();
3309
}
3310
_via_redraw_reg_canvas();
3311
_via_reg_canvas.focus();
3312
} else {
3313
show_message('Prevented accidental addition of a very small region.');
3314
}
3315
return;
3316
}
3317
}
3318
3319
function _via_reg_canvas_mouseover_handler(e) {
3320
// change the mouse cursor icon
3321
_via_redraw_reg_canvas();
3322
_via_reg_canvas.focus();
3323
}
3324
3325
function _via_reg_canvas_mousemove_handler(e) {
3326
if ( !_via_current_image_loaded ) {
3327
return;
3328
}
3329
3330
_via_current_x = e.offsetX; _via_current_y = e.offsetY;
3331
3332
// display the cursor coordinates
3333
var rf = document.getElementById('region_info');
3334
if ( rf != null && _via_is_region_info_visible ) {
3335
var img_x = Math.round( _via_current_x * _via_canvas_scale );
3336
var img_y = Math.round( _via_current_y * _via_canvas_scale );
3337
rf.innerHTML = 'X:' + img_x + ',' + ' Y:' + img_y;
3338
}
3339
3340
if ( _via_is_region_selected ) {
3341
// display the region's info if a region is selected
3342
if ( rf != null && _via_is_region_info_visible && _via_user_sel_region_id !== -1) {
3343
var canvas_attr = _via_canvas_regions[_via_user_sel_region_id].shape_attributes;
3344
switch (canvas_attr['name']) {
3345
case VIA_REGION_SHAPE.RECT:
3346
break;
3347
3348
case VIA_REGION_SHAPE.CIRCLE:
3349
var rf = document.getElementById('region_info');
3350
var attr = _via_canvas_regions[_via_user_sel_region_id].shape_attributes;
3351
rf.innerHTML += ',' + ' Radius:' + attr['r'];
3352
break;
3353
3354
case VIA_REGION_SHAPE.ELLIPSE:
3355
var rf = document.getElementById('region_info');
3356
var attr = _via_canvas_regions[_via_user_sel_region_id].shape_attributes;
3357
rf.innerHTML += ',' + ' X-radius:' + attr['rx'] + ',' + ' Y-radius:' + attr['ry'];
3358
break;
3359
3360
case VIA_REGION_SHAPE.POLYLINE:
3361
case VIA_REGION_SHAPE.POLYGON:
3362
break;
3363
}
3364
}
3365
3366
if ( !_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
3371
if ( _via_region_edge[0] === _via_user_sel_region_id ) {
3372
switch(_via_region_edge[1]) {
3373
// rect
3374
case 1: // Fall-through // top-left corner of rect
3375
case 3: // bottom-right corner of rect
3376
_via_reg_canvas.style.cursor = "nwse-resize";
3377
break;
3378
case 2: // Fall-through // top-right corner of rect
3379
case 4: // bottom-left corner of rect
3380
_via_reg_canvas.style.cursor = "nesw-resize";
3381
break;
3382
3383
case 5: // Fall-through // top-middle point of rect
3384
case 7: // bottom-middle point of rect
3385
_via_reg_canvas.style.cursor = "ns-resize";
3386
break;
3387
case 6: // Fall-through // top-middle point of rect
3388
case 8: // bottom-middle point of rect
3389
_via_reg_canvas.style.cursor = "ew-resize";
3390
break;
3391
3392
// circle and ellipse
3393
case 5:
3394
_via_reg_canvas.style.cursor = "n-resize";
3395
break;
3396
case 6:
3397
_via_reg_canvas.style.cursor = "e-resize";
3398
break;
3399
3400
default:
3401
_via_reg_canvas.style.cursor = "default";
3402
break;
3403
}
3404
3405
if (_via_region_edge[1] >= VIA_POLYGON_RESIZE_VERTEX_OFFSET) {
3406
// indicates mouse over polygon vertex
3407
_via_reg_canvas.style.cursor = "crosshair";
3408
show_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 {
3411
var yes = is_inside_this_region(_via_current_x,
3412
_via_current_y,
3413
_via_user_sel_region_id);
3414
if (yes) {
3415
_via_reg_canvas.style.cursor = "move";
3416
} else {
3417
_via_reg_canvas.style.cursor = "default";
3418
}
3419
3420
}
3421
} else {
3422
annotation_editor_hide() // resizing
3423
}
3424
}
3425
3426
if(_via_is_user_drawing_region) {
3427
// draw region as the user drags the mouse cursor
3428
if (_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
3435
var region_x0 = _via_click_x0;
3436
var region_y0 = _via_click_y0;
3437
3438
var dx = Math.round(Math.abs(_via_current_x - _via_click_x0));
3439
var dy = Math.round(Math.abs(_via_current_y - _via_click_y0));
3440
_via_reg_ctx.strokeStyle = VIA_THEME_BOUNDARY_FILL_COLOR;
3441
3442
switch (_via_current_shape ) {
3443
case VIA_REGION_SHAPE.RECT:
3444
if ( _via_click_x0 < _via_current_x ) {
3445
if ( _via_click_y0 < _via_current_y ) {
3446
region_x0 = _via_click_x0;
3447
region_y0 = _via_click_y0;
3448
} else {
3449
region_x0 = _via_click_x0;
3450
region_y0 = _via_current_y;
3451
}
3452
} else {
3453
if ( _via_click_y0 < _via_current_y ) {
3454
region_x0 = _via_current_x;
3455
region_y0 = _via_click_y0;
3456
} else {
3457
region_x0 = _via_current_x;
3458
region_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
3465
if ( rf != null && _via_is_region_info_visible ) {
3466
rf.innerHTML += ',' + ' W:' + dx + ',' + ' H:' + dy;
3467
}
3468
break;
3469
3470
case VIA_REGION_SHAPE.CIRCLE:
3471
var 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
3475
if ( rf != null && _via_is_region_info_visible ) {
3476
rf.innerHTML += ',' + ' Radius:' + circle_radius;
3477
}
3478
break;
3479
3480
case VIA_REGION_SHAPE.ELLIPSE:
3481
_via_draw_ellipse_region(region_x0, region_y0, dx, dy, 0, false);
3482
3483
// display the current region info
3484
if ( rf != null && _via_is_region_info_visible ) {
3485
rf.innerHTML += ',' + ' X-radius:' + fixfloat(dx) + ',' + ' Y-radius:' + fixfloat(dy);
3486
}
3487
break;
3488
3489
case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
3490
case VIA_REGION_SHAPE.POLYGON:
3491
// this is handled by the if ( _via_is_user_drawing_polygon ) { ... }
3492
// see below
3493
break;
3494
}
3495
_via_reg_canvas.focus();
3496
}
3497
3498
if ( _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
3501
if (_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
3508
var region_id = _via_region_edge[0];
3509
var attr = _via_canvas_regions[region_id].shape_attributes;
3510
switch (attr['name']) {
3511
case VIA_REGION_SHAPE.RECT:
3512
// original rectangle
3513
var d = [attr['x'], attr['y'], 0, 0];
3514
d[2] = d[0] + attr['width'];
3515
d[3] = d[1] + attr['height'];
3516
3517
var mx = _via_current_x;
3518
var my = _via_current_y;
3519
var preserve_aspect_ratio = false;
3520
// constrain (mx,my) to lie on a line connecting a diagonal of rectangle
3521
if ( _via_is_ctrl_pressed ) {
3522
preserve_aspect_ratio = true;
3523
}
3524
3525
rect_update_corner(_via_region_edge[1], d, mx, my, preserve_aspect_ratio);
3526
rect_standardize_coordinates(d);
3527
3528
var w = Math.abs(d[2] - d[0]);
3529
var h = Math.abs(d[3] - d[1]);
3530
_via_draw_rect_region(d[0], d[1], w, h, true);
3531
3532
if ( rf != null && _via_is_region_info_visible ) {
3533
rf.innerHTML += ',' + ' W:' + w + ',' + ' H:' + h;
3534
}
3535
break;
3536
3537
case VIA_REGION_SHAPE.CIRCLE:
3538
var dx = Math.abs(attr['cx'] - _via_current_x);
3539
var dy = Math.abs(attr['cy'] - _via_current_y);
3540
var new_r = Math.sqrt( dx*dx + dy*dy );
3541
_via_draw_circle_region(attr['cx'],
3542
attr['cy'],
3543
new_r,
3544
true);
3545
if ( rf != null && _via_is_region_info_visible ) {
3546
var curr_texts = rf.innerHTML.split(",");
3547
rf.innerHTML = "";
3548
rf.innerHTML += curr_texts[0] + ',' + curr_texts[1] + ',' + ' Radius:' + Math.round(new_r);
3549
}
3550
break;
3551
3552
case VIA_REGION_SHAPE.ELLIPSE:
3553
var new_rx = attr['rx'];
3554
var new_ry = attr['ry'];
3555
var new_theta = attr['theta'];
3556
var dx = Math.abs(attr['cx'] - _via_current_x);
3557
var dy = Math.abs(attr['cy'] - _via_current_y);
3558
switch(_via_region_edge[1]) {
3559
case 5:
3560
new_ry = Math.sqrt(dx*dx + dy*dy);
3561
new_theta = Math.atan2(- (_via_current_x - attr['cx']), (_via_current_y - attr['cy']));
3562
break;
3563
3564
case 6:
3565
new_rx = Math.sqrt(dx*dx + dy*dy);
3566
new_theta = Math.atan2((_via_current_y - attr['cy']), (_via_current_x - attr['cx']));
3567
break;
3568
3569
default:
3570
new_rx = dx;
3571
new_ry = dy;
3572
new_theta = 0;
3573
break;
3574
}
3575
3576
_via_draw_ellipse_region(attr['cx'],
3577
attr['cy'],
3578
new_rx,
3579
new_ry,
3580
new_theta,
3581
true);
3582
if ( rf != null && _via_is_region_info_visible ) {
3583
var curr_texts = rf.innerHTML.split(",");
3584
rf.innerHTML = "";
3585
rf.innerHTML = curr_texts[0] + ',' + curr_texts[1] + ',' + ' X-radius:' + fixfloat(new_rx) + ',' + ' Y-radius:' + fixfloat(new_ry);
3586
}
3587
break;
3588
3589
case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
3590
case VIA_REGION_SHAPE.POLYGON:
3591
var moved_all_points_x = attr['all_points_x'].slice(0);
3592
var moved_all_points_y = attr['all_points_y'].slice(0);
3593
var moved_vertex_id = _via_region_edge[1] - VIA_POLYGON_RESIZE_VERTEX_OFFSET;
3594
3595
moved_all_points_x[moved_vertex_id] = _via_current_x;
3596
moved_all_points_y[moved_vertex_id] = _via_current_y;
3597
3598
_via_draw_polygon_region(moved_all_points_x,
3599
moved_all_points_y,
3600
true,
3601
attr['name']);
3602
if ( rf != null && _via_is_region_info_visible ) {
3603
rf.innerHTML += ',' + ' Vertices:' + attr['all_points_x'].length;
3604
}
3605
break;
3606
}
3607
_via_reg_canvas.focus();
3608
}
3609
3610
if ( _via_is_user_moving_region ) {
3611
// draw region as the user drags the mouse coursor
3612
if (_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
3619
var move_x = (_via_current_x - _via_region_click_x);
3620
var move_y = (_via_current_y - _via_region_click_y);
3621
var attr = _via_canvas_regions[_via_user_sel_region_id].shape_attributes;
3622
3623
switch (attr['name']) {
3624
case VIA_REGION_SHAPE.RECT:
3625
_via_draw_rect_region(attr['x'] + move_x,
3626
attr['y'] + move_y,
3627
attr['width'],
3628
attr['height'],
3629
true);
3630
// display the current region info
3631
if ( rf != null && _via_is_region_info_visible ) {
3632
rf.innerHTML += ',' + ' W:' + attr['width'] + ',' + ' H:' + attr['height'];
3633
}
3634
break;
3635
3636
case VIA_REGION_SHAPE.CIRCLE:
3637
_via_draw_circle_region(attr['cx'] + move_x,
3638
attr['cy'] + move_y,
3639
attr['r'],
3640
true);
3641
break;
3642
3643
case VIA_REGION_SHAPE.ELLIPSE:
3644
if (typeof(attr['theta']) === 'undefined') { attr['theta'] = 0; }
3645
_via_draw_ellipse_region(attr['cx'] + move_x,
3646
attr['cy'] + move_y,
3647
attr['rx'],
3648
attr['ry'],
3649
attr['theta'],
3650
true);
3651
break;
3652
3653
case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
3654
case VIA_REGION_SHAPE.POLYGON:
3655
var moved_all_points_x = attr['all_points_x'].slice(0);
3656
var moved_all_points_y = attr['all_points_y'].slice(0);
3657
for (var i=0; i<moved_all_points_x.length; ++i) {
3658
moved_all_points_x[i] += move_x;
3659
moved_all_points_y[i] += move_y;
3660
}
3661
_via_draw_polygon_region(moved_all_points_x,
3662
moved_all_points_y,
3663
true,
3664
attr['name']);
3665
if ( rf != null && _via_is_region_info_visible ) {
3666
rf.innerHTML += ',' + ' Vertices:' + attr['all_points_x'].length;
3667
}
3668
break;
3669
3670
case VIA_REGION_SHAPE.POINT:
3671
_via_draw_point_region(attr['cx'] + move_x,
3672
attr['cy'] + move_y,
3673
true);
3674
break;
3675
}
3676
_via_reg_canvas.focus();
3677
annotation_editor_hide() // moving
3678
return;
3679
}
3680
3681
if ( _via_is_user_drawing_polygon ) {
3682
_via_redraw_reg_canvas();
3683
var attr = _via_canvas_regions[_via_current_polygon_region_id].shape_attributes;
3684
var all_points_x = attr['all_points_x'];
3685
var all_points_y = attr['all_points_y'];
3686
var npts = all_points_x.length;
3687
3688
if ( npts > 0 ) {
3689
var line_x = [all_points_x.slice(npts-1), _via_current_x];
3690
var 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
3694
if ( rf != null && _via_is_region_info_visible ) {
3695
rf.innerHTML += ',' + ' Vertices:' + npts;
3696
}
3697
}
3698
}
3699
3700
function _via_move_selected_regions(move_x, move_y) {
3701
var i, n;
3702
n = _via_region_selected_flag.length;
3703
for ( i = 0; i < n; ++i ) {
3704
if ( _via_region_selected_flag[i] ) {
3705
_via_move_region(i, move_x, move_y);
3706
}
3707
}
3708
}
3709
3710
function _via_validate_move_region(x, y, canvas_attr) {
3711
switch( canvas_attr['name'] ) {
3712
case VIA_REGION_SHAPE.RECT:
3713
// left and top boundary check
3714
if (x < 0 || y < 0) {
3715
show_message('Region moved beyond image boundary. Resetting.');
3716
return false;
3717
}
3718
// right and bottom boundary check
3719
if ((y + canvas_attr['height']) > _via_current_image_height ||
3720
(x + canvas_attr['width']) > _via_current_image_width) {
3721
show_message('Region moved beyond image boundary. Resetting.');
3722
return false;
3723
}
3724
3725
// same validation for all
3726
case VIA_REGION_SHAPE.CIRCLE:
3727
case VIA_REGION_SHAPE.ELLIPSE:
3728
case VIA_REGION_SHAPE.POINT:
3729
case VIA_REGION_SHAPE.POLYLINE:
3730
case VIA_REGION_SHAPE.POLYGON:
3731
if (x < 0 || y < 0 ||
3732
x > _via_current_image_width || y > _via_current_image_height) {
3733
show_message('Region moved beyond image boundary. Resetting.');
3734
return false;
3735
}
3736
}
3737
return true;
3738
}
3739
3740
function _via_move_region(region_id, move_x, move_y) {
3741
var image_attr = _via_img_metadata[_via_image_id].regions[region_id].shape_attributes;
3742
var canvas_attr = _via_canvas_regions[region_id].shape_attributes;
3743
3744
switch( canvas_attr['name'] ) {
3745
case VIA_REGION_SHAPE.RECT:
3746
var xnew = image_attr['x'] + Math.round(move_x * _via_canvas_scale);
3747
var ynew = image_attr['y'] + Math.round(move_y * _via_canvas_scale);
3748
3749
var is_valid = _via_validate_move_region(xnew, ynew, image_attr);
3750
if (! is_valid ) { break; }
3751
3752
image_attr['x'] = xnew;
3753
image_attr['y'] = ynew;
3754
3755
canvas_attr['x'] = Math.round( image_attr['x'] / _via_canvas_scale);
3756
canvas_attr['y'] = Math.round( image_attr['y'] / _via_canvas_scale);
3757
break;
3758
3759
case VIA_REGION_SHAPE.CIRCLE: // Fall-through
3760
case VIA_REGION_SHAPE.ELLIPSE: // Fall-through
3761
case VIA_REGION_SHAPE.POINT:
3762
var cxnew = image_attr['cx'] + Math.round(move_x * _via_canvas_scale);
3763
var cynew = image_attr['cy'] + Math.round(move_y * _via_canvas_scale);
3764
3765
var is_valid = _via_validate_move_region(cxnew, cynew, image_attr);
3766
if (! is_valid ) { break; }
3767
3768
image_attr['cx'] = cxnew;
3769
image_attr['cy'] = cynew;
3770
3771
canvas_attr['cx'] = Math.round( image_attr['cx'] / _via_canvas_scale);
3772
canvas_attr['cy'] = Math.round( image_attr['cy'] / _via_canvas_scale);
3773
break;
3774
3775
case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
3776
case VIA_REGION_SHAPE.POLYGON:
3777
var img_px = image_attr['all_points_x'];
3778
var img_py = image_attr['all_points_y'];
3779
var canvas_px = canvas_attr['all_points_x'];
3780
var canvas_py = canvas_attr['all_points_y'];
3781
// clone for reverting if valiation fails
3782
var img_px_old = Object.assign({}, img_px);
3783
var img_py_old = Object.assign({}, img_py);
3784
3785
// validate move
3786
for (var i=0; i<img_px.length; ++i) {
3787
var pxnew = img_px[i] + Math.round(move_x * _via_canvas_scale);
3788
var pynew = img_py[i] + Math.round(move_y * _via_canvas_scale);
3789
if (! _via_validate_move_region(pxnew, pynew, image_attr) ) {
3790
img_px = img_px_old;
3791
img_py = img_py_old;
3792
break;
3793
}
3794
}
3795
// move points
3796
for (var i=0; i<img_px.length; ++i) {
3797
img_px[i] = img_px[i] + Math.round(move_x * _via_canvas_scale);
3798
img_py[i] = img_py[i] + Math.round(move_y * _via_canvas_scale);
3799
}
3800
3801
for (var i=0; i<canvas_px.length; ++i) {
3802
canvas_px[i] = Math.round( img_px[i] / _via_canvas_scale );
3803
canvas_py[i] = Math.round( img_py[i] / _via_canvas_scale );
3804
}
3805
break;
3806
}
3807
}
3808
3809
function _via_polygon_del_vertex(region_id, vertex_id) {
3810
var rs = _via_canvas_regions[region_id].shape_attributes;
3811
var npts = rs['all_points_x'].length;
3812
var shape = rs['name'];
3813
if ( shape !== VIA_REGION_SHAPE.POLYGON && shape !== VIA_REGION_SHAPE.POLYLINE ) {
3814
show_message('Vertices can only be deleted from polygon/polyline.');
3815
return false;
3816
}
3817
if ( npts <=3 && shape === VIA_REGION_SHAPE.POLYGON ) {
3818
show_message('Failed to delete vertex because a polygon must have at least 3 vertices.');
3819
return false;
3820
}
3821
if ( npts <=2 && shape === VIA_REGION_SHAPE.POLYLINE ) {
3822
show_message('Failed to delete vertex because a polyline must have at least 2 vertices.');
3823
return 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);
3832
return true;
3833
}
3834
3835
//
3836
// Canvas update routines
3837
//
3838
function _via_redraw_reg_canvas() {
3839
if (_via_current_image_loaded) {
3840
_via_reg_ctx.clearRect(0, 0, _via_reg_canvas.width, _via_reg_canvas.height);
3841
if ( _via_canvas_regions.length > 0 ) {
3842
if (_via_is_region_boundary_visible) {
3843
draw_all_regions();
3844
}
3845
if (_via_is_region_id_visible) {
3846
draw_all_region_id();
3847
}
3848
}
3849
}
3850
}
3851
3852
function _via_clear_reg_canvas() {
3853
_via_reg_ctx.clearRect(0, 0, _via_reg_canvas.width, _via_reg_canvas.height);
3854
}
3855
3856
function draw_all_regions() {
3857
var aid = _via_settings.ui.image.region_color;
3858
var attr, is_selected, aid, avalue;
3859
for (var i=0; i < _via_canvas_regions.length; ++i) {
3860
attr = _via_canvas_regions[i].shape_attributes;
3861
is_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;
3865
if ( ! _via_is_user_drawing_polygon &&
3866
aid !== '__via_default_region_color__' ) {
3867
avalue = _via_img_metadata[_via_image_id].regions[i].region_attributes[aid];
3868
if ( _via_canvas_regions_group_color.hasOwnProperty(avalue) ) {
3869
_via_reg_ctx.strokeStyle = _via_canvas_regions_group_color[avalue];
3870
}
3871
}
3872
3873
switch( attr['name'] ) {
3874
case VIA_REGION_SHAPE.RECT:
3875
_via_draw_rect_region(attr['x'],
3876
attr['y'],
3877
attr['width'],
3878
attr['height'],
3879
is_selected);
3880
break;
3881
3882
case VIA_REGION_SHAPE.CIRCLE:
3883
_via_draw_circle_region(attr['cx'],
3884
attr['cy'],
3885
attr['r'],
3886
is_selected);
3887
break;
3888
3889
case VIA_REGION_SHAPE.ELLIPSE:
3890
if (typeof(attr['theta']) === 'undefined') { attr['theta'] = 0; }
3891
_via_draw_ellipse_region(attr['cx'],
3892
attr['cy'],
3893
attr['rx'],
3894
attr['ry'],
3895
attr['theta'],
3896
is_selected);
3897
break;
3898
3899
case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
3900
case VIA_REGION_SHAPE.POLYGON:
3901
_via_draw_polygon_region(attr['all_points_x'],
3902
attr['all_points_y'],
3903
is_selected,
3904
attr['name']);
3905
break;
3906
3907
case VIA_REGION_SHAPE.POINT:
3908
_via_draw_point_region(attr['cx'],
3909
attr['cy'],
3910
is_selected);
3911
break;
3912
}
3913
}
3914
}
3915
3916
// control point for resize of region boundaries
3917
function _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
3927
function _via_draw_rect_region(x, y, w, h, is_selected) {
3928
if (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
3954
if ( w > VIA_THEME_REGION_BOUNDARY_WIDTH &&
3955
h > 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,
3960
y - VIA_THEME_REGION_BOUNDARY_WIDTH/2,
3961
w + VIA_THEME_REGION_BOUNDARY_WIDTH,
3962
h + VIA_THEME_REGION_BOUNDARY_WIDTH);
3963
_via_reg_ctx.stroke();
3964
3965
_via_draw_rect(x + VIA_THEME_REGION_BOUNDARY_WIDTH/2,
3966
y + VIA_THEME_REGION_BOUNDARY_WIDTH/2,
3967
w - VIA_THEME_REGION_BOUNDARY_WIDTH,
3968
h - VIA_THEME_REGION_BOUNDARY_WIDTH);
3969
_via_reg_ctx.stroke();
3970
}
3971
}
3972
}
3973
3974
function _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
3983
function _via_draw_circle_region(cx, cy, r, is_selected) {
3984
if (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
4003
if ( 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,
4008
r - VIA_THEME_REGION_BOUNDARY_WIDTH/2);
4009
_via_reg_ctx.stroke();
4010
_via_draw_circle(cx, cy,
4011
r + VIA_THEME_REGION_BOUNDARY_WIDTH/2);
4012
_via_reg_ctx.stroke();
4013
}
4014
}
4015
}
4016
4017
function _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
4023
function _via_draw_ellipse_region(cx, cy, rx, ry, rr, is_selected) {
4024
if (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
4047
if ( rx > VIA_THEME_REGION_BOUNDARY_WIDTH &&
4048
ry > 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,
4053
rx + VIA_THEME_REGION_BOUNDARY_WIDTH/2,
4054
ry + VIA_THEME_REGION_BOUNDARY_WIDTH/2,
4055
rr);
4056
_via_reg_ctx.stroke();
4057
_via_draw_ellipse(cx, cy,
4058
rx - VIA_THEME_REGION_BOUNDARY_WIDTH/2,
4059
ry - VIA_THEME_REGION_BOUNDARY_WIDTH/2,
4060
rr);
4061
_via_reg_ctx.stroke();
4062
}
4063
}
4064
}
4065
4066
function _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
4076
function _via_draw_polygon_region(all_points_x, all_points_y, is_selected, shape) {
4077
if ( 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]);
4082
for ( var i=1; i < all_points_x.length; ++i ) {
4083
_via_reg_ctx.lineTo(all_points_x[i], all_points_y[i]);
4084
}
4085
if ( 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;
4094
for ( 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]);
4102
for ( var i=0; i < all_points_x.length; ++i ) {
4103
_via_reg_ctx.lineTo(all_points_x[i], all_points_y[i]);
4104
}
4105
if ( 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
4112
function _via_draw_point_region(cx, cy, is_selected) {
4113
if (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,
4134
VIA_REGION_POINT_RADIUS - VIA_THEME_REGION_BOUNDARY_WIDTH/2);
4135
_via_reg_ctx.stroke();
4136
_via_draw_point(cx, cy,
4137
VIA_REGION_POINT_RADIUS + VIA_THEME_REGION_BOUNDARY_WIDTH/2);
4138
_via_reg_ctx.stroke();
4139
}
4140
}
4141
4142
function _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
4148
function draw_all_region_id() {
4149
_via_reg_ctx.shadowColor = "transparent";
4150
_via_reg_ctx.font = _via_settings.ui.image.region_label_font;
4151
for ( var i = 0; i < _via_img_metadata[_via_image_id].regions.length; ++i ) {
4152
var canvas_reg = _via_canvas_regions[i];
4153
4154
var bbox = get_region_bounding_box(canvas_reg);
4155
var x = bbox[0];
4156
var y = bbox[1];
4157
var w = Math.abs(bbox[2] - bbox[0]);
4158
4159
var char_width = _via_reg_ctx.measureText('M').width;
4160
var char_height = 1.8 * char_width;
4161
4162
var annotation_str = (i+1).toString();
4163
var rattr = _via_img_metadata[_via_image_id].regions[i].region_attributes[_via_settings.ui.image.region_label];
4164
var rshape = _via_img_metadata[_via_image_id].regions[i].shape_attributes['name'];
4165
if ( _via_settings.ui.image.region_label !== '__via_region_id__' ) {
4166
if ( typeof(rattr) !== 'undefined' ) {
4167
switch( typeof(rattr) ) {
4168
default:
4169
case 'string':
4170
annotation_str = rattr;
4171
break;
4172
case 'object':
4173
annotation_str = Object.keys(rattr).join(',');
4174
break;
4175
}
4176
} else {
4177
annotation_str = 'undefined';
4178
}
4179
}
4180
4181
var bgnd_rect_width;
4182
var strw = _via_reg_ctx.measureText(annotation_str).width;
4183
if ( strw > w ) {
4184
if ( _via_settings.ui.image.region_label === '__via_region_id__' ) {
4185
// region-id is always visible in full
4186
bgnd_rect_width = strw + char_width;
4187
} else {
4188
4189
// if text overflows, crop it
4190
var str_max = Math.floor((w * annotation_str.length) / strw);
4191
if ( str_max > 1 ) {
4192
annotation_str = annotation_str.substr(0, str_max-1) + '.';
4193
bgnd_rect_width = w;
4194
} else {
4195
annotation_str = annotation_str.substr(0, 1) + '.';
4196
bgnd_rect_width = 2 * char_width;
4197
}
4198
}
4199
} else {
4200
bgnd_rect_width = strw + char_width;
4201
}
4202
4203
if (canvas_reg.shape_attributes['name'] === VIA_REGION_SHAPE.POLYGON ||
4204
canvas_reg.shape_attributes['name'] === VIA_REGION_SHAPE.POLYLINE) {
4205
// put label near the first vertex
4206
x = canvas_reg.shape_attributes['all_points_x'][0];
4207
y = canvas_reg.shape_attributes['all_points_y'][0];
4208
} else {
4209
// center the label
4210
x = x - (bgnd_rect_width/2 - w/2);
4211
}
4212
4213
// ensure that the text is within the image boundaries
4214
if ( y < char_height ) {
4215
y = 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),
4222
Math.floor(y - 1.1*char_height),
4223
Math.floor(bgnd_rect_width),
4224
Math.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,
4230
Math.floor(x + 0.4*char_width),
4231
Math.floor(y - 0.35*char_height));
4232
4233
}
4234
}
4235
4236
function get_region_bounding_box(region) {
4237
var d = region.shape_attributes;
4238
var bbox = new Array(4);
4239
4240
switch( d['name'] ) {
4241
case 'rect':
4242
bbox[0] = d['x'];
4243
bbox[1] = d['y'];
4244
bbox[2] = d['x'] + d['width'];
4245
bbox[3] = d['y'] + d['height'];
4246
break;
4247
4248
case 'circle':
4249
bbox[0] = d['cx'] - d['r'];
4250
bbox[1] = d['cy'] - d['r'];
4251
bbox[2] = d['cx'] + d['r'];
4252
bbox[3] = d['cy'] + d['r'];
4253
break;
4254
4255
case 'ellipse':
4256
let radians = d['theta'];
4257
let radians90 = radians + Math.PI / 2;
4258
let ux = d['rx'] * Math.cos(radians);
4259
let uy = d['rx'] * Math.sin(radians);
4260
let vx = d['ry'] * Math.cos(radians90);
4261
let vy = d['ry'] * Math.sin(radians90);
4262
4263
let width = Math.sqrt(ux * ux + vx * vx) * 2;
4264
let height = Math.sqrt(uy * uy + vy * vy) * 2;
4265
4266
bbox[0] = d['cx'] - (width / 2);
4267
bbox[1] = d['cy'] - (height / 2);
4268
bbox[2] = d['cx'] + (width / 2);
4269
bbox[3] = d['cy'] + (height / 2);
4270
break;
4271
4272
case 'polyline': // handled by polygon
4273
case 'polygon':
4274
var all_points_x = d['all_points_x'];
4275
var all_points_y = d['all_points_y'];
4276
4277
var minx = Number.MAX_SAFE_INTEGER;
4278
var miny = Number.MAX_SAFE_INTEGER;
4279
var maxx = 0;
4280
var maxy = 0;
4281
for ( var i=0; i < all_points_x.length; ++i ) {
4282
if ( all_points_x[i] < minx ) {
4283
minx = all_points_x[i];
4284
}
4285
if ( all_points_x[i] > maxx ) {
4286
maxx = all_points_x[i];
4287
}
4288
if ( all_points_y[i] < miny ) {
4289
miny = all_points_y[i];
4290
}
4291
if ( all_points_y[i] > maxy ) {
4292
maxy = all_points_y[i];
4293
}
4294
}
4295
bbox[0] = minx;
4296
bbox[1] = miny;
4297
bbox[2] = maxx;
4298
bbox[3] = maxy;
4299
break;
4300
4301
case 'point':
4302
bbox[0] = d['cx'] - VIA_REGION_POINT_RADIUS;
4303
bbox[1] = d['cy'] - VIA_REGION_POINT_RADIUS;
4304
bbox[2] = d['cx'] + VIA_REGION_POINT_RADIUS;
4305
bbox[3] = d['cy'] + VIA_REGION_POINT_RADIUS;
4306
break;
4307
}
4308
return bbox;
4309
}
4310
4311
//
4312
// Region collision routines
4313
//
4314
function is_inside_region(px, py, descending_order) {
4315
var N = _via_canvas_regions.length;
4316
if ( N === 0 ) {
4317
return -1;
4318
}
4319
var start, end, del;
4320
// traverse the canvas regions in alternating ascending
4321
// and descending order to solve the issue of nested regions
4322
if ( descending_order ) {
4323
start = N - 1;
4324
end = -1;
4325
del = -1;
4326
} else {
4327
start = 0;
4328
end = N;
4329
del = 1;
4330
}
4331
4332
var i = start;
4333
while ( i !== end ) {
4334
var yes = is_inside_this_region(px, py, i);
4335
if (yes) {
4336
return i;
4337
}
4338
i = i + del;
4339
}
4340
return -1;
4341
}
4342
4343
function is_inside_this_region(px, py, region_id) {
4344
var attr = _via_canvas_regions[region_id].shape_attributes;
4345
var result = false;
4346
switch ( attr['name'] ) {
4347
case VIA_REGION_SHAPE.RECT:
4348
result = is_inside_rect(attr['x'],
4349
attr['y'],
4350
attr['width'],
4351
attr['height'],
4352
px, py);
4353
break;
4354
4355
case VIA_REGION_SHAPE.CIRCLE:
4356
result = is_inside_circle(attr['cx'],
4357
attr['cy'],
4358
attr['r'],
4359
px, py);
4360
break;
4361
4362
case VIA_REGION_SHAPE.ELLIPSE:
4363
result = is_inside_ellipse(attr['cx'],
4364
attr['cy'],
4365
attr['rx'],
4366
attr['ry'],
4367
attr['theta'],
4368
px, py);
4369
break;
4370
4371
case VIA_REGION_SHAPE.POLYLINE: // handled by POLYGON
4372
case VIA_REGION_SHAPE.POLYGON:
4373
result = is_inside_polygon(attr['all_points_x'],
4374
attr['all_points_y'],
4375
px, py);
4376
break;
4377
4378
case VIA_REGION_SHAPE.POINT:
4379
result = is_inside_point(attr['cx'],
4380
attr['cy'],
4381
px, py);
4382
break;
4383
}
4384
return result;
4385
}
4386
4387
function is_inside_circle(cx, cy, r, px, py) {
4388
var dx = px - cx;
4389
var dy = py - cy;
4390
return (dx * dx + dy * dy) < r * r;
4391
}
4392
4393
function is_inside_rect(x, y, w, h, px, py) {
4394
return px > x &&
4395
px < (x + w) &&
4396
py > y &&
4397
py < (y + h);
4398
}
4399
4400
function is_inside_ellipse(cx, cy, rx, ry, rr, px, py) {
4401
// Inverse rotation of pixel coordinates
4402
var dx = Math.cos(-rr) * (cx - px) - Math.sin(-rr) * (cy - py)
4403
var dy = Math.sin(-rr) * (cx - px) + Math.cos(-rr) * (cy - py)
4404
4405
return ((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
4410
function is_inside_polygon(all_points_x, all_points_y, px, py) {
4411
if ( all_points_x.length === 0 || all_points_y.length === 0 ) {
4412
return 0;
4413
}
4414
4415
var wn = 0; // the winding number counter
4416
var n = all_points_x.length;
4417
var i;
4418
// loop through all edges of the polygon
4419
for ( i = 0; i < n-1; ++i ) { // edge from V[i] to V[i+1]
4420
var is_left_value = is_left( all_points_x[i], all_points_y[i],
4421
all_points_x[i+1], all_points_y[i+1],
4422
px, py);
4423
4424
if (all_points_y[i] <= py) {
4425
if (all_points_y[i+1] > py && is_left_value > 0) {
4426
++wn;
4427
}
4428
}
4429
else {
4430
if (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
4437
var is_left_value = is_left( all_points_x[n-1], all_points_y[n-1],
4438
all_points_x[0], all_points_y[0],
4439
px, py);
4440
4441
if (all_points_y[n-1] <= py) {
4442
if (all_points_y[0] > py && is_left_value > 0) {
4443
++wn;
4444
}
4445
}
4446
else {
4447
if (all_points_y[0] <= py && is_left_value < 0) {
4448
--wn;
4449
}
4450
}
4451
4452
if ( wn === 0 ) {
4453
return 0;
4454
}
4455
else {
4456
return 1;
4457
}
4458
}
4459
4460
function is_inside_point(cx, cy, px, py) {
4461
var dx = px - cx;
4462
var dy = py - cy;
4463
var r2 = VIA_POLYGON_VERTEX_MATCH_TOL * VIA_POLYGON_VERTEX_MATCH_TOL;
4464
return (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
4472
function is_left(x0, y0, x1, y1, x2, y2) {
4473
return ( ((x1 - x0) * (y2 - y0)) - ((x2 - x0) * (y1 - y0)) );
4474
}
4475
4476
function is_on_region_corner(px, py) {
4477
var _via_region_edge = [-1, -1]; // region_id, corner_id [top-left=1,top-right=2,bottom-right=3,bottom-left=4]
4478
4479
for ( var i = 0; i < _via_canvas_regions.length; ++i ) {
4480
var attr = _via_canvas_regions[i].shape_attributes;
4481
var result = false;
4482
_via_region_edge[0] = i;
4483
4484
switch ( attr['name'] ) {
4485
case VIA_REGION_SHAPE.RECT:
4486
result = is_on_rect_edge(attr['x'],
4487
attr['y'],
4488
attr['width'],
4489
attr['height'],
4490
px, py);
4491
break;
4492
4493
case VIA_REGION_SHAPE.CIRCLE:
4494
result = is_on_circle_edge(attr['cx'],
4495
attr['cy'],
4496
attr['r'],
4497
px, py);
4498
break;
4499
4500
case VIA_REGION_SHAPE.ELLIPSE:
4501
result = is_on_ellipse_edge(attr['cx'],
4502
attr['cy'],
4503
attr['rx'],
4504
attr['ry'],
4505
attr['theta'],
4506
px, py);
4507
break;
4508
4509
case VIA_REGION_SHAPE.POLYLINE: // handled by polygon
4510
case VIA_REGION_SHAPE.POLYGON:
4511
result = is_on_polygon_vertex(attr['all_points_x'],
4512
attr['all_points_y'],
4513
px, py);
4514
if ( result === 0 ) {
4515
result = is_on_polygon_edge(attr['all_points_x'],
4516
attr['all_points_y'],
4517
px, py);
4518
}
4519
break;
4520
4521
case VIA_REGION_SHAPE.POINT:
4522
// since there are no edges of a point
4523
result = 0;
4524
break;
4525
}
4526
4527
if (result > 0) {
4528
_via_region_edge[1] = result;
4529
return _via_region_edge;
4530
}
4531
}
4532
_via_region_edge[0] = -1;
4533
return _via_region_edge;
4534
}
4535
4536
function is_on_rect_edge(x, y, w, h, px, py) {
4537
var dx0 = Math.abs(x - px);
4538
var dy0 = Math.abs(y - py);
4539
var dx1 = Math.abs(x + w - px);
4540
var dy1 = Math.abs(y + h - py);
4541
//[top-left=1,top-right=2,bottom-right=3,bottom-left=4]
4542
if ( dx0 < VIA_REGION_EDGE_TOL &&
4543
dy0 < VIA_REGION_EDGE_TOL ) {
4544
return 1;
4545
}
4546
if ( dx1 < VIA_REGION_EDGE_TOL &&
4547
dy0 < VIA_REGION_EDGE_TOL ) {
4548
return 2;
4549
}
4550
if ( dx1 < VIA_REGION_EDGE_TOL &&
4551
dy1 < VIA_REGION_EDGE_TOL ) {
4552
return 3;
4553
}
4554
4555
if ( dx0 < VIA_REGION_EDGE_TOL &&
4556
dy1 < VIA_REGION_EDGE_TOL ) {
4557
return 4;
4558
}
4559
4560
var mx0 = Math.abs(x + w/2 - px);
4561
var my0 = Math.abs(y + h/2 - py);
4562
//[top-middle=5,right-middle=6,bottom-middle=7,left-middle=8]
4563
if ( mx0 < VIA_REGION_EDGE_TOL &&
4564
dy0 < VIA_REGION_EDGE_TOL ) {
4565
return 5;
4566
}
4567
if ( dx1 < VIA_REGION_EDGE_TOL &&
4568
my0 < VIA_REGION_EDGE_TOL ) {
4569
return 6;
4570
}
4571
if ( mx0 < VIA_REGION_EDGE_TOL &&
4572
dy1 < VIA_REGION_EDGE_TOL ) {
4573
return 7;
4574
}
4575
if ( dx0 < VIA_REGION_EDGE_TOL &&
4576
my0 < VIA_REGION_EDGE_TOL ) {
4577
return 8;
4578
}
4579
4580
return 0;
4581
}
4582
4583
function is_on_circle_edge(cx, cy, r, px, py) {
4584
var dx = cx - px;
4585
var dy = cy - py;
4586
if ( Math.abs(Math.sqrt( dx*dx + dy*dy ) - r) < VIA_REGION_EDGE_TOL ) {
4587
var theta = Math.atan2( py - cy, px - cx );
4588
if ( Math.abs(theta - (Math.PI/2)) < VIA_THETA_TOL ||
4589
Math.abs(theta + (Math.PI/2)) < VIA_THETA_TOL) {
4590
return 5;
4591
}
4592
if ( Math.abs(theta) < VIA_THETA_TOL ||
4593
Math.abs(Math.abs(theta) - Math.PI) < VIA_THETA_TOL) {
4594
return 6;
4595
}
4596
4597
if ( theta > 0 && theta < (Math.PI/2) ) {
4598
return 1;
4599
}
4600
if ( theta > (Math.PI/2) && theta < (Math.PI) ) {
4601
return 4;
4602
}
4603
if ( theta < 0 && theta > -(Math.PI/2) ) {
4604
return 2;
4605
}
4606
if ( theta < -(Math.PI/2) && theta > -Math.PI ) {
4607
return 3;
4608
}
4609
} else {
4610
return 0;
4611
}
4612
}
4613
4614
function is_on_ellipse_edge(cx, cy, rx, ry, rr, px, py) {
4615
// Inverse rotation of pixel coordinates
4616
px = px - cx;
4617
py = py - cy;
4618
var px_ = Math.cos(-rr) * px - Math.sin(-rr) * py;
4619
var py_ = Math.sin(-rr) * px + Math.cos(-rr) * py;
4620
px = px_ + cx;
4621
py = py_ + cy;
4622
4623
var dx = (cx - px)/rx;
4624
var dy = (cy - py)/ry;
4625
4626
if ( Math.abs(Math.sqrt( dx*dx + dy*dy ) - 1) < VIA_ELLIPSE_EDGE_TOL ) {
4627
var theta = Math.atan2( py - cy, px - cx );
4628
if ( Math.abs(theta - (Math.PI/2)) < VIA_THETA_TOL ||
4629
Math.abs(theta + (Math.PI/2)) < VIA_THETA_TOL) {
4630
return 5;
4631
}
4632
if ( Math.abs(theta) < VIA_THETA_TOL ||
4633
Math.abs(Math.abs(theta) - Math.PI) < VIA_THETA_TOL) {
4634
return 6;
4635
}
4636
} else {
4637
return 0;
4638
}
4639
}
4640
4641
function is_on_polygon_vertex(all_points_x, all_points_y, px, py) {
4642
var i, n;
4643
n = all_points_x.length;
4644
4645
for ( i = 0; i < n; ++i ) {
4646
if ( Math.abs(all_points_x[i] - px) < VIA_POLYGON_VERTEX_MATCH_TOL &&
4647
Math.abs(all_points_y[i] - py) < VIA_POLYGON_VERTEX_MATCH_TOL ) {
4648
return (VIA_POLYGON_RESIZE_VERTEX_OFFSET+i);
4649
}
4650
}
4651
return 0;
4652
}
4653
4654
function is_on_polygon_edge(all_points_x, all_points_y, px, py) {
4655
var i, n, di, d;
4656
n = all_points_x.length;
4657
d = [];
4658
for ( i = 0; i < n - 1; ++i ) {
4659
di = dist_to_line(px, py, all_points_x[i], all_points_y[i], all_points_x[i+1], all_points_y[i+1]);
4660
d.push(di);
4661
}
4662
// closing edge
4663
di = dist_to_line(px, py, all_points_x[n-1], all_points_y[n-1], all_points_x[0], all_points_y[0]);
4664
d.push(di);
4665
4666
var smallest_value = d[0];
4667
var smallest_index = 0;
4668
n = d.length;
4669
for ( i = 1; i < n; ++i ) {
4670
if ( d[i] < smallest_value ) {
4671
smallest_value = d[i];
4672
smallest_index = i;
4673
}
4674
}
4675
if ( smallest_value < VIA_POLYGON_VERTEX_MATCH_TOL ) {
4676
return (VIA_POLYGON_RESIZE_VERTEX_OFFSET + smallest_index);
4677
} else {
4678
return 0;
4679
}
4680
}
4681
4682
function 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
4684
var rect = {};
4685
if( x1 < x2 ) {
4686
rect.x1 = x1;
4687
rect.x2 = x2;
4688
} else {
4689
rect.x1 = x2;
4690
rect.x2 = x1;
4691
}
4692
if ( y1 < y2 ) {
4693
rect.y1 = y1;
4694
rect.y2 = y2;
4695
} else {
4696
rect.y1 = y2;
4697
rect.y2 = y1;
4698
}
4699
4700
if ( x >= rect.x1 && x <= rect.x2 && y >= rect.y1 && y <= rect.y2 ) {
4701
return true;
4702
} else {
4703
return false;
4704
}
4705
}
4706
4707
function dist_to_line(x, y, x1, y1, x2, y2) {
4708
if ( is_point_inside_bounding_box(x, y, x1, y1, x2, y2) ) {
4709
var dy = y2 - y1;
4710
var dx = x2 - x1;
4711
var nr = Math.abs( dy*x - dx*y + x2*y1 - y2*x1 );
4712
var dr = Math.sqrt( dx*dx + dy*dy );
4713
var dist = nr / dr;
4714
return Math.round(dist);
4715
} else {
4716
return Number.MAX_SAFE_INTEGER;
4717
}
4718
}
4719
4720
function 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
4724
if ( d[0] > d[2] ) {
4725
// swap
4726
var t = d[0];
4727
d[0] = d[2];
4728
d[2] = t;
4729
}
4730
4731
if ( d[1] > d[3] ) {
4732
// swap
4733
var t = d[1];
4734
d[1] = d[3];
4735
d[3] = t;
4736
}
4737
}
4738
4739
function 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 )
4742
if (preserve_aspect_ratio) {
4743
switch(corner_id) {
4744
case 1: // Fall-through // top-left
4745
case 3: // bottom-right
4746
var dx = d[2] - d[0];
4747
var dy = d[3] - d[1];
4748
var norm = Math.sqrt( dx*dx + dy*dy );
4749
var nx = dx / norm; // x component of unit vector along the diagonal of rect
4750
var ny = dy / norm; // y component
4751
var proj = (x - d[0]) * nx + (y - d[1]) * ny;
4752
var proj_x = nx * proj;
4753
var proj_y = ny * proj;
4754
// constrain (mx,my) to lie on a line connecting (x0,y0) and (x1,y1)
4755
x = Math.round( d[0] + proj_x );
4756
y = Math.round( d[1] + proj_y );
4757
break;
4758
4759
case 2: // Fall-through // top-right
4760
case 4: // bottom-left
4761
var dx = d[2] - d[0];
4762
var dy = d[1] - d[3];
4763
var norm = Math.sqrt( dx*dx + dy*dy );
4764
var nx = dx / norm; // x component of unit vector along the diagonal of rect
4765
var ny = dy / norm; // y component
4766
var proj = (x - d[0]) * nx + (y - d[3]) * ny;
4767
var proj_x = nx * proj;
4768
var proj_y = ny * proj;
4769
// constrain (mx,my) to lie on a line connecting (x0,y0) and (x1,y1)
4770
x = Math.round( d[0] + proj_x );
4771
y = Math.round( d[3] + proj_y );
4772
break;
4773
}
4774
}
4775
4776
switch(corner_id) {
4777
case 1: // top-left
4778
d[0] = x;
4779
d[1] = y;
4780
break;
4781
4782
case 3: // bottom-right
4783
d[2] = x;
4784
d[3] = y;
4785
break;
4786
4787
case 2: // top-right
4788
d[2] = x;
4789
d[1] = y;
4790
break;
4791
4792
case 4: // bottom-left
4793
d[0] = x;
4794
d[3] = y;
4795
break;
4796
4797
case 5: // top-middle
4798
d[1] = y;
4799
break;
4800
4801
case 6: // right-middle
4802
d[2] = x;
4803
break;
4804
4805
case 7: // bottom-middle
4806
d[3] = y;
4807
break;
4808
4809
case 8: // left-middle
4810
d[0] = x;
4811
break;
4812
}
4813
}
4814
4815
function _via_update_ui_components() {
4816
if ( ! _via_current_image_loaded ) {
4817
return;
4818
}
4819
4820
show_message('Updating user interface components.');
4821
switch(_via_display_area_content_name) {
4822
case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:
4823
image_grid_set_content_panel_height_fixed();
4824
image_grid_set_content_to_current_group();
4825
break;
4826
case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE:
4827
if ( !_via_is_window_resized && _via_current_image_loaded ) {
4828
_via_is_window_resized = true;
4829
_via_show_img(_via_image_index);
4830
4831
if (_via_is_canvas_zoomed) {
4832
reset_zoom_level();
4833
}
4834
}
4835
break;
4836
}
4837
}
4838
4839
//
4840
// Shortcut key handlers
4841
//
4842
function _via_window_keydown_handler(e) {
4843
if ( 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
4851
function _via_handle_global_keydown_event(e) {
4852
// zoom
4853
if (_via_current_image_loaded) {
4854
if ( e.key === "+") {
4855
zoom_in();
4856
return;
4857
}
4858
4859
if ( e.key === "=") {
4860
reset_zoom_level();
4861
return;
4862
}
4863
4864
if ( e.key === "-") {
4865
zoom_out();
4866
return;
4867
}
4868
}
4869
4870
if ( e.key === 'ArrowRight' || e.key === 'n') {
4871
move_to_next_image();
4872
e.preventDefault();
4873
return;
4874
}
4875
if ( e.key === 'ArrowLeft' || e.key === 'p') {
4876
move_to_prev_image();
4877
e.preventDefault();
4878
return;
4879
}
4880
4881
if ( e.key === 'ArrowUp' ) {
4882
region_visualisation_update('region_label', '__via_region_id__', 1);
4883
e.preventDefault();
4884
return;
4885
}
4886
4887
if ( e.key === 'ArrowDown' ) {
4888
region_visualisation_update('region_color', '__via_default_region_color__', -1);
4889
e.preventDefault();
4890
return;
4891
}
4892
4893
4894
if ( e.key === 'Home') {
4895
show_first_image();
4896
e.preventDefault();
4897
return;
4898
}
4899
if ( e.key === 'End') {
4900
show_last_image();
4901
e.preventDefault();
4902
return;
4903
}
4904
if ( e.key === 'PageDown') {
4905
jump_to_next_image_block();
4906
e.preventDefault();
4907
return;
4908
}
4909
if ( e.key === 'PageUp') {
4910
jump_to_prev_image_block();
4911
e.preventDefault();
4912
return;
4913
}
4914
4915
if ( e.key === 'a' ) {
4916
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
4917
// select all in image grid
4918
image_grid_group_toggle_select_all();
4919
}
4920
}
4921
4922
if ( e.key === 'Escape' ) {
4923
e.preventDefault();
4924
if ( _via_is_loading_current_image ) {
4925
_via_cancel_current_image_loading();
4926
}
4927
4928
if ( _via_is_user_resizing_region ) {
4929
// cancel region resizing action
4930
_via_is_user_resizing_region = false;
4931
}
4932
4933
if ( _via_is_region_selected ) {
4934
// clear all region selections
4935
_via_is_region_selected = false;
4936
_via_user_sel_region_id = -1;
4937
toggle_all_regions_selection(false);
4938
}
4939
4940
if ( _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
4945
if ( _via_is_user_drawing_region ) {
4946
_via_is_user_drawing_region = false;
4947
}
4948
4949
if ( _via_is_user_resizing_region ) {
4950
_via_is_user_resizing_region = false
4951
}
4952
4953
if ( _via_is_user_moving_region ) {
4954
_via_is_user_moving_region = false
4955
}
4956
4957
_via_redraw_reg_canvas();
4958
return;
4959
}
4960
4961
if ( e.key === ' ' ) { // Space key
4962
if ( e.ctrlKey ) {
4963
annotation_editor_toggle_on_image_editor();
4964
} else {
4965
annotation_editor_toggle_all_regions_editor();
4966
}
4967
e.preventDefault();
4968
return;
4969
}
4970
4971
if ( e.key === 'F1' ) { // F1 for help
4972
set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_GETTING_STARTED);
4973
e.preventDefault();
4974
return;
4975
}
4976
if ( e.key === 'F2' ) { // F2 for about
4977
set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.PAGE_ABOUT);
4978
e.preventDefault();
4979
return;
4980
}
4981
}
4982
4983
function _via_reg_canvas_keyup_handler(e) {
4984
if ( e.key === 'Control' ) {
4985
_via_is_ctrl_pressed = false;
4986
}
4987
}
4988
4989
function _via_reg_canvas_keydown_handler(e) {
4990
if ( e.key === 'Control' ) {
4991
_via_is_ctrl_pressed = true;
4992
}
4993
4994
if (_via_current_image_loaded) {
4995
if ( e.key === 'Enter' ) {
4996
if ( _via_current_shape === VIA_REGION_SHAPE.POLYLINE ||
4997
_via_current_shape === VIA_REGION_SHAPE.POLYGON) {
4998
_via_polyshape_finish_drawing();
4999
}
5000
}
5001
if ( e.key === 'Backspace' ) {
5002
if ( _via_current_shape === VIA_REGION_SHAPE.POLYLINE ||
5003
_via_current_shape === VIA_REGION_SHAPE.POLYGON) {
5004
_via_polyshape_delete_last_vertex();
5005
}
5006
}
5007
5008
if ( e.key === 'a' ) {
5009
sel_all_regions();
5010
e.preventDefault();
5011
return;
5012
}
5013
5014
if ( e.key === 'c' ) {
5015
if (_via_is_region_selected ||
5016
_via_is_all_region_selected) {
5017
copy_sel_regions();
5018
}
5019
e.preventDefault();
5020
return;
5021
}
5022
5023
if ( e.key === 'v' ) {
5024
paste_sel_regions_in_current_image();
5025
e.preventDefault();
5026
return;
5027
}
5028
5029
if ( e.key === 'b' ) {
5030
toggle_region_boundary_visibility();
5031
e.preventDefault();
5032
return;
5033
}
5034
5035
if ( e.key === 'l' ) {
5036
toggle_region_id_visibility();
5037
e.preventDefault();
5038
return;
5039
}
5040
5041
if ( e.key === 'd' ) {
5042
if ( _via_is_region_selected ||
5043
_via_is_all_region_selected ) {
5044
del_sel_regions();
5045
}
5046
e.preventDefault();
5047
return;
5048
}
5049
5050
if ( _via_is_region_selected ) {
5051
if ( e.key === 'ArrowRight' ||
5052
e.key === 'ArrowLeft' ||
5053
e.key === 'ArrowDown' ||
5054
e.key === 'ArrowUp' ) {
5055
var del = 1;
5056
if ( e.shiftKey ) {
5057
del = 10;
5058
}
5059
var move_x = 0;
5060
var move_y = 0;
5061
switch( e.key ) {
5062
case 'ArrowLeft':
5063
move_x = -del;
5064
break;
5065
case 'ArrowUp':
5066
move_y = -del;
5067
break;
5068
case 'ArrowRight':
5069
move_x = del;
5070
break;
5071
case 'ArrowDown':
5072
move_y = del;
5073
break;
5074
}
5075
_via_move_selected_regions(move_x, move_y);
5076
_via_redraw_reg_canvas();
5077
e.preventDefault();
5078
return;
5079
}
5080
}
5081
}
5082
_via_handle_global_keydown_event(e);
5083
}
5084
5085
function _via_polyshape_finish_drawing() {
5086
if ( _via_is_user_drawing_polygon ) {
5087
// double click is used to indicate completion of
5088
// polygon or polyline drawing action
5089
var new_region_id = _via_current_polygon_region_id;
5090
var new_region_shape = _via_current_shape;
5091
5092
var npts = _via_canvas_regions[new_region_id].shape_attributes['all_points_x'].length;
5093
if ( npts <=2 && new_region_shape === VIA_REGION_SHAPE.POLYGON ) {
5094
show_message('For a polygon, you must define at least 3 points. ' +
5095
'Press [Esc] to cancel drawing operation.!');
5096
return;
5097
}
5098
if ( npts <=1 && new_region_shape === VIA_REGION_SHAPE.POLYLINE ) {
5099
show_message('A polyline must have at least 2 points. ' +
5100
'Press [Esc] to cancel drawing operation.!');
5101
return;
5102
}
5103
5104
var 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);
5111
select_only_region(new_region_id); // select new region
5112
set_region_annotations_to_default_value( new_region_id );
5113
annotation_editor_add_row( new_region_id );
5114
annotation_editor_scroll_to_row( new_region_id );
5115
5116
_via_redraw_reg_canvas();
5117
_via_reg_canvas.focus();
5118
}
5119
return;
5120
}
5121
5122
function _via_polyshape_delete_last_vertex() {
5123
if ( _via_is_user_drawing_polygon ) {
5124
var npts = _via_canvas_regions[_via_current_polygon_region_id].shape_attributes['all_points_x'].length;
5125
if ( 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
5135
function _via_polyshape_add_new_polyshape(img_id, region_shape, region_id) {
5136
// add all polygon points stored in _via_canvas_regions[]
5137
var all_points_x = _via_canvas_regions[region_id].shape_attributes['all_points_x'].slice(0);
5138
var all_points_y = _via_canvas_regions[region_id].shape_attributes['all_points_y'].slice(0);
5139
5140
var canvas_all_points_x = [];
5141
var canvas_all_points_y = [];
5142
var n = all_points_x.length;
5143
var i;
5144
for ( i = 0; i < n; ++i ) {
5145
all_points_x[i] = Math.round( all_points_x[i] * _via_canvas_scale );
5146
all_points_y[i] = Math.round( all_points_y[i] * _via_canvas_scale );
5147
5148
canvas_all_points_x[i] = Math.round( all_points_x[i] / _via_canvas_scale );
5149
canvas_all_points_y[i] = Math.round( all_points_y[i] / _via_canvas_scale );
5150
}
5151
5152
var polygon_region = new file_region();
5153
polygon_region.shape_attributes['name'] = region_shape;
5154
polygon_region.shape_attributes['all_points_x'] = all_points_x;
5155
polygon_region.shape_attributes['all_points_y'] = all_points_y;
5156
_via_img_metadata[img_id].regions[region_id] = polygon_region;
5157
5158
// update canvas
5159
if ( 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
5166
function del_sel_regions() {
5167
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5168
return;
5169
}
5170
5171
if ( !_via_current_image_loaded ) {
5172
show_message('First load some images!');
5173
return;
5174
}
5175
5176
var del_region_count = 0;
5177
if ( _via_is_all_region_selected ) {
5178
del_region_count = _via_canvas_regions.length;
5179
_via_canvas_regions.splice(0);
5180
_via_img_metadata[_via_image_id].regions.splice(0);
5181
} else {
5182
var sorted_sel_reg_id = [];
5183
for ( var i = 0; i < _via_canvas_regions.length; ++i ) {
5184
if ( _via_region_selected_flag[i] ) {
5185
sorted_sel_reg_id.push(i);
5186
_via_region_selected_flag[i] = false;
5187
}
5188
}
5189
sorted_sel_reg_id.sort( function(a,b) {
5190
return (b-a);
5191
});
5192
for ( 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);
5195
del_region_count += 1;
5196
}
5197
5198
if ( 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
5207
if ( _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();
5214
annotation_editor_show();
5215
5216
show_message('Deleted ' + del_region_count + ' selected regions');
5217
}
5218
5219
function sel_all_regions() {
5220
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5221
image_grid_group_toggle_select_all();
5222
return;
5223
}
5224
5225
if (!_via_current_image_loaded) {
5226
show_message('First load some images!');
5227
return;
5228
}
5229
5230
toggle_all_regions_selection(true);
5231
_via_is_all_region_selected = true;
5232
_via_redraw_reg_canvas();
5233
}
5234
5235
function copy_sel_regions() {
5236
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5237
return;
5238
}
5239
5240
if (!_via_current_image_loaded) {
5241
show_message('First load some images!');
5242
return;
5243
}
5244
5245
if (_via_is_region_selected ||
5246
_via_is_all_region_selected) {
5247
_via_copied_image_regions.splice(0);
5248
for ( var i = 0; i < _via_img_metadata[_via_image_id].regions.length; ++i ) {
5249
var img_region = _via_img_metadata[_via_image_id].regions[i];
5250
var canvas_region = _via_canvas_regions[i];
5251
if ( _via_region_selected_flag[i] ) {
5252
_via_copied_image_regions.push( clone_image_region(img_region) );
5253
}
5254
}
5255
show_message('Copied ' + _via_copied_image_regions.length +
5256
' selected regions. Press Ctrl + v to paste');
5257
} else {
5258
show_message('Select a region first!');
5259
}
5260
}
5261
5262
function paste_sel_regions_in_current_image() {
5263
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5264
return;
5265
}
5266
5267
if ( !_via_current_image_loaded ) {
5268
show_message('First load some images!');
5269
return;
5270
}
5271
5272
if ( _via_copied_image_regions.length ) {
5273
var pasted_reg_count = 0;
5274
for ( var i = 0; i < _via_copied_image_regions.length; ++i ) {
5275
// ensure copied the regions are within this image's boundaries
5276
var bbox = get_region_bounding_box( _via_copied_image_regions[i] );
5277
if (bbox[2] < _via_current_image_width &&
5278
bbox[3] < _via_current_image_height) {
5279
var r = clone_image_region(_via_copied_image_regions[i]);
5280
_via_img_metadata[_via_image_id].regions.push(r);
5281
5282
pasted_reg_count += 1;
5283
}
5284
}
5285
_via_load_canvas_regions();
5286
var discarded_reg_count = _via_copied_image_regions.length - pasted_reg_count;
5287
show_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 {
5292
show_message('To paste a region, you first need to select a region and copy it!');
5293
}
5294
}
5295
5296
function paste_to_multiple_images_with_confirm() {
5297
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5298
return;
5299
}
5300
5301
if ( _via_copied_image_regions.length === 0 ) {
5302
show_message('First copy some regions!');
5303
return;
5304
}
5305
5306
var config = {'title':'Paste Regions to Multiple Images' };
5307
var 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
5314
invoke_with_user_inputs(paste_to_multiple_images_confirmed, input, config);
5315
}
5316
5317
function 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;
5320
var intersect = generate_img_index_list(input);
5321
var i;
5322
var total_pasted_region_count = 0;
5323
for ( i = 0; i < intersect.length; i++ ) {
5324
total_pasted_region_count += paste_regions( intersect[i] );
5325
}
5326
5327
show_message('Pasted [' + total_pasted_region_count + '] regions ' +
5328
'in ' + intersect.length + ' images');
5329
5330
if ( intersect.includes(_via_image_index) ) {
5331
_via_load_canvas_regions();
5332
_via_redraw_reg_canvas();
5333
_via_reg_canvas.focus();
5334
}
5335
user_input_default_cancel_handler();
5336
}
5337
5338
function paste_regions(img_index) {
5339
var pasted_reg_count = 0;
5340
if ( _via_copied_image_regions.length ) {
5341
var img_id = _via_image_id_list[img_index];
5342
var i;
5343
for ( i = 0; i < _via_copied_image_regions.length; ++i ) {
5344
var r = clone_image_region(_via_copied_image_regions[i]);
5345
_via_img_metadata[img_id].regions.push(r);
5346
5347
pasted_reg_count += 1;
5348
}
5349
}
5350
return pasted_reg_count;
5351
}
5352
5353
5354
function del_sel_regions_with_confirm() {
5355
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5356
return;
5357
}
5358
5359
if ( _via_copied_image_regions.length === 0 ) {
5360
show_message('First copy some regions!');
5361
return;
5362
}
5363
5364
var prev_next_count, img_index_list, regex;
5365
if ( _via_paste_to_multiple_images_input ) {
5366
prev_next_count = _via_paste_to_multiple_images_input.prev_next_count.value;
5367
img_index_list = _via_paste_to_multiple_images_input.img_index_list.value;
5368
regex = _via_paste_to_multiple_images_input.regex.value;
5369
}
5370
5371
var config = {'title':'Undo Regions Pasted to Multiple Images' };
5372
var 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
5378
invoke_with_user_inputs(del_sel_regions_confirmed, input, config);
5379
}
5380
5381
function del_sel_regions_confirmed(input) {
5382
user_input_default_cancel_handler();
5383
var intersect = generate_img_index_list(input);
5384
var i;
5385
var total_deleted_region_count = 0;
5386
for ( i = 0; i < intersect.length; i++ ) {
5387
total_deleted_region_count += delete_regions( intersect[i] );
5388
}
5389
5390
show_message('Deleted [' + total_deleted_region_count + '] regions ' +
5391
'in ' + intersect.length + ' images');
5392
5393
if ( intersect.includes(_via_image_index) ) {
5394
_via_load_canvas_regions();
5395
_via_redraw_reg_canvas();
5396
_via_reg_canvas.focus();
5397
}
5398
}
5399
5400
function delete_regions(img_index) {
5401
var del_region_count = 0;
5402
if ( _via_copied_image_regions.length ) {
5403
var img_id = _via_image_id_list[img_index];
5404
var i;
5405
for ( i = 0; i < _via_copied_image_regions.length; ++i ) {
5406
var copied_region_shape_str = JSON.stringify(_via_copied_image_regions[i].shape_attributes);
5407
var j;
5408
// start from last region in order to delete the last pasted region
5409
for ( j = _via_img_metadata[img_id].regions.length-1; j >= 0; --j ) {
5410
if ( 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);
5412
del_region_count += 1;
5413
break; // delete only one matching region
5414
}
5415
}
5416
}
5417
}
5418
return del_region_count;
5419
}
5420
5421
function show_first_image() {
5422
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5423
if ( _via_image_grid_group_var.length ) {
5424
image_grid_group_prev( { 'value':0 } ); // simulate button click
5425
} else {
5426
show_message('First, create groups by selecting items from "Group by" dropdown list');
5427
}
5428
return;
5429
}
5430
5431
if (_via_img_count > 0) {
5432
_via_show_img( _via_img_fn_list_img_index_list[0] );
5433
}
5434
}
5435
5436
function show_last_image() {
5437
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5438
if ( _via_image_grid_group_var.length ) {
5439
image_grid_group_prev( { 'value':_via_image_grid_group_var.length-1 } ); // simulate button click
5440
} else {
5441
show_message('First, create groups by selecting items from "Group by" dropdown list');
5442
}
5443
return;
5444
}
5445
5446
if (_via_img_count > 0) {
5447
var 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
5452
function jump_image_block_get_count() {
5453
var n = _via_img_fn_list_img_index_list.length;
5454
if ( n < 20 ) {
5455
return 2;
5456
}
5457
if ( n < 100 ) {
5458
return 10;
5459
}
5460
if ( n < 1000 ) {
5461
return 25;
5462
}
5463
if ( n < 5000 ) {
5464
return 50;
5465
}
5466
if ( n < 10000 ) {
5467
return 100;
5468
}
5469
if ( n < 50000 ) {
5470
return 500;
5471
}
5472
5473
return Math.round( n / 50 );
5474
}
5475
5476
function jump_to_next_image_block() {
5477
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5478
return;
5479
}
5480
5481
var jump_count = jump_image_block_get_count();
5482
if ( jump_count > 1 ) {
5483
var current_img_index = _via_image_index;
5484
if ( _via_img_fn_list_img_index_list.includes( current_img_index ) ) {
5485
var list_index = _via_img_fn_list_img_index_list.indexOf( current_img_index );
5486
var next_list_index = list_index + jump_count;
5487
if ( (next_list_index + 1) > _via_img_fn_list_img_index_list.length ) {
5488
next_list_index = 0;
5489
}
5490
var next_img_index = _via_img_fn_list_img_index_list[next_list_index];
5491
_via_show_img(next_img_index);
5492
}
5493
} else {
5494
move_to_next_image();
5495
}
5496
}
5497
5498
function jump_to_prev_image_block() {
5499
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5500
return;
5501
}
5502
5503
var jump_count = jump_image_block_get_count();
5504
if ( jump_count > 1 ) {
5505
var current_img_index = _via_image_index;
5506
if ( _via_img_fn_list_img_index_list.includes( current_img_index ) ) {
5507
var list_index = _via_img_fn_list_img_index_list.indexOf( current_img_index );
5508
var prev_list_index = list_index - jump_count;
5509
if ( prev_list_index < 0 ) {
5510
prev_list_index = _via_img_fn_list_img_index_list.length - 1;
5511
}
5512
var prev_img_index = _via_img_fn_list_img_index_list[prev_list_index];
5513
_via_show_img(prev_img_index);
5514
}
5515
} else {
5516
move_to_prev_image();
5517
}
5518
}
5519
5520
function move_to_prev_image() {
5521
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5522
if ( _via_image_grid_group_var.length ) {
5523
var last_group_index = _via_image_grid_group_var.length - 1;
5524
image_grid_group_prev( { 'value':last_group_index } ); // simulate button click
5525
} else {
5526
show_message('First, create groups by selecting items from "Group by" dropdown list');
5527
}
5528
return;
5529
}
5530
5531
if (_via_img_count > 0) {
5532
var current_img_index = _via_image_index;
5533
if ( _via_img_fn_list_img_index_list.includes( current_img_index ) ) {
5534
var list_index = _via_img_fn_list_img_index_list.indexOf( current_img_index );
5535
var next_list_index = list_index - 1;
5536
if ( next_list_index === -1 ) {
5537
next_list_index = _via_img_fn_list_img_index_list.length - 1;
5538
}
5539
var next_img_index = _via_img_fn_list_img_index_list[next_list_index];
5540
_via_show_img(next_img_index);
5541
} else {
5542
if ( _via_img_fn_list_img_index_list.length === 0 ) {
5543
show_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
5549
if (typeof _via_hook_prev_image === 'function') {
5550
_via_hook_prev_image(current_img_index);
5551
}
5552
}
5553
}
5554
5555
function move_to_next_image() {
5556
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5557
if ( _via_image_grid_group_var.length ) {
5558
var last_group_index = _via_image_grid_group_var.length - 1;
5559
image_grid_group_next( { 'value':last_group_index } ); // simulate button click
5560
} else {
5561
show_message('First, create groups by selecting items from "Group by" dropdown list');
5562
}
5563
return;
5564
}
5565
5566
if (_via_img_count > 0) {
5567
var current_img_index = _via_image_index;
5568
if ( _via_img_fn_list_img_index_list.includes( current_img_index ) ) {
5569
var list_index = _via_img_fn_list_img_index_list.indexOf( current_img_index );
5570
var next_list_index = list_index + 1;
5571
if ( next_list_index === _via_img_fn_list_img_index_list.length ) {
5572
next_list_index = 0;
5573
}
5574
var next_img_index = _via_img_fn_list_img_index_list[next_list_index];
5575
_via_show_img(next_img_index);
5576
} else {
5577
if ( _via_img_fn_list_img_index_list.length === 0 ) {
5578
show_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
5584
if (typeof _via_hook_next_image === 'function') {
5585
_via_hook_next_image(current_img_index);
5586
}
5587
}
5588
}
5589
5590
function set_zoom(zoom_level_index) {
5591
if ( 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
5599
var zoom_scale = VIA_CANVAS_ZOOM_LEVELS[_via_canvas_zoom_level_index];
5600
set_all_canvas_scale(zoom_scale);
5601
var canvas_w = ( _via_current_image.naturalWidth * zoom_scale ) / _via_canvas_scale_without_zoom;
5602
var canvas_h = ( _via_current_image.naturalHeight * zoom_scale ) / _via_canvas_scale_without_zoom;
5603
set_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
5607
if ( zoom_scale === 1 ) {
5608
VIA_REGION_POINT_RADIUS = VIA_REGION_POINT_RADIUS_DEFAULT;
5609
} else {
5610
if ( zoom_scale > 1 ) {
5611
VIA_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();
5618
update_vertical_space();
5619
}
5620
5621
function reset_zoom_level() {
5622
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5623
image_grid_image_size_reset();
5624
show_message('Zoom reset');
5625
return;
5626
}
5627
5628
if (!_via_current_image_loaded) {
5629
show_message('First load some images!');
5630
return;
5631
}
5632
5633
if (_via_is_canvas_zoomed) {
5634
set_zoom(VIA_CANVAS_DEFAULT_ZOOM_LEVEL_INDEX);
5635
show_message('Zoom reset');
5636
} else {
5637
show_message('Cannot reset zoom because image zoom has not been applied!');
5638
}
5639
update_vertical_space();
5640
}
5641
5642
function zoom_in() {
5643
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5644
image_grid_image_size_increase();
5645
show_message('Increased size of images shown in image grid');
5646
return;
5647
}
5648
5649
if (!_via_current_image_loaded) {
5650
show_message('First load some images!');
5651
return;
5652
}
5653
5654
if ( _via_is_user_drawing_polygon || _via_is_user_drawing_region ) {
5655
return;
5656
}
5657
5658
if (_via_canvas_zoom_level_index === (VIA_CANVAS_ZOOM_LEVELS.length-1)) {
5659
show_message('Further zoom-in not possible');
5660
} else {
5661
var new_zoom_level_index = _via_canvas_zoom_level_index + 1;
5662
set_zoom( new_zoom_level_index );
5663
show_message('Zoomed in to level ' + VIA_CANVAS_ZOOM_LEVELS[_via_canvas_zoom_level_index] + 'X');
5664
}
5665
}
5666
5667
function zoom_out() {
5668
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5669
image_grid_image_size_decrease();
5670
show_message('Reduced size of images shown in image grid');
5671
return;
5672
}
5673
5674
if (!_via_current_image_loaded) {
5675
show_message('First load some images!');
5676
return;
5677
}
5678
5679
if ( _via_is_user_drawing_polygon || _via_is_user_drawing_region ) {
5680
return;
5681
}
5682
5683
if (_via_canvas_zoom_level_index === 0) {
5684
show_message('Further zoom-out not possible');
5685
} else {
5686
var new_zoom_level_index = _via_canvas_zoom_level_index - 1;
5687
set_zoom( new_zoom_level_index );
5688
show_message('Zoomed out to level ' + VIA_CANVAS_ZOOM_LEVELS[_via_canvas_zoom_level_index] + 'X');
5689
}
5690
}
5691
5692
function toggle_region_boundary_visibility() {
5693
if ( _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
5699
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
5700
if ( _via_settings.ui.image_grid.show_region_shape ) {
5701
_via_settings.ui.image_grid.show_region_shape = false;
5702
document.getElementById('image_grid_content_rshape').innerHTML = '';
5703
} else {
5704
_via_settings.ui.image_grid.show_region_shape = true;
5705
image_grid_page_show_all_regions();
5706
}
5707
}
5708
}
5709
5710
function 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
5716
function toggle_region_info_visibility() {
5717
var elem = document.getElementById('region_info');
5718
// toggle between displaying and not displaying
5719
if ( elem.classList.contains('display_none') ) {
5720
elem.classList.remove('display_none');
5721
_via_is_region_info_visible = true;
5722
} else {
5723
elem.classList.add('display_none');
5724
_via_is_region_info_visible = false;
5725
}
5726
}
5727
5728
//
5729
// Mouse wheel event listener
5730
//
5731
function _via_reg_canvas_mouse_wheel_listener(e) {
5732
if (!_via_current_image_loaded) {
5733
return;
5734
}
5735
5736
if ( e.ctrlKey ) {
5737
// perform zoom
5738
if (e.deltaY < 0) {
5739
zoom_in();
5740
} else {
5741
zoom_out();
5742
}
5743
e.preventDefault();
5744
}
5745
}
5746
5747
function region_visualisation_update(type, default_id, next_offset) {
5748
var attr_list = [ default_id ];
5749
attr_list = attr_list.concat(Object.keys(_via_attributes['region']));
5750
var n = attr_list.length;
5751
var current_index = attr_list.indexOf(_via_settings.ui.image[type]);
5752
var new_index;
5753
if ( current_index !== -1 ) {
5754
new_index = current_index + next_offset;
5755
5756
if ( new_index < 0 ) {
5757
new_index = n + new_index;
5758
}
5759
if ( new_index >= n ) {
5760
new_index = new_index - n;
5761
}
5762
switch(type) {
5763
case 'region_label':
5764
_via_settings.ui.image.region_label = attr_list[new_index];
5765
_via_redraw_reg_canvas();
5766
break;
5767
case '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
5773
var type_str = type.replace('_', ' ');
5774
if ( _via_settings.ui.image[type].startsWith('__via') ) {
5775
show_message(type_str + ' cleared');
5776
} else {
5777
show_message(type_str + ' set to region attribute [' + _via_settings.ui.image[type] + ']');
5778
}
5779
}
5780
}
5781
5782
//
5783
// left sidebar toolbox maintainer
5784
//
5785
function leftsidebar_toggle() {
5786
var leftsidebar = document.getElementById('leftsidebar');
5787
if ( leftsidebar.style.display === 'none' ) {
5788
leftsidebar.style.display = 'table-cell';
5789
document.getElementById('leftsidebar_collapse_panel').style.display = 'none';
5790
} else {
5791
leftsidebar.style.display = 'none';
5792
document.getElementById('leftsidebar_collapse_panel').style.display = 'table-cell';
5793
}
5794
_via_update_ui_components();
5795
}
5796
5797
function leftsidebar_increase_width() {
5798
var leftsidebar = document.getElementById('leftsidebar');
5799
var new_width = _via_settings.ui.leftsidebar_width + VIA_LEFTSIDEBAR_WIDTH_CHANGE;
5800
leftsidebar.style.width = new_width + 'rem';
5801
_via_settings.ui.leftsidebar_width = new_width;
5802
if ( _via_current_image_loaded ) {
5803
_via_show_img(_via_image_index);
5804
}
5805
}
5806
5807
function leftsidebar_decrease_width() {
5808
var leftsidebar = document.getElementById('leftsidebar');
5809
var new_width = _via_settings.ui.leftsidebar_width - VIA_LEFTSIDEBAR_WIDTH_CHANGE;
5810
if ( new_width >= 5 ) {
5811
leftsidebar.style.width = new_width + 'rem';
5812
_via_settings.ui.leftsidebar_width = new_width;
5813
if ( _via_current_image_loaded ) {
5814
_via_show_img(_via_image_index);
5815
}
5816
}
5817
}
5818
5819
function leftsidebar_show() {
5820
var leftsidebar = document.getElementById('leftsidebar');
5821
leftsidebar.style.display = 'table-cell';
5822
document.getElementById('leftsidebar_collapse_panel').style.display = 'none';
5823
}
5824
5825
// source: https://www.w3schools.com/howto/howto_js_accordion.asp
5826
function init_leftsidebar_accordion() {
5827
var leftsidebar = document.getElementById('leftsidebar');
5828
leftsidebar.style.width = _via_settings.ui.leftsidebar_width + 'rem';
5829
5830
var acc = document.getElementsByClassName('leftsidebar_accordion');
5831
var i;
5832
for ( i = 0; i < acc.length; ++i ) {
5833
acc[i].addEventListener('click', function() {
5834
update_vertical_space();
5835
this.classList.toggle('active');
5836
this.nextElementSibling.classList.toggle('show');
5837
5838
switch( this.innerHTML ) {
5839
case 'Attributes':
5840
update_attributes_update_panel();
5841
break;
5842
case 'Project':
5843
update_img_fn_list();
5844
break;
5845
}
5846
});
5847
}
5848
}
5849
5850
//
5851
// image filename list shown in leftsidebar panel
5852
//
5853
function is_img_fn_list_visible() {
5854
return img_fn_list_panel.classList.contains('show');
5855
}
5856
5857
function img_loading_spinbar(image_index, show) {
5858
var panel = document.getElementById('project_panel_title');
5859
if ( show ) {
5860
panel.innerHTML = 'Project <span style="margin-left:1rem;" class="loading_spinbox"></span>';
5861
} else {
5862
panel.innerHTML = 'Project';
5863
}
5864
}
5865
5866
function update_img_fn_list() {
5867
var regex = document.getElementById('img_fn_list_regex').value;
5868
var p = document.getElementById('filelist_preset_filters_list');
5869
if ( regex === '' || regex === null ) {
5870
if ( 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>');
5875
for ( 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>');
5880
img_fn_list.innerHTML = _via_img_fn_list_html.join('');
5881
img_fn_list_scroll_to_current_file();
5882
} else {
5883
// filter according to preset filters
5884
img_fn_list_onpresetfilter_select();
5885
}
5886
} else {
5887
if ( p.selectedIndex === 6 ) {
5888
// Filter By Region Attribute
5889
filter_selected_region_attribute(regex);
5890
} else {
5891
// RegExp
5892
img_fn_list_generate_html(regex);
5893
img_fn_list.innerHTML = _via_img_fn_list_html.join('');
5894
img_fn_list_scroll_to_current_file();
5895
}
5896
}
5897
}
5898
5899
function img_fn_list_onregex() {
5900
var regex = document.getElementById('img_fn_list_regex').value;
5901
var p = document.getElementById('filelist_preset_filters_list');
5902
5903
if ( p.selectedIndex === 6 ) {
5904
filter_selected_region_attribute(regex);
5905
return;
5906
}
5907
5908
img_fn_list_generate_html( regex );
5909
img_fn_list.innerHTML = _via_img_fn_list_html.join('');
5910
img_fn_list_scroll_to_current_file();
5911
5912
// select 'regex' in the predefined filter list ( Unless Filter By Attribute is Selected)
5913
if ( regex === '' ) {
5914
p.selectedIndex = 0;
5915
} else {
5916
if ( p.options[p.selectedIndex].value != 'files_region_attribute' &&
5917
p.options[p.selectedIndex].value != 'regex' ) {
5918
var i;
5919
for ( i=0; i<p.options.length; ++i) {
5920
if ( p.options[i].value === 'regex' ) {
5921
p.selectedIndex = i;
5922
break;
5923
}
5924
}
5925
}
5926
}
5927
}
5928
5929
function img_fn_list_onpresetfilter_select() {
5930
var p = document.getElementById('filelist_preset_filters_list');
5931
var filter = p.options[p.selectedIndex].value;
5932
switch(filter) {
5933
case 'all':
5934
document.getElementById('img_fn_list_regex').value = '';
5935
img_fn_list_generate_html();
5936
img_fn_list.innerHTML = _via_img_fn_list_html.join('');
5937
img_fn_list_scroll_to_current_file();
5938
break;
5939
case 'regex':
5940
img_fn_list_onregex();
5941
document.getElementById('img_fn_list_regex').focus();
5942
break;
5943
case 'files_region_attribute':
5944
img_fn_list_onregex();
5945
document.getElementById('img_fn_list_regex').focus();
5946
break;
5947
default:
5948
_via_img_fn_list_html = [];
5949
_via_img_fn_list_img_index_list = [];
5950
_via_img_fn_list_html.push('<ul>');
5951
var i;
5952
for ( i=0; i < _via_image_filename_list.length; ++i ) {
5953
var img_id = _via_image_id_list[i];
5954
var add_to_list = false;
5955
switch(filter) {
5956
case 'files_without_region':
5957
if ( _via_img_metadata[img_id].regions.length === 0 ) {
5958
add_to_list = true;
5959
}
5960
break;
5961
case 'files_missing_region_annotations':
5962
if ( is_region_annotation_missing(img_id) ) {
5963
add_to_list = true;
5964
}
5965
break;
5966
case 'files_missing_file_annotations':
5967
if ( is_file_annotation_missing(img_id) ) {
5968
add_to_list = true;
5969
}
5970
break;
5971
case 'files_error_loading':
5972
if ( _via_image_load_error[i] === true ) {
5973
add_to_list = true;
5974
}
5975
}
5976
if ( 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>');
5982
img_fn_list.innerHTML = _via_img_fn_list_html.join('');
5983
img_fn_list_scroll_to_current_file();
5984
break;
5985
}
5986
}
5987
5988
function filter_selected_region_attribute(property_entry) {
5989
const propArray = property_entry.split("=");
5990
if ( propArray.length != 2 || propArray[0] === "" || propArray[1] === "") return;
5991
var selected_attribute = propArray[0];
5992
var selected_value = propArray[1];
5993
if ( 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
5999
if ( _via_attributes.region.hasOwnProperty(selected_attribute) ) {
6000
var i;
6001
6002
for ( i=0; i < _via_image_filename_list.length; ++i ) {
6003
var img_id = _via_image_id_list[i];
6004
var add_to_list = false;
6005
var idx;
6006
6007
for ( idx = 0; idx < _via_img_metadata[img_id].regions.length; ++idx ) {
6008
if ( _via_img_metadata[img_id].regions[idx].region_attributes.hasOwnProperty(selected_attribute) ) {
6009
if ( JSON.stringify(_via_img_metadata[img_id].regions[idx].region_attributes[selected_attribute]) === JSON.stringify(selected_value) ) {
6010
add_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);
6013
break;
6014
}
6015
}
6016
}
6017
}
6018
}
6019
_via_img_fn_list_html.push('</ul>');
6020
img_fn_list.innerHTML = _via_img_fn_list_html.join('');
6021
img_fn_list_scroll_to_current_file();
6022
}
6023
6024
function is_region_annotation_missing(img_id) {
6025
var region_attribute;
6026
var i;
6027
for ( i = 0; i < _via_img_metadata[img_id].regions.length; ++i ) {
6028
for ( region_attribute in _via_attributes['region'] ) {
6029
if ( _via_img_metadata[img_id].regions[i].region_attributes.hasOwnProperty(region_attribute) ) {
6030
if ( _via_img_metadata[img_id].regions[i].region_attributes[region_attribute] === '' ) {
6031
return true;
6032
}
6033
} else {
6034
return true;
6035
}
6036
}
6037
}
6038
return false;
6039
}
6040
6041
function is_file_annotation_missing(img_id) {
6042
var file_attribute;
6043
for ( file_attribute in _via_attributes['file'] ) {
6044
if ( _via_img_metadata[img_id].file_attributes.hasOwnProperty(file_attribute) ) {
6045
if ( _via_img_metadata[img_id].file_attributes[file_attribute] === '' ) {
6046
return true;
6047
}
6048
} else {
6049
return true;
6050
}
6051
}
6052
return false;
6053
}
6054
6055
function img_fn_list_ith_entry_selected(img_index, is_selected) {
6056
if ( is_selected ) {
6057
img_fn_list_ith_entry_add_css_class(img_index, 'sel');
6058
} else {
6059
img_fn_list_ith_entry_remove_css_class(img_index, 'sel');
6060
}
6061
}
6062
6063
function img_fn_list_ith_entry_error(img_index, is_error) {
6064
if ( is_error ) {
6065
img_fn_list_ith_entry_add_css_class(img_index, 'error');
6066
} else {
6067
img_fn_list_ith_entry_remove_css_class(img_index, 'error');
6068
}
6069
}
6070
6071
function img_fn_list_ith_entry_add_css_class(img_index, classname) {
6072
var li = document.getElementById('fl' + img_index);
6073
if ( li && ! li.classList.contains(classname) ) {
6074
li.classList.add(classname);
6075
}
6076
}
6077
6078
function img_fn_list_ith_entry_remove_css_class(img_index, classname) {
6079
var li = document.getElementById('fl' + img_index);
6080
if ( li && li.classList.contains(classname) ) {
6081
li.classList.remove(classname);
6082
}
6083
}
6084
6085
function img_fn_list_clear_all_style() {
6086
var cn = document.getElementById('img_fn_list').childNodes[0].childNodes;
6087
var i, j;
6088
var n = cn.length;
6089
var nclass;
6090
for ( i = 0; i < n; ++i ) {
6091
//cn[i].classList = []; // throws error in Edge browser
6092
nclass = cn[i].classList.length;
6093
if ( nclass ) {
6094
for ( j = 0; j < nclass; ++j ) {
6095
cn[i].classList.remove( cn[i].classList.item(j) );
6096
}
6097
}
6098
}
6099
}
6100
6101
function img_fn_list_clear_css_classname(classname) {
6102
var cn = document.getElementById('img_fn_list').childNodes[0].childNodes;
6103
var i;
6104
var n = cn.length;
6105
for ( i = 0; i < n; ++i ) {
6106
if ( cn[i].classList.contains(classname) ) {
6107
cn[i].classList.remove(classname);
6108
}
6109
}
6110
}
6111
6112
function img_fn_list_ith_entry_html(i) {
6113
var htmli = '';
6114
var filename = _via_image_filename_list[i];
6115
if ( is_url(filename) ) {
6116
filename = filename.substr(0,4) + '...' + get_filename_from_url(filename);
6117
}
6118
6119
htmli += '<li id="fl' + i + '"';
6120
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
6121
if ( _via_image_grid_page_img_index_list.includes(i) ) {
6122
// highlight images being shown in image grid
6123
htmli += ' class="sel"';
6124
}
6125
6126
} else {
6127
if ( i === _via_image_index ) {
6128
// highlight the current entry
6129
htmli += ' class="sel"';
6130
}
6131
}
6132
htmli += ' onclick="jump_to_image(' + (i) + ')" title="' + _via_image_filename_list[i] + '">[' + (i+1) + '] ' + decodeURIComponent(filename) + '</li>';
6133
return htmli;
6134
}
6135
6136
function 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>');
6140
for ( var i=0; i < _via_image_filename_list.length; ++i ) {
6141
var filename = _via_image_filename_list[i];
6142
if ( 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
6150
function img_fn_list_scroll_to_current_file() {
6151
img_fn_list_scroll_to_file( _via_image_index );
6152
}
6153
6154
function img_fn_list_scroll_to_file(file_index) {
6155
if( _via_img_fn_list_img_index_list.includes(file_index) ) {
6156
var sel_file = document.getElementById( 'fl' + file_index );
6157
var panel_height = img_fn_list.clientHeight - 20;
6158
var window_top = img_fn_list.scrollTop;
6159
var window_bottom = img_fn_list.scrollTop + panel_height
6160
if ( sel_file.offsetTop > window_top ) {
6161
if ( sel_file.offsetTop > window_bottom ) {
6162
img_fn_list.scrollTop = sel_file.offsetTop;
6163
}
6164
} else {
6165
img_fn_list.scrollTop = sel_file.offsetTop - panel_height;
6166
}
6167
}
6168
}
6169
6170
function toggle_img_fn_list_visibility() {
6171
leftsidebar_show();
6172
document.getElementById('img_fn_list_panel').classList.toggle('show');
6173
document.getElementById('project_panel_title').classList.toggle('active');
6174
}
6175
6176
function toggle_attributes_editor() {
6177
leftsidebar_show();
6178
document.getElementById('attributes_editor_panel').classList.toggle('show');
6179
document.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
6184
function update_vertical_space() {
6185
var panel = document.getElementById('vertical_space');
6186
var aepanel = document.getElementById('annotation_editor_panel');
6187
panel.style.height = (aepanel.offsetHeight + 40) + 'px';
6188
}
6189
6190
//
6191
// region and file attributes update panel
6192
//
6193
function attribute_update_panel_set_active_button() {
6194
var attribute_type;
6195
for ( attribute_type in _via_attributes ) {
6196
var bid = 'button_show_' + attribute_type + '_attributes';
6197
document.getElementById(bid).classList.remove('active');
6198
}
6199
var bid = 'button_show_' + _via_attribute_being_updated + '_attributes';
6200
document.getElementById(bid).classList.add('active');
6201
}
6202
6203
function show_region_attributes_update_panel() {
6204
_via_attribute_being_updated = 'region';
6205
var rattr_list = Object.keys(_via_attributes['region']);
6206
if ( rattr_list.length ) {
6207
_via_current_attribute_id = rattr_list[0];
6208
} else {
6209
_via_current_attribute_id = '';
6210
}
6211
update_attributes_update_panel();
6212
attribute_update_panel_set_active_button();
6213
6214
}
6215
6216
function show_file_attributes_update_panel() {
6217
_via_attribute_being_updated = 'file';
6218
var fattr_list = Object.keys(_via_attributes['file']);
6219
if ( fattr_list.length ) {
6220
_via_current_attribute_id = fattr_list[0];
6221
} else {
6222
_via_current_attribute_id = '';
6223
}
6224
update_attributes_update_panel();
6225
attribute_update_panel_set_active_button();
6226
}
6227
6228
function update_attributes_name_list() {
6229
var p = document.getElementById('attributes_name_list');
6230
p.innerHTML = '';
6231
6232
var attr;
6233
for ( attr in _via_attributes[_via_attribute_being_updated] ) {
6234
var option = document.createElement('option');
6235
option.setAttribute('value', attr)
6236
option.innerHTML = attr;
6237
if ( attr === _via_current_attribute_id ) {
6238
option.setAttribute('selected', 'selected');
6239
}
6240
p.appendChild(option);
6241
}
6242
}
6243
6244
function update_attributes_update_panel() {
6245
if ( document.getElementById('attributes_editor_panel').classList.contains('show') ) {
6246
update_attributes_name_list();
6247
show_attribute_properties();
6248
show_attribute_options();
6249
}
6250
}
6251
6252
function update_attribute_properties_panel() {
6253
if ( document.getElementById('attributes_editor_panel').classList.contains('show') ) {
6254
show_attribute_properties();
6255
show_attribute_options();
6256
}
6257
}
6258
6259
function show_attribute_properties() {
6260
var attr_list = document.getElementById('attributes_name_list');
6261
document.getElementById('attribute_properties').innerHTML = '';
6262
6263
if ( attr_list.options.length === 0 ) {
6264
return;
6265
}
6266
6267
if ( typeof(_via_current_attribute_id) === 'undefined' || _via_current_attribute_id === '' ) {
6268
_via_current_attribute_id = attr_list.options[0].value;
6269
}
6270
6271
var attr_id = _via_current_attribute_id;
6272
var attr_type = _via_attribute_being_updated;
6273
var attr_input_type = _via_attributes[attr_type][attr_id].type;
6274
var attr_desc = _via_attributes[attr_type][attr_id].description;
6275
6276
attribute_property_add_input_property('Name of attribute (appears in exported annotations)',
6277
'Name',
6278
attr_id,
6279
'attribute_name');
6280
attribute_property_add_input_property('Description of attribute (shown to user during annotation session)',
6281
'Desc.',
6282
attr_desc,
6283
'attribute_description');
6284
6285
if ( attr_input_type === 'text' ) {
6286
var attr_default_value = _via_attributes[attr_type][attr_id].default_value;
6287
attribute_property_add_input_property('Default value of this attribute',
6288
'Def.',
6289
attr_default_value,
6290
'attribute_default_value');
6291
}
6292
6293
// add dropdown for type of attribute
6294
var p = document.createElement('div');
6295
p.setAttribute('class', 'property');
6296
var c0 = document.createElement('span');
6297
c0.setAttribute('title', 'Attribute type (e.g. text, checkbox, radio, etc)');
6298
c0.innerHTML = 'Type';
6299
var c1 = document.createElement('span');
6300
var c1b = document.createElement('select');
6301
c1b.setAttribute('onchange', 'attribute_property_on_update(this)');
6302
c1b.setAttribute('id', 'attribute_type');
6303
var type_id;
6304
for ( type_id in VIA_ATTRIBUTE_TYPE ) {
6305
var type = VIA_ATTRIBUTE_TYPE[type_id];
6306
var option = document.createElement('option');
6307
option.setAttribute('value', type);
6308
option.innerHTML = type;
6309
if ( attr_input_type == type ) {
6310
option.setAttribute('selected', 'selected');
6311
}
6312
c1b.appendChild(option);
6313
}
6314
c1.appendChild(c1b);
6315
p.appendChild(c0);
6316
p.appendChild(c1);
6317
document.getElementById('attribute_properties').appendChild(p);
6318
}
6319
6320
function show_attribute_options() {
6321
var attr_list = document.getElementById('attributes_name_list');
6322
document.getElementById('attribute_options').innerHTML = '';
6323
if ( attr_list.options.length === 0 ) {
6324
return;
6325
}
6326
6327
var attr_id = attr_list.value;
6328
var attr_type = _via_attributes[_via_attribute_being_updated][attr_id].type;
6329
6330
// populate additional options based on attribute type
6331
switch( attr_type ) {
6332
case VIA_ATTRIBUTE_TYPE.TEXT:
6333
// text does not have any additional properties
6334
break;
6335
case VIA_ATTRIBUTE_TYPE.IMAGE:
6336
var p = document.createElement('div');
6337
p.setAttribute('class', 'property');
6338
p.setAttribute('style', 'text-align:center');
6339
var c0 = document.createElement('span');
6340
c0.setAttribute('style', 'width:25%');
6341
c0.setAttribute('title', 'When selected, this is the value that appears in exported annotations');
6342
c0.innerHTML = 'id';
6343
var c1 = document.createElement('span');
6344
c1.setAttribute('style', 'width:60%');
6345
c1.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');
6346
c1.innerHTML = 'image url or b64';
6347
var c2 = document.createElement('span');
6348
c2.setAttribute('title', 'The default value of this attribute');
6349
c2.innerHTML = 'def.';
6350
p.appendChild(c0);
6351
p.appendChild(c1);
6352
p.appendChild(c2);
6353
document.getElementById('attribute_options').appendChild(p);
6354
6355
var options = _via_attributes[_via_attribute_being_updated][attr_id].options;
6356
var option_id;
6357
for ( option_id in options ) {
6358
var option_desc = options[option_id];
6359
6360
var option_default = _via_attributes[_via_attribute_being_updated][attr_id].default_options[option_id];
6361
attribute_property_add_option(attr_id, option_id, option_desc, option_default, attr_type);
6362
}
6363
attribute_property_add_new_entry_option(attr_id, attr_type);
6364
break;
6365
case VIA_ATTRIBUTE_TYPE.CHECKBOX: // handled by next case
6366
case VIA_ATTRIBUTE_TYPE.DROPDOWN: // handled by next case
6367
case VIA_ATTRIBUTE_TYPE.RADIO:
6368
var p = document.createElement('div');
6369
p.setAttribute('class', 'property');
6370
p.setAttribute('style', 'text-align:center');
6371
var c0 = document.createElement('span');
6372
c0.setAttribute('style', 'width:25%');
6373
c0.setAttribute('title', 'When selected, this is the value that appears in exported annotations');
6374
c0.innerHTML = 'id';
6375
var c1 = document.createElement('span');
6376
c1.setAttribute('style', 'width:60%');
6377
c1.setAttribute('title', 'This is the text shown as an option to the annotator');
6378
c1.innerHTML = 'description';
6379
var c2 = document.createElement('span');
6380
c2.setAttribute('title', 'The default value of this attribute');
6381
c2.innerHTML = 'def.';
6382
p.appendChild(c0);
6383
p.appendChild(c1);
6384
p.appendChild(c2);
6385
document.getElementById('attribute_options').appendChild(p);
6386
6387
var options = _via_attributes[_via_attribute_being_updated][attr_id].options;
6388
var option_id;
6389
for ( option_id in options ) {
6390
var option_desc = options[option_id];
6391
6392
var option_default = _via_attributes[_via_attribute_being_updated][attr_id].default_options[option_id];
6393
attribute_property_add_option(attr_id, option_id, option_desc, option_default, attr_type);
6394
}
6395
attribute_property_add_new_entry_option(attr_id, attr_type);
6396
break;
6397
default:
6398
console.log('Attribute type ' + attr_type + ' is unavailable');
6399
}
6400
}
6401
6402
function attribute_property_add_input_property(title, name, value, id) {
6403
var p = document.createElement('div');
6404
p.setAttribute('class', 'property');
6405
var c0 = document.createElement('span');
6406
c0.setAttribute('title', title);
6407
c0.innerHTML = name;
6408
var c1 = document.createElement('span');
6409
var c1b = document.createElement('input');
6410
c1b.setAttribute('onchange', 'attribute_property_on_update(this)');
6411
if ( typeof(value) !== 'undefined' ) {
6412
c1b.setAttribute('value', value);
6413
}
6414
c1b.setAttribute('id', id);
6415
c1.appendChild(c1b);
6416
p.appendChild(c0);
6417
p.appendChild(c1);
6418
6419
document.getElementById('attribute_properties').appendChild(p);
6420
}
6421
6422
function attribute_property_add_option(attr_id, option_id, option_desc, option_default, attribute_type) {
6423
var p = document.createElement('div');
6424
p.setAttribute('class', 'property');
6425
var c0 = document.createElement('span');
6426
var c0b = document.createElement('input');
6427
c0b.setAttribute('type', 'text');
6428
c0b.setAttribute('value', option_id);
6429
c0b.setAttribute('title', option_id);
6430
c0b.setAttribute('onchange', 'attribute_property_on_option_update(this)');
6431
c0b.setAttribute('id', '_via_attribute_option_id_' + option_id);
6432
6433
var c1 = document.createElement('span');
6434
var c1b = document.createElement('input');
6435
c1b.setAttribute('type', 'text');
6436
6437
if ( attribute_type === VIA_ATTRIBUTE_TYPE.IMAGE ) {
6438
var option_desc_info = option_desc.length + ' bytes of base64 image data';
6439
c1b.setAttribute('value', option_desc_info);
6440
c1b.setAttribute('title', 'To update, copy and paste base64 image data in this text box');
6441
} else {
6442
c1b.setAttribute('value', option_desc);
6443
c1b.setAttribute('title', option_desc);
6444
}
6445
c1b.setAttribute('onchange', 'attribute_property_on_option_update(this)');
6446
c1b.setAttribute('id', '_via_attribute_option_description_' + option_id);
6447
6448
var c2 = document.createElement('span');
6449
var c2b = document.createElement('input');
6450
c2b.setAttribute('type', attribute_type);
6451
if ( typeof option_default !== 'undefined' ) {
6452
c2b.checked = option_default;
6453
}
6454
if ( attribute_type === 'radio' || attribute_type === 'image' || attribute_type === 'dropdown' ) {
6455
// ensured that user can activate only one radio button
6456
c2b.setAttribute('type', 'radio');
6457
c2b.setAttribute('name', attr_id);
6458
}
6459
6460
c2b.setAttribute('onchange', 'attribute_property_on_option_update(this)');
6461
c2b.setAttribute('id', '_via_attribute_option_default_' + option_id);
6462
6463
c0.appendChild(c0b);
6464
c1.appendChild(c1b);
6465
c2.appendChild(c2b);
6466
p.appendChild(c0);
6467
p.appendChild(c1);
6468
p.appendChild(c2);
6469
6470
document.getElementById('attribute_options').appendChild(p);
6471
}
6472
6473
function attribute_property_add_new_entry_option(attr_id, attribute_type) {
6474
var p = document.createElement('div');
6475
p.setAttribute('class', 'new_option_id_entry');
6476
var c0b = document.createElement('input');
6477
c0b.setAttribute('type', 'text');
6478
c0b.setAttribute('onchange', 'attribute_property_on_option_add(this)');
6479
c0b.setAttribute('id', '_via_attribute_new_option_id');
6480
c0b.setAttribute('placeholder', 'Add new option id');
6481
p.appendChild(c0b);
6482
document.getElementById('attribute_options').appendChild(p);
6483
}
6484
6485
function attribute_property_on_update(p) {
6486
var attr_id = get_current_attribute_id();
6487
var attr_type = _via_attribute_being_updated;
6488
var new_attr_type = p.value;
6489
6490
switch(p.id) {
6491
case 'attribute_name':
6492
if ( new_attr_type !== attr_id ) {
6493
Object.defineProperty(_via_attributes[attr_type],
6494
new_attr_type,
6495
Object.getOwnPropertyDescriptor(_via_attributes[attr_type], attr_id));
6496
6497
delete _via_attributes[attr_type][attr_id];
6498
update_attributes_update_panel();
6499
annotation_editor_update_content();
6500
}
6501
break;
6502
case 'attribute_description':
6503
_via_attributes[attr_type][attr_id].description = new_attr_type;
6504
update_attributes_update_panel();
6505
annotation_editor_update_content();
6506
break;
6507
case 'attribute_default_value':
6508
_via_attributes[attr_type][attr_id].default_value = new_attr_type;
6509
update_attributes_update_panel();
6510
annotation_editor_update_content();
6511
break;
6512
case 'attribute_type':
6513
var old_attr_type = _via_attributes[attr_type][attr_id].type;
6514
_via_attributes[attr_type][attr_id].type = new_attr_type;
6515
if( new_attr_type === VIA_ATTRIBUTE_TYPE.TEXT ) {
6516
_via_attributes[attr_type][attr_id].default_value = '';
6517
delete _via_attributes[attr_type][attr_id].options;
6518
delete _via_attributes[attr_type][attr_id].default_options;
6519
} else {
6520
// add options entry (if missing)
6521
if ( ! _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
}
6525
if ( _via_attributes[attr_type][attr_id].hasOwnProperty('default_value') ) {
6526
delete _via_attributes[attr_type][attr_id].default_value;
6527
}
6528
6529
// 1. gather all the attribute values in existing metadata
6530
var 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
6533
for(var option_id in _via_attributes[attr_type][attr_id]['options']) {
6534
if( !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
6541
for(var img_id in _via_img_metadata ) {
6542
for(var rindex in _via_img_metadata[img_id]['regions']) {
6543
if(_via_img_metadata[img_id]['regions'][rindex]['region_attributes'].hasOwnProperty(attr_id)) {
6544
if(old_attr_type === VIA_ATTRIBUTE_TYPE.CHECKBOX &&
6545
(new_attr_type === VIA_ATTRIBUTE_TYPE.RADIO ||
6546
new_attr_type === VIA_ATTRIBUTE_TYPE.DROPDOWN) ) {
6547
// add only if checkbox has only single option selected
6548
var sel_option_count = 0;
6549
var sel_option_id;
6550
for(var option_id in _via_img_metadata[img_id]['regions'][rindex]['region_attributes'][attr_id]) {
6551
if(_via_img_metadata[img_id]['regions'][rindex]['region_attributes'][attr_id][option_id]) {
6552
sel_option_count = sel_option_count + 1;
6553
sel_option_id = option_id;
6554
}
6555
}
6556
if(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
6560
delete _via_img_metadata[img_id]['regions'][rindex]['region_attributes'][attr_id];
6561
}
6562
}
6563
if( (old_attr_type === VIA_ATTRIBUTE_TYPE.RADIO ||
6564
old_attr_type === VIA_ATTRIBUTE_TYPE.DROPDOWN) &&
6565
new_attr_type === VIA_ATTRIBUTE_TYPE.CHECKBOX) {
6566
var 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
}
6574
show_attribute_properties();
6575
show_attribute_options();
6576
annotation_editor_update_content();
6577
break;
6578
}
6579
}
6580
6581
function attribute_get_unique_values(attr_type, attr_id) {
6582
var values = [];
6583
switch ( attr_type ) {
6584
case 'file':
6585
var img_id, attr_val;
6586
for ( img_id in _via_img_metadata ) {
6587
if ( _via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_id) ) {
6588
attr_val = _via_img_metadata[img_id].file_attributes[attr_id];
6589
if ( ! values.includes(attr_val) ) {
6590
values.push(attr_val);
6591
}
6592
}
6593
}
6594
break;
6595
case 'region':
6596
var img_id, attr_val, i;
6597
for ( img_id in _via_img_metadata ) {
6598
for ( i = 0; i < _via_img_metadata[img_id].regions.length; ++i ) {
6599
if ( _via_img_metadata[img_id].regions[i].region_attributes.hasOwnProperty(attr_id) ) {
6600
attr_val = _via_img_metadata[img_id].regions[i].region_attributes[attr_id];
6601
if( typeof(attr_val) === 'object' ) {
6602
for(var option_id in _via_img_metadata[img_id].regions[i].region_attributes[attr_id]) {
6603
if ( ! values.includes(option_id) ) {
6604
values.push(option_id);
6605
}
6606
}
6607
} else {
6608
if ( ! values.includes(attr_val) ) {
6609
values.push(attr_val);
6610
}
6611
}
6612
}
6613
}
6614
}
6615
break;
6616
default:
6617
break;
6618
}
6619
return values;
6620
}
6621
6622
function attribute_property_on_option_update(p) {
6623
var attr_id = get_current_attribute_id();
6624
if ( p.id.startsWith('_via_attribute_option_id_') ) {
6625
var old_key = p.id.substr( '_via_attribute_option_id_'.length );
6626
var new_key = p.value;
6627
if ( old_key !== new_key ) {
6628
var option_id_test = attribute_property_option_id_is_valid(attr_id, new_key);
6629
if ( option_id_test.is_valid ) {
6630
update_attribute_option_id_with_confirm(_via_attribute_being_updated,
6631
attr_id,
6632
old_key,
6633
new_key);
6634
} else {
6635
p.value = old_key; // restore old value
6636
show_message( option_id_test.message );
6637
show_attribute_properties();
6638
}
6639
return;
6640
}
6641
}
6642
6643
if ( p.id.startsWith('_via_attribute_option_description_') ) {
6644
var key = p.id.substr( '_via_attribute_option_description_'.length );
6645
var old_value = _via_attributes[_via_attribute_being_updated][attr_id].options[key];
6646
var new_value = p.value;
6647
if ( new_value !== old_value ) {
6648
_via_attributes[_via_attribute_being_updated][attr_id].options[key] = new_value;
6649
show_attribute_properties();
6650
annotation_editor_update_content();
6651
}
6652
}
6653
6654
if ( p.id.startsWith('_via_attribute_option_default_') ) {
6655
var new_default_option_id = p.id.substr( '_via_attribute_option_default_'.length );
6656
var old_default_option_id_list = Object.keys(_via_attributes[_via_attribute_being_updated][attr_id].default_options);
6657
6658
if ( 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 {
6662
switch ( _via_attributes[_via_attribute_being_updated][attr_id].type ) {
6663
case 'image': // fallback
6664
case 'dropdown': // fallback
6665
case '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;
6669
break;
6670
case 'checkbox':
6671
_via_attributes[_via_attribute_being_updated][attr_id].default_options[new_default_option_id] = p.checked;
6672
break;
6673
}
6674
}
6675
// default option updated
6676
attribute_property_on_option_default_update(_via_attribute_being_updated,
6677
attr_id,
6678
new_default_option_id).then( function() {
6679
show_attribute_properties();
6680
annotation_editor_update_content();
6681
});
6682
}
6683
}
6684
6685
function attribute_property_on_option_default_update(attribute_being_updated, attr_id, new_default_option_id) {
6686
return 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
6690
var img_id, attr_value, n, i;
6691
var attr_type = _via_attributes[attribute_being_updated][attr_id].type;
6692
switch( attribute_being_updated ) {
6693
case 'file':
6694
for ( img_id in _via_img_metadata ) {
6695
if ( ! _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
}
6699
break;
6700
case 'region':
6701
for ( img_id in _via_img_metadata ) {
6702
n = _via_img_metadata[img_id].regions.length;
6703
for ( i = 0; i < n; ++i ) {
6704
if ( ! _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
}
6709
break;
6710
}
6711
ok_callback();
6712
});
6713
}
6714
6715
function attribute_property_on_option_add(p) {
6716
if ( p.value === '' || p.value === null ) {
6717
return;
6718
}
6719
6720
if ( p.id === '_via_attribute_new_option_id' ) {
6721
var attr_id = get_current_attribute_id();
6722
var option_id = p.value;
6723
var option_id_test = attribute_property_option_id_is_valid(attr_id, option_id);
6724
if ( option_id_test.is_valid ) {
6725
_via_attributes[_via_attribute_being_updated][attr_id].options[option_id] = '';
6726
show_attribute_options();
6727
annotation_editor_update_content();
6728
} else {
6729
show_message( option_id_test.message );
6730
attribute_property_reset_new_entry_inputs();
6731
}
6732
}
6733
}
6734
6735
function attribute_property_reset_new_entry_inputs() {
6736
var container = document.getElementById('attribute_options');
6737
var p = container.lastChild;
6738
if ( p.childNodes[0] ) {
6739
p.childNodes[0].value = '';
6740
}
6741
if ( p.childNodes[1] ) {
6742
p.childNodes[1].value = '';
6743
}
6744
}
6745
6746
function attribute_property_show_new_entry_inputs(attr_id, attribute_type) {
6747
var n0 = document.createElement('div');
6748
n0.classList.add('property');
6749
var n1a = document.createElement('span');
6750
var n1b = document.createElement('input');
6751
n1b.setAttribute('onchange', 'attribute_property_on_option_add(this)');
6752
n1b.setAttribute('placeholder', 'Add new id');
6753
n1b.setAttribute('value', '');
6754
n1b.setAttribute('id', '_via_attribute_new_option_id');
6755
n1a.appendChild(n1b);
6756
6757
var n2a = document.createElement('span');
6758
var n2b = document.createElement('input');
6759
n2b.setAttribute('onchange', 'attribute_property_on_option_add(this)');
6760
n2b.setAttribute('placeholder', 'Optional description');
6761
n2b.setAttribute('value', '');
6762
n2b.setAttribute('id', '_via_attribute_new_option_description');
6763
n2a.appendChild(n2b);
6764
6765
var n3a = document.createElement('span');
6766
var n3b = document.createElement('input');
6767
n3b.setAttribute('type', attribute_type);
6768
if ( attribute_type === 'radio' ) {
6769
n3b.setAttribute('name', attr_id);
6770
}
6771
n3b.setAttribute('onchange', 'attribute_property_on_option_add(this)');
6772
n3b.setAttribute('id', '_via_attribute_new_option_default');
6773
n3a.appendChild(n3b);
6774
6775
n0.appendChild(n1a);
6776
n0.appendChild(n2a);
6777
n0.appendChild(n3a);
6778
6779
var container = document.getElementById('attribute_options');
6780
container.appendChild(n0);
6781
}
6782
6783
function attribute_property_option_id_is_valid(attr_id, new_option_id) {
6784
var option_id;
6785
for ( option_id in _via_attributes[_via_attribute_being_updated][attr_id].options ) {
6786
if ( option_id === new_option_id ) {
6787
return { 'is_valid':false, 'message':'Option id [' + attr_id + '] already exists' };
6788
}
6789
}
6790
6791
if ( new_option_id.includes('__') ) { // reserved separator for attribute-id, row-id, option-id
6792
return {'is_valid':false, 'message':'Option id cannot contain two consecutive underscores'};
6793
}
6794
6795
return {'is_valid':true};
6796
}
6797
6798
function attribute_property_id_exists(name) {
6799
var attr_name;
6800
for ( attr_name in _via_attributes[_via_attribute_being_updated] ) {
6801
if ( attr_name === name ) {
6802
return true;
6803
}
6804
}
6805
return false;
6806
}
6807
6808
function delete_existing_attribute_with_confirm() {
6809
var attr_id = document.getElementById('user_input_attribute_id').value;
6810
if ( attr_id === '' ) {
6811
show_message('Enter the name of attribute that you wish to delete');
6812
return;
6813
}
6814
if ( attribute_property_id_exists(attr_id) ) {
6815
var 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.'};
6817
var 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
};
6820
invoke_with_user_inputs(delete_existing_attribute_confirmed, input, config);
6821
} else {
6822
show_message('Attribute [' + attr_id + '] does not exist!');
6823
return;
6824
}
6825
}
6826
6827
function delete_existing_attribute_confirmed(input) {
6828
var attr_type = input.attr_type.value;
6829
var attr_id = input.attr_id.value;
6830
delete_existing_attribute(attr_type, attr_id);
6831
document.getElementById('user_input_attribute_id').value = '';
6832
show_message('Deleted ' + attr_type + ' attribute [' + attr_id + ']');
6833
user_input_default_cancel_handler();
6834
}
6835
6836
function delete_existing_attribute(attribute_type, attr_id) {
6837
if ( _via_attributes[attribute_type].hasOwnProperty( attr_id ) ) {
6838
var attr_id_list = Object.keys(_via_attributes[attribute_type]);
6839
if ( attr_id_list.length === 1 ) {
6840
_via_current_attribute_id = '';
6841
} else {
6842
var current_index = attr_id_list.indexOf(attr_id);
6843
var next_index = current_index + 1;
6844
if ( next_index === attr_id_list.length ) {
6845
next_index = current_index - 1;
6846
}
6847
_via_current_attribute_id = attr_id_list[next_index];
6848
}
6849
delete _via_attributes[attribute_type][attr_id];
6850
delete_region_attribute_in_all_metadata(attr_id);
6851
update_attributes_update_panel();
6852
annotation_editor_update_content();
6853
}
6854
}
6855
6856
function add_new_attribute_from_user_input() {
6857
var attr_id = document.getElementById('user_input_attribute_id').value;
6858
if ( attr_id === '' ) {
6859
show_message('Enter the name of attribute that you wish to delete');
6860
return;
6861
}
6862
6863
if ( attribute_property_id_exists(attr_id) ) {
6864
show_message('The ' + _via_attribute_being_updated + ' attribute [' + attr_id + '] already exists.');
6865
} else {
6866
_via_current_attribute_id = attr_id;
6867
add_new_attribute(attr_id);
6868
update_attributes_update_panel();
6869
annotation_editor_update_content();
6870
show_message('Added ' + _via_attribute_being_updated + ' attribute [' + attr_id + '].');
6871
}
6872
}
6873
6874
function 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
6881
function update_current_attribute_id(p) {
6882
_via_current_attribute_id = p.options[p.selectedIndex].value;
6883
update_attribute_properties_panel();
6884
}
6885
6886
function get_current_attribute_id() {
6887
return document.getElementById('attributes_name_list').value;
6888
}
6889
6890
function update_attribute_option_id_with_confirm(attr_type, attr_id, option_id, new_option_id) {
6891
var is_delete = false;
6892
var config;
6893
if ( new_option_id === '' || typeof(new_option_id) === 'undefined' ) {
6894
// an empty new_option_id indicates deletion of option_id
6895
config = {'title':'Delete an option for ' + attr_type + ' attribute'};
6896
is_delete = true;
6897
} else {
6898
config = {'title':'Rename an option for ' + attr_type + ' attribute'};
6899
}
6900
6901
var 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
6905
if ( is_delete ) {
6906
input['option_id'] = {'type':'text', 'name':'Attribute Option', 'value':option_id, 'disabled':true};
6907
} else {
6908
input['option_id'] = {'type':'text', 'name':'Attribute Option (old)', 'value':option_id, 'disabled':true},
6909
input['new_option_id'] = {'type':'text', 'name':'Attribute Option (new)', 'value':new_option_id, 'disabled':true};
6910
}
6911
6912
invoke_with_user_inputs(update_attribute_option_id_confirmed, input, config, update_attribute_option_id_cancel);
6913
}
6914
6915
function update_attribute_option_id_cancel(input) {
6916
update_attribute_properties_panel();
6917
}
6918
6919
function update_attribute_option_id_confirmed(input) {
6920
var attr_type = input.attr_type.value;
6921
var attr_id = input.attr_id.value;
6922
var option_id = input.option_id.value;
6923
var is_delete;
6924
var new_option_id;
6925
if ( typeof(input.new_option_id) === 'undefined' || input.new_option_id === '' ) {
6926
is_delete = true;
6927
new_option_id = '';
6928
} else {
6929
is_delete = false;
6930
new_option_id = input.new_option_id.value;
6931
}
6932
6933
update_attribute_option(is_delete, attr_type, attr_id, option_id, new_option_id);
6934
6935
if ( is_delete ) {
6936
show_message('Deleted option [' + option_id + '] for ' + attr_type + ' attribute [' + attr_id + '].');
6937
} else {
6938
show_message('Renamed option [' + option_id + '] to [' + new_option_id + '] for ' + attr_type + ' attribute [' + attr_id + '].');
6939
}
6940
update_attribute_properties_panel();
6941
annotation_editor_update_content();
6942
user_input_default_cancel_handler();
6943
}
6944
6945
function update_attribute_option(is_delete, attr_type, attr_id, option_id, new_option_id) {
6946
switch ( attr_type ) {
6947
case 'region':
6948
update_region_attribute_option_in_all_metadata(is_delete, attr_id, option_id, new_option_id);
6949
if ( ! is_delete ) {
6950
Object.defineProperty(_via_attributes[attr_type][attr_id].options,
6951
new_option_id,
6952
Object.getOwnPropertyDescriptor(_via_attributes[_via_attribute_being_updated][attr_id].options, option_id));
6953
}
6954
delete _via_attributes['region'][attr_id].options[option_id];
6955
6956
break;
6957
case 'file':
6958
update_file_attribute_option_in_all_metadata(attr_id, option_id);
6959
if ( ! is_delete ) {
6960
Object.defineProperty(_via_attributes[attr_type][attr_id].options,
6961
new_option_id,
6962
Object.getOwnPropertyDescriptor(_via_attributes[_via_attribute_being_updated][attr_id].options, option_id));
6963
}
6964
6965
delete _via_attributes['file'][attr_id].options[option_id];
6966
break;
6967
}
6968
}
6969
6970
function update_file_attribute_option_in_all_metadata(is_delete, attr_id, option_id, new_option_id) {
6971
var image_id;
6972
for ( image_id in _via_img_metadata ) {
6973
if ( _via_img_metadata[image_id].file_attributes.hasOwnProperty(attr_id) ) {
6974
if ( _via_img_metadata[image_id].file_attributes[attr_id].hasOwnProperty(option_id) ) {
6975
Object.defineProperty(_via_img_metadata[image_id].file_attributes[attr_id],
6976
new_option_id,
6977
Object.getOwnPropertyDescriptor(_via_img_metadata[image_id].file_attributes[attr_id], option_id));
6978
delete _via_img_metadata[image_id].file_attributes[attr_id][option_id];
6979
}
6980
}
6981
}
6982
}
6983
6984
function update_region_attribute_option_in_all_metadata(is_delete, attr_id, option_id, new_option_id) {
6985
var image_id;
6986
for ( image_id in _via_img_metadata ) {
6987
for (var i = 0; i < _via_img_metadata[image_id].regions.length; ++i ) {
6988
if ( _via_img_metadata[image_id].regions[i].region_attributes.hasOwnProperty(attr_id) ) {
6989
if ( _via_img_metadata[image_id].regions[i].region_attributes[attr_id].hasOwnProperty(option_id) ) {
6990
Object.defineProperty(_via_img_metadata[image_id].regions[i].region_attributes[attr_id],
6991
new_option_id,
6992
Object.getOwnPropertyDescriptor(_via_img_metadata[image_id].regions[i].region_attributes[attr_id], option_id));
6993
delete _via_img_metadata[image_id].regions[i].region_attributes[attr_id][option_id];
6994
}
6995
}
6996
}
6997
}
6998
}
6999
7000
function delete_region_attribute_in_all_metadata(attr_id) {
7001
var image_id;
7002
for ( image_id in _via_img_metadata ) {
7003
for (var i = 0; i < _via_img_metadata[image_id].regions.length; ++i ) {
7004
if ( _via_img_metadata[image_id].regions[i].region_attributes.hasOwnProperty(attr_id)) {
7005
delete _via_img_metadata[image_id].regions[i].region_attributes[attr_id];
7006
}
7007
}
7008
}
7009
}
7010
7011
function delete_file_attribute_option_from_all_metadata(attr_id, option_id) {
7012
var image_id;
7013
for ( image_id in _via_img_metadata ) {
7014
if ( _via_img_metadata.hasOwnProperty(image_id) ) {
7015
delete_file_attribute_option_from_metadata(image_id, attr_id, option_id);
7016
}
7017
}
7018
}
7019
7020
function delete_file_attribute_option_from_metadata(image_id, attr_id, option_id) {
7021
var i;
7022
if ( _via_img_metadata[image_id].file_attributes.hasOwnProperty(attr_id) ) {
7023
if ( _via_img_metadata[image_id].file_attributes[attr_id].hasOwnProperty(option_id) ) {
7024
delete _via_img_metadata[image_id].file_attributes[attr_id][option_id];
7025
}
7026
}
7027
}
7028
7029
function delete_file_attribute_from_all_metadata(image_id, attr_id) {
7030
var image_id;
7031
for ( image_id in _via_img_metadata ) {
7032
if ( _via_img_metadata.hasOwnProperty(image_id) ) {
7033
if ( _via_img_metadata[image_id].file_attributes.hasOwnProperty(attr_id) ) {
7034
delete _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
//
7043
function invoke_with_user_inputs(ok_handler, input, config, cancel_handler) {
7044
setup_user_input_panel(ok_handler, input, config, cancel_handler);
7045
show_user_input_panel();
7046
}
7047
7048
function 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
7059
var p = document.getElementById('user_input_panel');
7060
var c = document.createElement('div');
7061
c.setAttribute('class', 'content');
7062
var html = [];
7063
html.push('<p class="title">' + config.title + '</p>');
7064
7065
html.push('<div class="user_inputs">');
7066
var key;
7067
for ( key in _via_user_input_data ) {
7068
html.push('<div class="row">');
7069
html.push('<span class="cell">' + _via_user_input_data[key].name + '</span>');
7070
var disabled_html = '';
7071
if ( _via_user_input_data[key].disabled ) {
7072
disabled_html = 'disabled="disabled"';
7073
}
7074
var value_html = '';
7075
if ( _via_user_input_data[key].value ) {
7076
value_html = 'value="' + _via_user_input_data[key].value + '"';
7077
}
7078
7079
switch(_via_user_input_data[key].type) {
7080
case 'checkbox':
7081
if ( _via_user_input_data[key].checked ) {
7082
value_html = 'checked="checked"';
7083
} else {
7084
value_html = '';
7085
}
7086
html.push('<span class="cell">' +
7087
'<input class="_via_user_input_variable" ' +
7088
value_html + ' ' +
7089
disabled_html + ' ' +
7090
'type="checkbox" id="' + key + '"></span>');
7091
break;
7092
case 'text':
7093
var size = '50';
7094
if ( _via_user_input_data[key].size ) {
7095
size = _via_user_input_data[key].size;
7096
}
7097
var placeholder = '';
7098
if ( _via_user_input_data[key].placeholder ) {
7099
placeholder = _via_user_input_data[key].placeholder;
7100
}
7101
html.push('<span class="cell">' +
7102
'<input class="_via_user_input_variable" ' +
7103
value_html + ' ' +
7104
disabled_html + ' ' +
7105
'size="' + size + '" ' +
7106
'placeholder="' + placeholder + '" ' +
7107
'type="text" id="' + key + '"></span>');
7108
7109
break;
7110
case 'textarea':
7111
var rows = '5';
7112
var cols = '50'
7113
if ( _via_user_input_data[key].rows ) {
7114
rows = _via_user_input_data[key].rows;
7115
}
7116
if ( _via_user_input_data[key].cols ) {
7117
cols = _via_user_input_data[key].cols;
7118
}
7119
var placeholder = '';
7120
if ( _via_user_input_data[key].placeholder ) {
7121
placeholder = _via_user_input_data[key].placeholder;
7122
}
7123
html.push('<span class="cell">' +
7124
'<textarea class="_via_user_input_variable" ' +
7125
disabled_html + ' ' +
7126
'rows="' + rows + '" ' +
7127
'cols="' + cols + '" ' +
7128
'placeholder="' + placeholder + '" ' +
7129
'id="' + key + '">' + value_html + '</textarea></span>');
7130
7131
break;
7132
7133
}
7134
html.push('</div>'); // end of row
7135
}
7136
html.push('</div>'); // end of user_input div
7137
// optional warning before confirmation
7138
if (config.hasOwnProperty("warning") ) {
7139
html.push('<div class="warning">' + config.warning + '</div>');
7140
}
7141
html.push('<div class="user_confirm">' +
7142
'<span class="ok">' +
7143
'<button id="user_input_ok_button" onclick="user_input_parse_and_invoke_handler()">&nbsp;OK&nbsp;</button></span>' +
7144
'<span class="cancel">' +
7145
'<button id="user_input_cancel_button" onclick="user_input_cancel_handler()">CANCEL</button></span></div>');
7146
c.innerHTML = html.join('');
7147
p.innerHTML = '';
7148
p.appendChild(c);
7149
7150
}
7151
7152
function user_input_default_cancel_handler() {
7153
hide_user_input_panel();
7154
_via_user_input_data = {};
7155
_via_user_input_ok_handler = null;
7156
_via_user_input_cancel_handler = null;
7157
}
7158
7159
function user_input_cancel_handler() {
7160
if ( _via_user_input_cancel_handler ) {
7161
_via_user_input_cancel_handler();
7162
}
7163
user_input_default_cancel_handler();
7164
}
7165
7166
function user_input_parse_and_invoke_handler() {
7167
var elist = document.getElementsByClassName('_via_user_input_variable');
7168
var i;
7169
for ( i=0; i < elist.length; ++i ) {
7170
var eid = elist[i].id;
7171
if ( _via_user_input_data.hasOwnProperty(eid) ) {
7172
switch(_via_user_input_data[eid].type) {
7173
case 'checkbox':
7174
_via_user_input_data[eid].value = elist[i].checked;
7175
break;
7176
default:
7177
_via_user_input_data[eid].value = elist[i].value;
7178
break;
7179
}
7180
}
7181
}
7182
if ( typeof(_via_user_input_data.confirm) !== 'undefined' ) {
7183
if ( _via_user_input_data.confirm.value ) {
7184
_via_user_input_ok_handler(_via_user_input_data);
7185
} else {
7186
if ( _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
}
7193
user_input_default_cancel_handler();
7194
}
7195
7196
function show_user_input_panel() {
7197
document.getElementById('user_input_panel').style.display = 'block';
7198
}
7199
7200
function hide_user_input_panel() {
7201
document.getElementById('user_input_panel').style.display = 'none';
7202
}
7203
7204
//
7205
// annotations editor panel
7206
//
7207
function annotation_editor_show() {
7208
// remove existing annotation editor (if any)
7209
annotation_editor_remove();
7210
7211
// create new container of annotation editor
7212
var ae = document.createElement('div');
7213
ae.setAttribute('id', 'annotation_editor');
7214
7215
if ( _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION ) {
7216
if ( _via_settings.ui.image.on_image_annotation_editor_placement === VIA_ANNOTATION_EDITOR_PLACEMENT.DISABLE ) {
7217
return;
7218
}
7219
7220
// only display on-image annotation editor if
7221
// - region attribute are defined
7222
// - region is selected
7223
if ( _via_is_region_selected &&
7224
Object.keys(_via_attributes['region']).length &&
7225
_via_attributes['region'].constructor === Object ) {
7226
ae.classList.add('force_small_font');
7227
ae.classList.add('display_area_content'); // to enable automatic hiding of this content
7228
// add annotation editor to image_panel
7229
if ( _via_settings.ui.image.on_image_annotation_editor_placement === VIA_ANNOTATION_EDITOR_PLACEMENT.NEAR_REGION ) {
7230
var html_position = annotation_editor_get_placement(_via_user_sel_region_id);
7231
ae.style.top = html_position.top;
7232
ae.style.left = html_position.left;
7233
}
7234
_via_display_area.appendChild(ae);
7235
annotation_editor_update_content();
7236
update_vertical_space();
7237
}
7238
} else {
7239
// show annotation editor in a separate panel at the bottom
7240
_via_annotaion_editor_panel.appendChild(ae);
7241
annotation_editor_update_content();
7242
update_vertical_space();
7243
7244
if ( _via_is_region_selected ) {
7245
// highlight entry for region_id in annotation editor panel
7246
annotation_editor_scroll_to_row(_via_user_sel_region_id);
7247
annotation_editor_highlight_row(_via_user_sel_region_id);
7248
}
7249
}
7250
}
7251
7252
function annotation_editor_hide() {
7253
if ( _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION ) {
7254
// remove existing annotation editor (if any)
7255
annotation_editor_remove();
7256
} else {
7257
annotation_editor_clear_row_highlight();
7258
}
7259
}
7260
7261
function annotation_editor_toggle_on_image_editor() {
7262
if ( _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;
7265
annotation_editor_show();
7266
show_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;
7270
annotation_editor_hide();
7271
show_message('Disabled on image annotation editor');
7272
}
7273
}
7274
7275
function annotation_editor_update_content() {
7276
return new Promise( function(ok_callback, err_callback) {
7277
var ae = document.getElementById('annotation_editor');
7278
if (ae ) {
7279
ae.innerHTML = '';
7280
annotation_editor_update_header_html();
7281
annotation_editor_update_metadata_html();
7282
}
7283
ok_callback();
7284
});
7285
}
7286
7287
function annotation_editor_get_placement(region_id) {
7288
var html_position = {};
7289
var r = _via_canvas_regions[region_id]['shape_attributes'];
7290
var shape = r['name'];
7291
switch( shape ) {
7292
case 'rect':
7293
html_position.top = r['y'] + r['height'];
7294
html_position.left = r['x'] + r['width'];
7295
break;
7296
case 'circle':
7297
html_position.top = r['cy'] + r['r'];
7298
html_position.left = r['cx'];
7299
break;
7300
case 'ellipse':
7301
html_position.top = r['cy'] + r['ry'] * Math.cos(r['theta']);
7302
html_position.left = r['cx'] - r['ry'] * Math.sin(r['theta']);
7303
break;
7304
case 'polygon':
7305
case 'polyline':
7306
var most_left =
7307
Object.keys(r['all_points_x']).reduce(function(a, b){
7308
return r['all_points_x'][a] > r['all_points_x'][b] ? a : b });
7309
html_position.top = Math.max( r['all_points_y'][most_left] );
7310
html_position.left = Math.max( r['all_points_x'][most_left] );
7311
break;
7312
case 'point':
7313
html_position.top = r['cy'];
7314
html_position.left = r['cx'];
7315
break;
7316
}
7317
html_position.top = html_position.top + _via_img_panel.offsetTop + VIA_REGION_EDGE_TOL + 'px';
7318
html_position.left = html_position.left + _via_img_panel.offsetLeft + VIA_REGION_EDGE_TOL + 'px';
7319
return html_position;
7320
}
7321
7322
function annotation_editor_remove() {
7323
var p = document.getElementById('annotation_editor');
7324
if ( p ) {
7325
p.remove();
7326
}
7327
}
7328
7329
function is_annotation_editor_visible() {
7330
return document.getElementById('annotation_editor_panel').classList.contains('display_block');
7331
}
7332
7333
function annotation_editor_toggle_all_regions_editor() {
7334
var p = document.getElementById('annotation_editor_panel');
7335
if ( p.classList.contains('display_block') ) {
7336
p.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;
7340
p.classList.add('display_block');
7341
p.style.height = _via_settings.ui.annotation_editor_height + '%';
7342
p.style.fontSize = _via_settings.ui.annotation_editor_fontsize + 'rem';
7343
annotation_editor_show();
7344
}
7345
}
7346
7347
function annotation_editor_set_active_button() {
7348
var attribute_type;
7349
for ( attribute_type in _via_attributes ) {
7350
var bid = 'button_edit_' + attribute_type + '_metadata';
7351
document.getElementById(bid).classList.remove('active');
7352
}
7353
var bid = 'button_edit_' + _via_metadata_being_updated + '_metadata';
7354
document.getElementById(bid).classList.add('active');
7355
}
7356
7357
function edit_region_metadata_in_annotation_editor() {
7358
_via_metadata_being_updated = 'region';
7359
annotation_editor_set_active_button();
7360
annotation_editor_update_content();
7361
}
7362
function edit_file_metadata_in_annotation_editor() {
7363
_via_metadata_being_updated = 'file';
7364
annotation_editor_set_active_button();
7365
annotation_editor_update_content();
7366
}
7367
7368
function annotation_editor_update_header_html() {
7369
var head = document.createElement('div');
7370
head.setAttribute('class', 'row');
7371
head.setAttribute('id', 'annotation_editor_header');
7372
7373
if ( _via_metadata_being_updated === 'region' ) {
7374
var rid_col = document.createElement('span');
7375
rid_col.setAttribute('class', 'col');
7376
rid_col.innerHTML = '';
7377
head.appendChild(rid_col);
7378
}
7379
7380
if ( _via_metadata_being_updated === 'file' ) {
7381
var rid_col = document.createElement('span');
7382
rid_col.setAttribute('class', 'col header');
7383
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
7384
rid_col.innerHTML = 'group';
7385
} else {
7386
rid_col.innerHTML = 'filename';
7387
}
7388
head.appendChild(rid_col);
7389
}
7390
7391
var attr_id;
7392
for ( attr_id in _via_attributes[_via_metadata_being_updated] ) {
7393
var col = document.createElement('span');
7394
col.setAttribute('class', 'col header');
7395
col.innerHTML = attr_id;
7396
head.appendChild(col);
7397
}
7398
7399
var ae = document.getElementById('annotation_editor');
7400
if ( ae.childNodes.length === 0 ) {
7401
ae.appendChild(head);
7402
} else {
7403
if ( ae.firstChild.id === 'annotation_editor_header') {
7404
ae.replaceChild(head, ae.firstChild);
7405
} else {
7406
// header node is absent
7407
ae.insertBefore(head, ae.firstChild);
7408
}
7409
}
7410
}
7411
7412
function annotation_editor_update_metadata_html() {
7413
if ( ! _via_img_count ) {
7414
return;
7415
}
7416
7417
var ae = document.getElementById('annotation_editor');
7418
switch ( _via_metadata_being_updated ) {
7419
case 'region':
7420
var rindex;
7421
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
7422
ae.appendChild( annotation_editor_get_metadata_row_html(0) );
7423
} else {
7424
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE ) {
7425
if ( _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.SINGLE_REGION ) {
7426
ae.appendChild( annotation_editor_get_metadata_row_html(_via_user_sel_region_id) );
7427
} else {
7428
for ( rindex = 0; rindex < _via_img_metadata[_via_image_id].regions.length; ++rindex ) {
7429
ae.appendChild( annotation_editor_get_metadata_row_html(rindex) );
7430
}
7431
}
7432
}
7433
}
7434
break;
7435
7436
case 'file':
7437
ae.appendChild( annotation_editor_get_metadata_row_html(0) );
7438
break;
7439
}
7440
}
7441
7442
function annotation_editor_update_row(row_id) {
7443
var ae = document.getElementById('annotation_editor');
7444
7445
var new_row = annotation_editor_get_metadata_row_html(row_id);
7446
var old_row = document.getElementById(new_row.getAttribute('id'));
7447
ae.replaceChild(new_row, old_row);
7448
}
7449
7450
function annotation_editor_add_row(row_id) {
7451
if ( is_annotation_editor_visible() ) {
7452
var ae = document.getElementById('annotation_editor');
7453
var new_row = annotation_editor_get_metadata_row_html(row_id);
7454
var penultimate_row_id = parseInt(row_id) - 1;
7455
if ( penultimate_row_id >= 0 ) {
7456
var penultimate_row_html_id = 'ae_' + _via_metadata_being_updated + '_' + penultimate_row_id;
7457
var penultimate_row = document.getElementById(penultimate_row_html_id);
7458
ae.insertBefore(new_row, penultimate_row.nextSibling);
7459
} else {
7460
ae.appendChild(new_row);
7461
}
7462
}
7463
}
7464
7465
function annotation_editor_get_metadata_row_html(row_id) {
7466
var row = document.createElement('div');
7467
row.setAttribute('class', 'row');
7468
row.setAttribute('id', 'ae_' + _via_metadata_being_updated + '_' + row_id);
7469
7470
if ( _via_metadata_being_updated === 'region' ) {
7471
var rid = document.createElement('span');
7472
7473
switch(_via_display_area_content_name) {
7474
case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:
7475
rid.setAttribute('class', 'col');
7476
rid.innerHTML = 'Grouped regions in ' + _via_image_grid_selected_img_index_list.length + ' files';
7477
break;
7478
case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE:
7479
rid.setAttribute('class', 'col id');
7480
rid.innerHTML = (row_id + 1);
7481
break;
7482
}
7483
row.appendChild(rid);
7484
}
7485
7486
if ( _via_metadata_being_updated === 'file' ) {
7487
var rid = document.createElement('span');
7488
rid.setAttribute('class', 'col');
7489
switch(_via_display_area_content_name) {
7490
case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID:
7491
rid.innerHTML = 'Group of ' + _via_image_grid_selected_img_index_list.length + ' files';
7492
break;
7493
case VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE:
7494
rid.innerHTML = _via_image_filename_list[_via_image_index];
7495
break;
7496
}
7497
7498
row.appendChild(rid);
7499
}
7500
7501
var attr_id;
7502
for ( attr_id in _via_attributes[_via_metadata_being_updated] ) {
7503
var col = document.createElement('span');
7504
col.setAttribute('class', 'col');
7505
7506
var attr_type = _via_attributes[_via_metadata_being_updated][attr_id].type;
7507
var attr_desc = _via_attributes[_via_metadata_being_updated][attr_id].desc;
7508
if ( typeof(attr_desc) === 'undefined' ) {
7509
attr_desc = '';
7510
}
7511
var attr_html_id = attr_id + '__' + row_id;
7512
7513
var attr_value = '';
7514
var attr_placeholder = '';
7515
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE ) {
7516
switch(_via_metadata_being_updated) {
7517
case 'region':
7518
if ( _via_img_metadata[_via_image_id].regions[row_id].region_attributes.hasOwnProperty(attr_id) ) {
7519
attr_value = _via_img_metadata[_via_image_id].regions[row_id].region_attributes[attr_id];
7520
} else {
7521
attr_placeholder = 'not defined yet!';
7522
}
7523
case 'file':
7524
if ( _via_img_metadata[_via_image_id].file_attributes.hasOwnProperty(attr_id) ) {
7525
attr_value = _via_img_metadata[_via_image_id].file_attributes[attr_id];
7526
} else {
7527
attr_placeholder = 'not defined yet!';
7528
}
7529
}
7530
}
7531
7532
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
7533
var attr_metadata_stat;
7534
switch(_via_metadata_being_updated) {
7535
case 'region':
7536
attr_metadata_stat = _via_get_region_metadata_stat(_via_image_grid_selected_img_index_list, attr_id);
7537
break;
7538
case 'file':
7539
attr_metadata_stat = _via_get_file_metadata_stat(_via_image_grid_selected_img_index_list, attr_id);
7540
break;
7541
}
7542
7543
switch ( attr_type ) {
7544
case 'text':
7545
if ( attr_metadata_stat.hasOwnProperty(attr_id) ) {
7546
var attr_value_set = Object.keys(attr_metadata_stat[attr_id]);
7547
if ( attr_value_set.includes('undefined') ) {
7548
attr_value = '';
7549
attr_placeholder = 'includes ' + attr_metadata_stat[attr_id]['undefined'] + ' undefined values';
7550
} else {
7551
switch( attr_value_set.length ) {
7552
case 0:
7553
attr_value = '';
7554
attr_placeholder = 'not applicable';
7555
break;
7556
case 1:
7557
attr_value = attr_value_set[0];
7558
attr_placeholder = '';
7559
break;
7560
default:
7561
attr_value = '';
7562
attr_placeholder = attr_value_set.length + ' different values: ' + JSON.stringify(attr_value_set).replace(/"/g,'\'');
7563
}
7564
}
7565
} else {
7566
attr_value = '';
7567
attr_placeholder = 'not defined yet!';
7568
}
7569
break;
7570
7571
case 'radio': // fallback
7572
case 'dropdown': // fallback
7573
case 'image': // fallback
7574
if ( attr_metadata_stat.hasOwnProperty(attr_id) ) {
7575
var attr_value_set = Object.keys(attr_metadata_stat[attr_id]);
7576
if ( attr_value_set.length === 1 ) {
7577
attr_value = attr_value_set[0];
7578
} else {
7579
attr_value = '';
7580
}
7581
} else {
7582
attr_value = '';
7583
}
7584
break;
7585
7586
case 'checkbox':
7587
attr_value = {};
7588
if ( attr_metadata_stat.hasOwnProperty(attr_id) ) {
7589
var attr_value_set = Object.keys(attr_metadata_stat[attr_id]);
7590
var same_count = true;
7591
var i, n;
7592
var attr_value_curr, attr_value_next;
7593
n = attr_value_set.length;
7594
for ( i = 0; i < n - 1; ++i ) {
7595
attr_value_curr = attr_value_set[i];
7596
attr_value_next = attr_value_set[i+1];
7597
7598
if ( attr_metadata_stat[attr_id][attr_value_curr] !== attr_metadata_stat[attr_id][attr_value_next] ) {
7599
same_count = false;
7600
break;
7601
}
7602
}
7603
if ( same_count ) {
7604
var attr_value_i;
7605
for ( attr_value_i in attr_metadata_stat[attr_id] ) {
7606
attr_value[attr_value_i] = true;
7607
}
7608
}
7609
}
7610
break;
7611
}
7612
}
7613
7614
switch(attr_type) {
7615
case 'text':
7616
col.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>';
7622
break;
7623
case 'checkbox':
7624
var options = _via_attributes[_via_metadata_being_updated][attr_id].options;
7625
var option_id;
7626
for ( option_id in options ) {
7627
var option_html_id = attr_html_id + '__' + option_id;
7628
var option = document.createElement('input');
7629
option.setAttribute('type', 'checkbox');
7630
option.setAttribute('value', option_id);
7631
option.setAttribute('id', option_html_id);
7632
option.setAttribute('onfocus', 'annotation_editor_on_metadata_focus(this)');
7633
option.setAttribute('onchange', 'annotation_editor_on_metadata_update(this)');
7634
7635
var option_desc = _via_attributes[_via_metadata_being_updated][attr_id].options[option_id];
7636
if ( option_desc === '' || typeof(option_desc) === 'undefined' ) {
7637
// option description is optional, use option_id when description is not present
7638
option_desc = option_id;
7639
}
7640
7641
// set the value of options based on the user annotations
7642
if ( typeof attr_value !== 'undefined') {
7643
if ( attr_value.hasOwnProperty(option_id) ) {
7644
option.checked = attr_value[option_id];
7645
}
7646
}
7647
7648
var label = document.createElement('label');
7649
label.setAttribute('for', option_html_id);
7650
label.innerHTML = option_desc;
7651
7652
var container = document.createElement('span');
7653
container.appendChild(option);
7654
container.appendChild(label);
7655
col.appendChild(container);
7656
}
7657
break;
7658
case 'radio':
7659
var option_id;
7660
for ( option_id in _via_attributes[_via_metadata_being_updated][attr_id].options ) {
7661
var option_html_id = attr_html_id + '__' + option_id;
7662
var option = document.createElement('input');
7663
option.setAttribute('type', 'radio');
7664
option.setAttribute('name', attr_html_id);
7665
option.setAttribute('value', option_id);
7666
option.setAttribute('id', option_html_id);
7667
option.setAttribute('onfocus', 'annotation_editor_on_metadata_focus(this)');
7668
option.setAttribute('onchange', 'annotation_editor_on_metadata_update(this)');
7669
7670
var option_desc = _via_attributes[_via_metadata_being_updated][attr_id].options[option_id];
7671
if ( option_desc === '' || typeof(option_desc) === 'undefined' ) {
7672
// option description is optional, use option_id when description is not present
7673
option_desc = option_id;
7674
}
7675
7676
if ( attr_value === option_id ) {
7677
option.checked = true;
7678
}
7679
7680
var label = document.createElement('label');
7681
label.setAttribute('for', option_html_id);
7682
label.innerHTML = option_desc;
7683
7684
var container = document.createElement('span');
7685
container.appendChild(option);
7686
container.appendChild(label);
7687
col.appendChild(container);
7688
}
7689
break;
7690
case 'image':
7691
var option_id;
7692
var option_count = 0;
7693
for ( option_id in _via_attributes[_via_metadata_being_updated][attr_id].options ) {
7694
option_count = option_count + 1;
7695
}
7696
var img_options = document.createElement('div');
7697
img_options.setAttribute('class', 'img_options');
7698
col.appendChild(img_options);
7699
7700
var option_index = 0;
7701
for ( option_id in _via_attributes[_via_metadata_being_updated][attr_id].options ) {
7702
var option_html_id = attr_html_id + '__' + option_id;
7703
var option = document.createElement('input');
7704
option.setAttribute('type', 'radio');
7705
option.setAttribute('name', attr_html_id);
7706
option.setAttribute('value', option_id);
7707
option.setAttribute('id', option_html_id);
7708
option.setAttribute('onfocus', 'annotation_editor_on_metadata_focus(this)');
7709
option.setAttribute('onchange', 'annotation_editor_on_metadata_update(this)');
7710
7711
var option_desc = _via_attributes[_via_metadata_being_updated][attr_id].options[option_id];
7712
if ( option_desc === '' || typeof(option_desc) === 'undefined' ) {
7713
// option description is optional, use option_id when description is not present
7714
option_desc = option_id;
7715
}
7716
7717
if ( attr_value === option_id ) {
7718
option.checked = true;
7719
}
7720
7721
var label = document.createElement('label');
7722
label.setAttribute('for', option_html_id);
7723
label.innerHTML = '<img src="' + option_desc + '"><p>' + option_id + '</p>';
7724
7725
var container = document.createElement('span');
7726
container.appendChild(option);
7727
container.appendChild(label);
7728
img_options.appendChild(container);
7729
}
7730
break;
7731
7732
case 'dropdown':
7733
var sel = document.createElement('select');
7734
sel.setAttribute('id', attr_html_id);
7735
sel.setAttribute('onfocus', 'annotation_editor_on_metadata_focus(this)');
7736
sel.setAttribute('onchange', 'annotation_editor_on_metadata_update(this)');
7737
var option_id;
7738
var option_selected = false;
7739
for ( option_id in _via_attributes[_via_metadata_being_updated][attr_id].options ) {
7740
var option_html_id = attr_html_id + '__' + option_id;
7741
var option = document.createElement('option');
7742
option.setAttribute('value', option_id);
7743
7744
var option_desc = _via_attributes[_via_metadata_being_updated][attr_id].options[option_id];
7745
if ( option_desc === '' || typeof(option_desc) === 'undefined' ) {
7746
// option description is optional, use option_id when description is not present
7747
option_desc = option_id;
7748
}
7749
7750
if ( option_id === attr_value ) {
7751
option.setAttribute('selected', 'selected');
7752
option_selected = true;
7753
}
7754
option.innerHTML = option_desc;
7755
sel.appendChild(option);
7756
}
7757
7758
if ( ! option_selected ) {
7759
sel.selectedIndex = '-1';
7760
}
7761
col.appendChild(sel);
7762
break;
7763
}
7764
7765
row.appendChild(col);
7766
}
7767
return row;
7768
}
7769
7770
function annotation_editor_scroll_to_row(row_id) {
7771
if ( is_annotation_editor_visible() ) {
7772
var row_html_id = 'ae_' + _via_metadata_being_updated + '_' + row_id;
7773
var row = document.getElementById(row_html_id);
7774
row.scrollIntoView(false);
7775
}
7776
}
7777
7778
function annotation_editor_highlight_row(row_id) {
7779
if ( is_annotation_editor_visible() ) {
7780
var row_html_id = 'ae_' + _via_metadata_being_updated + '_' + row_id;
7781
var row = document.getElementById(row_html_id);
7782
row.classList.add('highlight');
7783
}
7784
}
7785
7786
function annotation_editor_clear_row_highlight() {
7787
if ( is_annotation_editor_visible() ) {
7788
var ae = document.getElementById('annotation_editor');
7789
var i;
7790
for ( i=0; i<ae.childNodes.length; ++i ) {
7791
ae.childNodes[i].classList.remove('highlight');
7792
}
7793
}
7794
}
7795
7796
function annotation_editor_extract_html_id_components(html_id) {
7797
// html_id : attribute_name__row-id__option_id
7798
var parts = html_id.split('__');
7799
var parsed_id = {};
7800
switch( parts.length ) {
7801
case 3:
7802
// html_id : attribute-id__row-id__option_id
7803
parsed_id.attr_id = parts[0];
7804
parsed_id.row_id = parts[1];
7805
parsed_id.option_id = parts[2];
7806
break;
7807
case 2:
7808
// html_id : attribute-id__row-id
7809
parsed_id.attr_id = parts[0];
7810
parsed_id.row_id = parts[1];
7811
break;
7812
default:
7813
}
7814
return parsed_id;
7815
}
7816
7817
function _via_get_file_metadata_stat(img_index_list, attr_id) {
7818
var stat = {};
7819
stat[attr_id] = {};
7820
var i, n, img_id, img_index, value;
7821
n = img_index_list.length;
7822
for ( i = 0; i < n; ++i ) {
7823
img_index = img_index_list[i];
7824
img_id = _via_image_id_list[img_index];
7825
if ( _via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_id) ) {
7826
value = _via_img_metadata[img_id].file_attributes[attr_id];
7827
if ( typeof(value) === 'object' ) {
7828
// checkbox has multiple values and hence is object
7829
var key;
7830
for ( key in value ) {
7831
if ( stat[attr_id].hasOwnProperty(key) ) {
7832
stat[attr_id][key] += 1;
7833
} else {
7834
stat[attr_id][key] = 1;
7835
}
7836
}
7837
} else {
7838
if ( stat[attr_id].hasOwnProperty(value) ) {
7839
stat[attr_id][value] += 1;
7840
} else {
7841
stat[attr_id][value] = 1;
7842
}
7843
}
7844
}
7845
7846
}
7847
return stat;
7848
}
7849
7850
function _via_get_region_metadata_stat(img_index_list, attr_id) {
7851
var stat = {};
7852
stat[attr_id] = {};
7853
var i, n, img_id, img_index, value;
7854
var j, m;
7855
n = img_index_list.length;
7856
for ( i = 0; i < n; ++i ) {
7857
img_index = img_index_list[i];
7858
img_id = _via_image_id_list[img_index];
7859
m = _via_img_metadata[img_id].regions.length;
7860
for ( j = 0; j < m; ++j ) {
7861
if ( ! image_grid_is_region_in_current_group( _via_img_metadata[img_id].regions[j].region_attributes ) ) {
7862
// skip region not in current group
7863
continue;
7864
}
7865
7866
value = _via_img_metadata[img_id].regions[j].region_attributes[attr_id];
7867
if ( typeof(value) === 'object' ) {
7868
// checkbox has multiple values and hence is object
7869
var key;
7870
for ( key in value ) {
7871
if ( stat[attr_id].hasOwnProperty(key) ) {
7872
stat[attr_id][key] += 1;
7873
} else {
7874
stat[attr_id][key] = 1;
7875
}
7876
}
7877
} else {
7878
if ( stat[attr_id].hasOwnProperty(value) ) {
7879
stat[attr_id][value] += 1;
7880
} else {
7881
stat[attr_id][value] = 1;
7882
}
7883
}
7884
}
7885
}
7886
return stat;
7887
}
7888
7889
// invoked when the input entry in annotation editor receives focus
7890
function annotation_editor_on_metadata_focus(p) {
7891
if ( _via_annotation_editor_mode === VIA_ANNOTATION_EDITOR_MODE.ALL_REGIONS ) {
7892
var pid = annotation_editor_extract_html_id_components(p.id);
7893
var region_id = pid.row_id;
7894
// clear existing highlights (if any)
7895
toggle_all_regions_selection(false);
7896
annotation_editor_clear_row_highlight();
7897
// set new selection highlights
7898
set_region_select_state(region_id, true);
7899
annotation_editor_scroll_to_row(region_id);
7900
annotation_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
7907
function annotation_editor_on_metadata_update(p) {
7908
var pid = annotation_editor_extract_html_id_components(p.id);
7909
var img_id = _via_image_id;
7910
7911
var img_index_list = [ _via_image_index ];
7912
var region_id = pid.row_id;
7913
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
7914
img_index_list = _via_image_grid_selected_img_index_list.slice(0);
7915
region_id = -1; // this flag denotes that we want to update all regions
7916
}
7917
7918
if ( _via_metadata_being_updated === 'file' ) {
7919
annotation_editor_update_file_metadata(img_index_list, pid.attr_id, p.value, p.checked).then( function(update_count) {
7920
annotation_editor_on_metadata_update_done('file', pid.attr_id, update_count);
7921
}, function(err) {
7922
console.log(err)
7923
show_message('Failed to update file attributes! ' + err);
7924
});
7925
return;
7926
}
7927
7928
if ( _via_metadata_being_updated === 'region' ) {
7929
annotation_editor_update_region_metadata(img_index_list, region_id, pid.attr_id, p.value, p.checked).then( function(update_count) {
7930
annotation_editor_on_metadata_update_done('region', pid.attr_id, update_count);
7931
}, function(err) {
7932
show_message('Failed to update region attributes! ');
7933
});
7934
return;
7935
}
7936
}
7937
7938
function annotation_editor_on_metadata_update_done(type, attr_id, update_count) {
7939
show_message('Updated ' + type + ' attributes of ' + update_count + ' ' + type + 's');
7940
// check if the updated attribute is one of the group variables
7941
var i, n, type, attr_id;
7942
n = _via_image_grid_group_var.length;
7943
var clear_all_group = false;
7944
for ( i = 0; i < n; ++i ) {
7945
if ( _via_image_grid_group_var[i].type === type &&
7946
_via_image_grid_group_var[i].name === attr_id ) {
7947
clear_all_group = true;
7948
break;
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.
7956
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
7957
if ( clear_all_group ) {
7958
image_grid_show_all_project_images();
7959
}
7960
}
7961
}
7962
7963
function annotation_editor_update_file_metadata(img_index_list, attr_id, new_value, new_checked) {
7964
return new Promise( function(ok_callback, err_callback) {
7965
var i, n, img_id, img_index;
7966
n = img_index_list.length;
7967
var update_count = 0;
7968
for ( i = 0; i < n; ++i ) {
7969
img_index = img_index_list[i];
7970
img_id = _via_image_id_list[img_index];
7971
7972
switch( _via_attributes['file'][attr_id].type ) {
7973
case 'text': // fallback
7974
case 'radio': // fallback
7975
case 'dropdown': // fallback
7976
case 'image':
7977
_via_img_metadata[img_id].file_attributes[attr_id] = new_value;
7978
update_count += 1;
7979
break;
7980
7981
case 'checkbox':
7982
var option_id = new_value;
7983
if ( _via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_id) ) {
7984
if ( typeof(_via_img_metadata[img_id].file_attributes[attr_id]) !== 'object' ) {
7985
var old_value = _via_img_metadata[img_id].file_attributes[attr_id];
7986
_via_img_metadata[img_id].file_attributes[attr_id] = {};
7987
if ( 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
}
7996
if ( new_checked ) {
7997
_via_img_metadata[img_id].file_attributes[attr_id][option_id] = true;
7998
} else {
7999
// false option values are not stored
8000
delete _via_img_metadata[img_id].file_attributes[attr_id][option_id];
8001
}
8002
update_count += 1;
8003
break;
8004
}
8005
}
8006
ok_callback(update_count);
8007
});
8008
}
8009
8010
function annotation_editor_update_region_metadata(img_index_list, region_id, attr_id, new_value, new_checked) {
8011
return new Promise( function(ok_callback, err_callback) {
8012
var i, n, img_id, img_index;
8013
n = img_index_list.length;
8014
var update_count = 0;
8015
var region_list = [];
8016
var j, m;
8017
8018
if ( region_id === -1 ) {
8019
// update all regions on a file (for image grid view)
8020
for ( i = 0; i < n; ++i ) {
8021
img_index = img_index_list[i];
8022
img_id = _via_image_id_list[img_index];
8023
8024
m = _via_img_metadata[img_id].regions.length;
8025
for ( j = 0; j < m; ++j ) {
8026
if ( ! image_grid_is_region_in_current_group( _via_img_metadata[img_id].regions[j].region_attributes ) ) {
8027
continue;
8028
}
8029
8030
switch( _via_attributes['region'][attr_id].type ) {
8031
case 'text': // fallback
8032
case 'dropdown': // fallback
8033
case 'radio': // fallback
8034
case 'image':
8035
_via_img_metadata[img_id].regions[j].region_attributes[attr_id] = new_value;
8036
update_count += 1;
8037
break;
8038
case 'checkbox':
8039
var option_id = new_value;
8040
if ( _via_img_metadata[img_id].regions[j].region_attributes.hasOwnProperty(attr_id) ) {
8041
if ( typeof(_via_img_metadata[img_id].regions[j].region_attributes[attr_id]) !== 'object' ) {
8042
var 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] = {}
8044
if ( 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
8053
if ( 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
8057
delete _via_img_metadata[img_id].regions[j].region_attributes[attr_id][option_id];
8058
}
8059
update_count += 1;
8060
break;
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)
8067
for ( i = 0; i < n; ++i ) {
8068
img_index = img_index_list[i];
8069
img_id = _via_image_id_list[img_index];
8070
8071
switch( _via_attributes['region'][attr_id].type ) {
8072
case 'text': // fallback
8073
case 'dropdown': // fallback
8074
case 'radio': // fallback
8075
case 'image':
8076
_via_img_metadata[img_id].regions[region_id].region_attributes[attr_id] = new_value;
8077
update_count += 1;
8078
break;
8079
case 'checkbox':
8080
var option_id = new_value;
8081
8082
if ( _via_img_metadata[img_id].regions[region_id].region_attributes.hasOwnProperty(attr_id) ) {
8083
if ( typeof(_via_img_metadata[img_id].regions[region_id].region_attributes[attr_id]) !== 'object' ) {
8084
var 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] = {};
8086
if ( 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
8095
if ( 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
8099
delete _via_img_metadata[img_id].regions[region_id].region_attributes[attr_id][option_id];
8100
}
8101
update_count += 1;
8102
break;
8103
}
8104
}
8105
}
8106
ok_callback(update_count);
8107
});
8108
}
8109
8110
function set_region_annotations_to_default_value(rid) {
8111
var attr_id;
8112
for ( attr_id in _via_attributes['region'] ) {
8113
var attr_type = _via_attributes['region'][attr_id].type;
8114
switch( attr_type ) {
8115
case 'text':
8116
var default_value = _via_attributes['region'][attr_id].default_value;
8117
if ( typeof(default_value) !== 'undefined' ) {
8118
_via_img_metadata[_via_image_id].regions[rid].region_attributes[attr_id] = default_value;
8119
}
8120
break;
8121
case 'image': // fallback
8122
case 'dropdown': // fallback
8123
case 'radio':
8124
_via_img_metadata[_via_image_id].regions[rid].region_attributes[attr_id] = '';
8125
var default_options = _via_attributes['region'][attr_id].default_options;
8126
if ( typeof(default_options) !== 'undefined' ) {
8127
_via_img_metadata[_via_image_id].regions[rid].region_attributes[attr_id] = Object.keys(default_options)[0];
8128
}
8129
break;
8130
8131
case 'checkbox':
8132
_via_img_metadata[_via_image_id].regions[rid].region_attributes[attr_id] = {};
8133
var default_options = _via_attributes['region'][attr_id].default_options;
8134
if ( typeof(default_options) !== 'underfined' ) {
8135
var option_id;
8136
for ( option_id in default_options ) {
8137
var default_value = default_options[option_id];
8138
if ( typeof(default_value) !== 'underfined' ) {
8139
_via_img_metadata[_via_image_id].regions[rid].region_attributes[attr_id][option_id] = default_value;
8140
}
8141
}
8142
}
8143
break;
8144
}
8145
}
8146
}
8147
8148
function set_file_annotations_to_default_value(image_id) {
8149
var attr_id;
8150
for ( attr_id in _via_attributes['file'] ) {
8151
var attr_type = _via_attributes['file'][attr_id].type;
8152
switch( attr_type ) {
8153
case 'text':
8154
var default_value = _via_attributes['file'][attr_id].default_value;
8155
_via_img_metadata[image_id].file_attributes[attr_id] = default_value;
8156
break;
8157
case 'image': // fallback
8158
case 'dropdown': // fallback
8159
case 'radio':
8160
_via_img_metadata[image_id].file_attributes[attr_id] = '';
8161
var default_options = _via_attributes['file'][attr_id].default_options;
8162
_via_img_metadata[image_id].file_attributes[attr_id] = Object.keys(default_options)[0];
8163
break;
8164
case 'checkbox':
8165
_via_img_metadata[image_id].file_attributes[attr_id] = {};
8166
var default_options = _via_attributes['file'][attr_id].default_options;
8167
var option_id;
8168
for ( option_id in default_options ) {
8169
var default_value = default_options[option_id];
8170
_via_img_metadata[image_id].file_attributes[attr_id][option_id] = default_value;
8171
}
8172
break;
8173
}
8174
}
8175
}
8176
8177
function annotation_editor_increase_panel_height() {
8178
var p = document.getElementById('annotation_editor_panel');
8179
if ( _via_settings.ui.annotation_editor_height < 95 ) {
8180
_via_settings.ui.annotation_editor_height += VIA_ANNOTATION_EDITOR_HEIGHT_CHANGE;
8181
p.style.height = _via_settings.ui.annotation_editor_height + '%';
8182
}
8183
}
8184
8185
function annotation_editor_decrease_panel_height() {
8186
var p = document.getElementById('annotation_editor_panel');
8187
if ( _via_settings.ui.annotation_editor_height > 10 ) {
8188
_via_settings.ui.annotation_editor_height -= VIA_ANNOTATION_EDITOR_HEIGHT_CHANGE;
8189
p.style.height = _via_settings.ui.annotation_editor_height + '%';
8190
}
8191
}
8192
8193
function annotation_editor_increase_content_size() {
8194
var p = document.getElementById('annotation_editor_panel');
8195
if ( _via_settings.ui.annotation_editor_fontsize < 1.6 ) {
8196
_via_settings.ui.annotation_editor_fontsize += VIA_ANNOTATION_EDITOR_FONTSIZE_CHANGE;
8197
p.style.fontSize = _via_settings.ui.annotation_editor_fontsize + 'rem';
8198
}
8199
}
8200
8201
function annotation_editor_decrease_content_size() {
8202
var p = document.getElementById('annotation_editor_panel');
8203
if ( _via_settings.ui.annotation_editor_fontsize > 0.4 ) {
8204
_via_settings.ui.annotation_editor_fontsize -= VIA_ANNOTATION_EDITOR_FONTSIZE_CHANGE;
8205
p.style.fontSize = _via_settings.ui.annotation_editor_fontsize + 'rem';
8206
}
8207
}
8208
8209
//
8210
// via project
8211
//
8212
function project_set_name(name) {
8213
_via_settings.project.name = name;
8214
8215
var p = document.getElementById('project_name');
8216
p.value = _via_settings.project.name;
8217
}
8218
8219
function project_init_default_project() {
8220
if ( ! _via_settings.hasOwnProperty('project') ) {
8221
_via_settings.project = {};
8222
}
8223
8224
project_set_name( project_get_default_project_name() );
8225
}
8226
8227
function project_on_name_update(p) {
8228
project_set_name(p.value);
8229
}
8230
8231
function project_get_default_project_name() {
8232
const now = new Date();
8233
var MONTH_SHORT_NAME = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
8234
var ts = now.getDate() + MONTH_SHORT_NAME[now.getMonth()] + now.getFullYear() +
8235
'_' + now.getHours() + 'h' + now.getMinutes() + 'm';
8236
8237
var project_name = 'via_project_' + ts;
8238
return project_name;
8239
}
8240
8241
function project_save_with_confirm() {
8242
var config = {'title':'Save Project' };
8243
var 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
8251
invoke_with_user_inputs(project_save_confirmed, input, config);
8252
}
8253
8254
function project_save_confirmed(input) {
8255
if ( input.project_name.value !== _via_settings.project.name ) {
8256
project_set_name(input.project_name.value);
8257
}
8258
8259
// via project
8260
var _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
8267
var filename = input.project_name.value + '.json';
8268
var data_blob = new Blob( [JSON.stringify(_via_project)],
8269
{type: 'text/json;charset=utf-8'});
8270
8271
save_data_to_local_file(data_blob, filename);
8272
8273
user_input_default_cancel_handler();
8274
}
8275
8276
function project_open_select_project_file() {
8277
if (invisible_file_input) {
8278
invisible_file_input.accept = '.json';
8279
invisible_file_input.onchange = project_open;
8280
invisible_file_input.removeAttribute('multiple');
8281
invisible_file_input.click();
8282
}
8283
}
8284
8285
function project_open(event) {
8286
var selected_file = event.target.files[0];
8287
load_text_file(selected_file, project_open_parse_json_file);
8288
}
8289
8290
function project_open_parse_json_file(project_file_data) {
8291
var d = JSON.parse(project_file_data);
8292
if ( d['_via_settings'] && d['_via_img_metadata'] && d['_via_attributes'] ) {
8293
// import settings
8294
project_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 = {};
8308
for ( var img_id in d['_via_img_metadata'] ) {
8309
if('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]) {
8313
if( !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
8318
set_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 {
8322
console.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
8329
if( d.hasOwnProperty('_via_image_id_list') ) {
8330
_via_image_id_list = d['_via_image_id_list'];
8331
for(var img_id_index in d['_via_image_id_list']) {
8332
var 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'];
8339
project_parse_via_attributes_from_img_metadata();
8340
var fattr_id_list = Object.keys(_via_attributes['file']);
8341
var rattr_id_list = Object.keys(_via_attributes['region']);
8342
if ( rattr_id_list.length ) {
8343
_via_attribute_being_updated = 'region';
8344
_via_current_attribute_id = rattr_id_list[0];
8345
} else {
8346
if ( fattr_id_list.length ) {
8347
_via_attribute_being_updated = 'file';
8348
_via_current_attribute_id = fattr_id_list[0];
8349
}
8350
}
8351
8352
if ( _via_settings.core.default_filepath !== '' ) {
8353
_via_file_resolve_all_to_default_filepath();
8354
}
8355
8356
show_message('Imported project [' + _via_settings['project'].name + '] with ' + _via_img_count + ' files.');
8357
8358
if ( _via_img_count > 0 ) {
8359
_via_show_img(0);
8360
update_img_fn_list();
8361
_via_reload_img_fn_list_table = true;
8362
}
8363
} else {
8364
show_message('Cannot import project from a corrupt file!');
8365
}
8366
}
8367
8368
function project_parse_via_attributes_from_img_metadata() {
8369
// parse _via_img_metadata to populate _via_attributes
8370
var img_id, fa, ra;
8371
8372
if ( ! _via_attributes.hasOwnProperty('file') ) {
8373
_via_attributes['file'] = {};
8374
}
8375
if ( ! _via_attributes.hasOwnProperty('region') ) {
8376
_via_attributes['region'] = {};
8377
}
8378
8379
for ( img_id in _via_img_metadata ) {
8380
// file attributes
8381
for ( fa in _via_img_metadata[img_id].file_attributes ) {
8382
if ( ! _via_attributes['file'].hasOwnProperty(fa) ) {
8383
_via_attributes['file'][fa] = {};
8384
_via_attributes['file'][fa]['type'] = 'text';
8385
}
8386
}
8387
// region attributes
8388
var ri;
8389
for ( ri = 0; ri < _via_img_metadata[img_id].regions.length; ++ri ) {
8390
for ( ra in _via_img_metadata[img_id].regions[ri].region_attributes ) {
8391
if ( ! _via_attributes['region'].hasOwnProperty(ra) ) {
8392
_via_attributes['region'][ra] = {};
8393
_via_attributes['region'][ra]['type'] = 'text';
8394
}
8395
}
8396
}
8397
}
8398
}
8399
8400
function 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)
8403
var k1;
8404
for ( k1 in s ) {
8405
if ( typeof( s[k1] ) === 'object' ) {
8406
var k2;
8407
for ( k2 in s[k1] ) {
8408
if ( typeof( s[k1][k2] ) === 'object' ) {
8409
var k3;
8410
for ( 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
8423
function project_file_remove_with_confirm() {
8424
var img_id = _via_image_id_list[_via_image_index];
8425
var filename = _via_img_metadata[img_id].filename;
8426
var region_count = _via_img_metadata[img_id].regions.length;
8427
8428
var config = {'title':'Remove File from Project' };
8429
var 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
8434
invoke_with_user_inputs(project_file_remove_confirmed, input, config);
8435
}
8436
8437
function project_file_remove_confirmed(input) {
8438
var img_index = input.img_index.value - 1;
8439
project_remove_file(img_index);
8440
8441
if ( img_index === _via_img_count ) {
8442
if ( _via_img_count === 0 ) {
8443
_via_current_image_loaded = false;
8444
show_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;
8452
update_img_fn_list();
8453
show_message('Removed file [' + input.filename.value + '] from project');
8454
user_input_default_cancel_handler();
8455
}
8456
8457
8458
function project_remove_file(img_index) {
8459
if ( img_index < 0 || img_index >= _via_img_count ) {
8460
console.log('project_remove_file(): invalid img_index ' + img_index);
8461
return;
8462
}
8463
var 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
8470
var img_fn_list_index = _via_img_fn_list_img_index_list.indexOf(img_index);
8471
if ( 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();
8478
img_fn_list_clear_css_classname('buffered');
8479
8480
_via_clear_reg_canvas();
8481
delete _via_img_metadata[img_id];
8482
delete _via_img_src[img_id];
8483
delete _via_img_fileref[img_id];
8484
8485
_via_img_count -= 1;
8486
}
8487
8488
function project_add_new_file(filename, size, file_id) {
8489
var img_id = file_id;
8490
if ( typeof(img_id) === 'undefined' ) {
8491
if ( typeof(size) === 'undefined' ) {
8492
size = -1;
8493
}
8494
img_id = _via_get_image_id(filename, size);
8495
}
8496
8497
if ( ! _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
}
8503
return img_id;
8504
}
8505
8506
function project_file_add_local(event) {
8507
var user_selected_images = event.target.files;
8508
var original_image_count = _via_img_count;
8509
8510
var new_img_index_list = [];
8511
var discarded_file_count = 0;
8512
for ( var i = 0; i < user_selected_images.length; ++i ) {
8513
var filetype = user_selected_images[i].type.substr(0, 5);
8514
if ( filetype === 'image' ) {
8515
// check which filename in project matches the user selected file
8516
var img_index = _via_image_filename_list.indexOf(user_selected_images[i].name);
8517
if( img_index === -1) {
8518
// a new file was added to project
8519
var new_img_id = project_add_new_file(user_selected_images[i].name,
8520
user_selected_images[i].size);
8521
_via_img_fileref[new_img_id] = user_selected_images[i];
8522
set_file_annotations_to_default_value(new_img_id);
8523
new_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
8526
var 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 {
8531
discarded_file_count += 1;
8532
}
8533
}
8534
8535
if ( _via_img_metadata ) {
8536
var status_msg = 'Loaded ' + new_img_index_list.length + ' images.';
8537
if ( discarded_file_count ) {
8538
status_msg += ' ( Discarded ' + discarded_file_count + ' non-image files! )';
8539
}
8540
show_message(status_msg);
8541
8542
if ( 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
}
8549
update_img_fn_list();
8550
} else {
8551
show_message("Please upload some image files!");
8552
}
8553
}
8554
8555
function project_file_add_abs_path_with_input() {
8556
var config = {'title':'Add File using Absolute Path' };
8557
var 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
8561
invoke_with_user_inputs(project_file_add_abs_path_input_done, input, config);
8562
}
8563
8564
function project_file_add_abs_path_input_done(input) {
8565
if ( input.absolute_path.value !== '' ) {
8566
var abs_path = input.absolute_path.value.trim();
8567
var img_id = project_file_add_url(abs_path);
8568
var img_index = _via_image_id_list.indexOf(img_id);
8569
_via_show_img(img_index);
8570
show_message('Added file at absolute path [' + abs_path + ']');
8571
update_img_fn_list();
8572
user_input_default_cancel_handler();
8573
} else {
8574
if ( input.absolute_path_list.value !== '' ) {
8575
var absolute_path_list_str = input.absolute_path_list.value;
8576
import_files_url_from_csv(absolute_path_list_str);
8577
}
8578
}
8579
}
8580
8581
function project_file_add_url_with_input() {
8582
var config = {'title':'Add File using URL' };
8583
var 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
8587
invoke_with_user_inputs(project_file_add_url_input_done, input, config);
8588
}
8589
8590
function project_file_add_url_input_done(input) {
8591
if ( input.url.value !== '' ) {
8592
var url = input.url.value.trim();
8593
var img_id = project_file_add_url(url);
8594
var img_index = _via_image_id_list.indexOf(img_id);
8595
show_message('Added file at url [' + url + ']');
8596
update_img_fn_list();
8597
_via_show_img(img_index);
8598
user_input_default_cancel_handler();
8599
} else {
8600
if ( input.url_list.value !== '' ) {
8601
var url_list_str = input.url_list.value;
8602
import_files_url_from_csv(url_list_str);
8603
}
8604
}
8605
}
8606
8607
function project_file_add_url(url) {
8608
if ( url !== '' ) {
8609
var size = -1; // convention: files added using url have size = -1
8610
var img_id = _via_get_image_id(url, size);
8611
8612
if ( ! _via_img_metadata.hasOwnProperty(img_id) ) {
8613
img_id = project_add_new_file(url);
8614
_via_img_src[img_id] = _via_img_metadata[img_id].filename;
8615
set_file_annotations_to_default_value(img_id);
8616
return img_id;
8617
}
8618
}
8619
}
8620
8621
function project_file_add_base64(filename, base64) {
8622
var size = -1; // convention: files added using url have size = -1
8623
var img_id = _via_get_image_id(filename, size);
8624
8625
if ( ! _via_img_metadata.hasOwnProperty(img_id) ) {
8626
img_id = project_add_new_file(filename, size);
8627
_via_img_src[img_id] = base64;
8628
set_file_annotations_to_default_value(img_id);
8629
}
8630
}
8631
8632
function project_file_load_on_fail(img_index) {
8633
var img_id = _via_image_id_list[img_index];
8634
_via_img_src[img_id] = '';
8635
_via_image_load_error[img_index] = true;
8636
img_fn_list_ith_entry_error(img_index, true);
8637
}
8638
8639
function project_file_load_on_success(img_index) {
8640
_via_image_load_error[img_index] = false;
8641
img_fn_list_ith_entry_error(img_index, false);
8642
}
8643
8644
function project_settings_toggle() {
8645
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.SETTINGS ) {
8646
show_single_image_view();
8647
} else {
8648
project_settings_show();
8649
}
8650
}
8651
8652
function project_settings_show() {
8653
set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.SETTINGS);
8654
}
8655
8656
function project_filepath_add_from_input(p, button) {
8657
var new_path = document.getElementById(p).value.trim();
8658
var img_index = parseInt(button.getAttribute('value'));
8659
project_filepath_add(new_path);
8660
_via_show_img(img_index);
8661
}
8662
8663
function project_filepath_add(new_path) {
8664
if ( path === '' ) {
8665
return;
8666
}
8667
8668
if ( _via_settings.core.filepath.hasOwnProperty(new_path) ) {
8669
return;
8670
} else {
8671
var largest_order = 0;
8672
var path;
8673
for ( path in _via_settings.core.filepath ) {
8674
if ( _via_settings.core.filepath[path] > largest_order ) {
8675
largest_order = _via_settings.core.filepath[path];
8676
}
8677
}
8678
_via_settings.core.filepath[new_path] = largest_order + 1;
8679
8680
}
8681
}
8682
8683
function project_filepath_del(path) {
8684
if ( _via_settings.core.filepath.hasOwnProperty(path) ) {
8685
delete _via_settings.core.filepath[path];
8686
}
8687
}
8688
8689
function project_save_attributes() {
8690
var blob_attr = {type: 'application/json;charset=utf-8'};
8691
var all_region_data_blob = new Blob( [ JSON.stringify(_via_attributes) ], blob_attr);
8692
8693
save_data_to_local_file(all_region_data_blob, _via_settings.project.name + '_attributes.json');
8694
}
8695
8696
function project_import_attributes_from_file(event) {
8697
var selected_files = event.target.files;
8698
for ( var i = 0; i < selected_files.length; ++i ) {
8699
var file = selected_files[i];
8700
load_text_file(file, project_import_attributes_from_json);
8701
}
8702
}
8703
8704
function project_import_attributes_from_json(data) {
8705
try {
8706
var d = JSON.parse(data);
8707
var attr;
8708
var fattr_count = 0;
8709
var rattr_count = 0;
8710
// process file attributes
8711
for ( attr in d['file'] ) {
8712
_via_attributes['file'][attr] = JSON.parse( JSON.stringify( d['file'][attr] ) );
8713
fattr_count += 1;
8714
}
8715
8716
// process region attributes
8717
for ( attr in d['region'] ) {
8718
_via_attributes['region'][attr] = JSON.parse( JSON.stringify( d['region'][attr] ) );
8719
rattr_count += 1;
8720
}
8721
8722
if ( fattr_count > 0 || rattr_count > 0 ) {
8723
var fattr_id_list = Object.keys(_via_attributes['file']);
8724
var rattr_id_list = Object.keys(_via_attributes['region']);
8725
if ( rattr_id_list.length ) {
8726
_via_attribute_being_updated = 'region';
8727
_via_current_attribute_id = rattr_id_list[0];
8728
} else {
8729
if ( fattr_id_list.length ) {
8730
_via_attribute_being_updated = 'file';
8731
_via_current_attribute_id = fattr_id_list[0];
8732
}
8733
}
8734
attribute_update_panel_set_active_button();
8735
update_attributes_update_panel();
8736
annotation_editor_update_content();
8737
}
8738
show_message('Imported ' + fattr_count + ' file attributes and '
8739
+ rattr_count + ' region attributes');
8740
} catch (error) {
8741
show_message('Failed to import attributes: [' + error + ']');
8742
}
8743
}
8744
8745
//
8746
// image grid
8747
//
8748
function image_grid_init() {
8749
var p = document.getElementById('image_grid_content');
8750
p.focus();
8751
p.addEventListener('mousedown', image_grid_mousedown_handler, false);
8752
p.addEventListener('mouseup', image_grid_mouseup_handler, false);
8753
p.addEventListener('dblclick', image_grid_dblclick_handler, false);
8754
8755
image_grid_set_content_panel_height_fixed();
8756
8757
// select policy as defined in settings
8758
var i, option;
8759
var p = document.getElementById('image_grid_show_image_policy');
8760
var n = p.options.length;
8761
for ( i = 0; i < n; ++i ) {
8762
if ( p.options[i].value === _via_settings.ui.image_grid.show_image_policy ) {
8763
p.selectedIndex = i;
8764
break;
8765
}
8766
}
8767
}
8768
8769
function image_grid_update() {
8770
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
8771
image_grid_set_content( _via_image_grid_img_index_list );
8772
}
8773
}
8774
8775
function image_grid_toggle() {
8776
var p = document.getElementById('toolbar_image_grid_toggle');
8777
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE_GRID ) {
8778
image_grid_clear_all_groups();
8779
show_single_image_view();
8780
} else {
8781
show_image_grid_view();
8782
}
8783
}
8784
8785
function image_grid_show_all_project_images() {
8786
var all_img_index_list = [];
8787
var i, n;
8788
//n = _via_image_id_list.length;
8789
n = _via_img_fn_list_img_index_list.length;
8790
for ( i = 0; i < n; ++i ) {
8791
all_img_index_list.push( _via_img_fn_list_img_index_list[i] );
8792
}
8793
image_grid_clear_all_groups();
8794
8795
var p = document.getElementById('image_grid_toolbar_group_by_select');
8796
p.selectedIndex = 0;
8797
8798
image_grid_set_content(all_img_index_list);
8799
}
8800
8801
function image_grid_clear_all_groups() {
8802
var i, n;
8803
n = _via_image_grid_group_var.length;
8804
for ( i = 0; i < n; ++i ) {
8805
image_grid_remove_html_group_panel( _via_image_grid_group_var[i] );
8806
image_grid_group_by_select_set_disabled( _via_image_grid_group_var[i].type,
8807
_via_image_grid_group_var[i].name,
8808
false);
8809
}
8810
_via_image_grid_group = {};
8811
_via_image_grid_group_var = [];
8812
8813
}
8814
8815
function image_grid_set_content(img_index_list) {
8816
if ( img_index_list.length === 0 ) {
8817
return;
8818
}
8819
if ( _via_image_grid_load_ongoing ) {
8820
return;
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
8826
document.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
8833
image_grid_clear_content();
8834
image_grid_set_content_panel_height_fixed();
8835
_via_image_grid_load_ongoing = true;
8836
8837
var n = _via_image_grid_img_index_list.length;
8838
switch ( _via_settings.ui.image_grid.show_image_policy ) {
8839
case 'all':
8840
_via_image_grid_page_img_index_list = _via_image_grid_img_index_list.slice(0);
8841
break;
8842
case 'first_mid_last':
8843
if ( n < 3 ) {
8844
var i;
8845
for ( 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
}
8853
break;
8854
case 'even_indexed':
8855
var i;
8856
for ( i = 0; i < n; ++i ) {
8857
if ( 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
}
8861
break;
8862
case 'odd_indexed':
8863
var i;
8864
for ( i = 0; i < n; ++i ) {
8865
if ( 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
}
8869
break;
8870
case 'gap5': // fallback
8871
case 'gap25': // fallback
8872
case 'gap50': // fallback
8873
var del = parseInt( _via_settings.ui.image_grid.show_image_policy.substr( 'gap'.length ) );
8874
var i;
8875
for ( i = 0; i < n; i = i + del ) {
8876
_via_image_grid_page_img_index_list.push( _via_image_grid_img_index_list[i] );
8877
}
8878
break;
8879
}
8880
8881
_via_image_grid_visible_img_index_list = [];
8882
8883
image_grid_update_sel_count_html();
8884
annotation_editor_update_content();
8885
8886
image_grid_content_append_img( _via_image_grid_page_first_index );
8887
8888
show_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
8893
function image_grid_clear_content() {
8894
var img_container = document.getElementById('image_grid_content_img');
8895
var img_rshape = document.getElementById('image_grid_content_rshape');
8896
img_container.innerHTML = '';
8897
img_rshape.innerHTML = '';
8898
_via_image_grid_visible_img_index_list = [];
8899
}
8900
8901
function image_grid_set_content_panel_height_fixed() {
8902
var pc = document.getElementById('image_grid_content');
8903
var de = document.documentElement;
8904
pc.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.
8910
function image_grid_content_append_img( img_grid_index ) {
8911
var img_index = _via_image_grid_page_img_index_list[img_grid_index];
8912
var html_img_id = image_grid_get_html_img_id(img_index);
8913
var img_id = _via_image_id_list[img_index];
8914
var e = document.createElement('img');
8915
if ( _via_img_fileref[img_id] instanceof File ) {
8916
var img_reader = new FileReader();
8917
img_reader.addEventListener( "error", function() {
8918
//@todo
8919
}, false);
8920
img_reader.addEventListener( "load", function() {
8921
e.src = img_reader.result;
8922
}, false);
8923
img_reader.readAsDataURL( _via_img_fileref[img_id] );
8924
} else {
8925
e.src = _via_img_src[img_id];
8926
}
8927
e.setAttribute('id', html_img_id);
8928
e.setAttribute('height', _via_settings.ui.image_grid.img_height + 'px');
8929
e.setAttribute('title', '[' + (img_index+1) + '] ' + _via_img_metadata[img_id].filename);
8930
8931
e.addEventListener('load', image_grid_on_img_load, false);
8932
e.addEventListener('error', image_grid_on_img_error, false);
8933
document.getElementById('image_grid_content_img').appendChild(e);
8934
}
8935
8936
function image_grid_on_img_load(e) {
8937
var img = e.target;
8938
var img_index = image_grid_parse_html_img_id(img.id);
8939
project_file_load_on_success(img_index);
8940
8941
image_grid_add_img_if_possible(img);
8942
}
8943
8944
function image_grid_on_img_error(e) {
8945
var img = e.target;
8946
var img_index = image_grid_parse_html_img_id(img.id);
8947
project_file_load_on_fail(img_index);
8948
image_grid_add_img_if_possible(img);
8949
}
8950
8951
function image_grid_add_img_if_possible(img) {
8952
var img_index = image_grid_parse_html_img_id(img.id);
8953
8954
var p = document.getElementById('image_grid_content_img');
8955
var img_bottom_right_corner = parseInt(img.offsetTop) + parseInt(img.height);
8956
if ( p.clientHeight < img_bottom_right_corner ) {
8957
// stop as addition of this image caused overflow of parent container
8958
var img_container = document.getElementById('image_grid_content_img');
8959
img_container.removeChild(img);
8960
8961
if ( _via_settings.ui.image_grid.show_region_shape ) {
8962
image_grid_page_show_all_regions();
8963
}
8964
_via_image_grid_load_ongoing = false;
8965
8966
var 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
8970
var info = document.getElementById('image_grid_nav');
8971
var html = [];
8972
var first_index = _via_image_grid_page_first_index;
8973
var last_index = _via_image_grid_page_last_index - 1;
8974
html.push('<span>Showing&nbsp;' + (first_index + 1) +
8975
' to ' + (last_index + 1) + '&nbsp;:</span>');
8976
if ( _via_image_grid_stack_prev_page.length ) {
8977
html.push('<span class="text_button" onclick="image_grid_page_prev()">Prev</span>');
8978
} else {
8979
html.push('<span>Prev</span>');
8980
}
8981
html.push('<span class="text_button" onclick="image_grid_page_next()">Next</span');
8982
info.innerHTML = html.join('');
8983
} else {
8984
// process this image and trigger addition of next image in sequence
8985
var img_fn_list_index = _via_image_grid_page_img_index_list.indexOf(img_index);
8986
var next_img_fn_list_index = img_fn_list_index + 1;
8987
8988
_via_image_grid_visible_img_index_list.push( img_index );
8989
var is_selected = ( _via_image_grid_selected_img_index_list.indexOf(img_index) !== -1 );
8990
if ( ! is_selected ) {
8991
image_grid_update_img_select(img_index, 'unselect');
8992
}
8993
8994
if ( next_img_fn_list_index !== _via_image_grid_page_img_index_list.length ) {
8995
if ( _via_image_grid_load_ongoing ) {
8996
image_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
9001
var info = document.getElementById('image_grid_nav');
9002
var html = [];
9003
html.push('<span>Cancelled&nbsp;:</span>');
9004
if ( _via_image_grid_stack_prev_page.length ) {
9005
html.push('<span class="text_button" onclick="image_grid_page_prev()">Prev</span>');
9006
} else {
9007
html.push('<span>Prev</span>');
9008
}
9009
html.push('<span class="text_button" onclick="image_grid_page_next()">Next</span');
9010
info.innerHTML = html.join('');
9011
}
9012
} else {
9013
// last page
9014
var index = _via_image_grid_page_img_index_list.indexOf(img_index);
9015
_via_image_grid_page_last_index = index;
9016
9017
if ( _via_settings.ui.image_grid.show_region_shape ) {
9018
image_grid_page_show_all_regions();
9019
}
9020
_via_image_grid_load_ongoing = false;
9021
9022
// setup prev, next navigation
9023
var info = document.getElementById('image_grid_nav');
9024
var html = [];
9025
var first_index = _via_image_grid_page_first_index;
9026
var last_index = _via_image_grid_page_last_index;
9027
html.push('<span>Showing&nbsp;' + (first_index + 1) +
9028
' to ' + (last_index + 1) + ' (end)&nbsp;</span>');
9029
if ( _via_image_grid_stack_prev_page.length ) {
9030
html.push('<span class="text_button" onclick="image_grid_page_prev()">Prev</span>');
9031
} else {
9032
html.push('<span>Prev</span>');
9033
}
9034
html.push('<span>Next</span');
9035
9036
info.innerHTML = html.join('');
9037
}
9038
}
9039
}
9040
9041
function image_grid_onchange_show_image_policy(p) {
9042
_via_settings.ui.image_grid.show_image_policy = p.options[p.selectedIndex].value;
9043
image_grid_set_content(_via_image_grid_img_index_list);
9044
}
9045
9046
function image_grid_page_show_all_regions() {
9047
var all_promises = [];
9048
if ( _via_settings.ui.image_grid.show_region_shape ) {
9049
var p = document.getElementById('image_grid_content_img');
9050
var n = p.childNodes.length;
9051
var i;
9052
for ( i = 0; i < n; ++i ) {
9053
// draw region shape into global canvas for image grid
9054
var img_index = image_grid_parse_html_img_id( p.childNodes[i].id );
9055
var img_param = []; // [width, height, originalWidth, originalHeight, x, y]
9056
img_param.push( parseInt(p.childNodes[i].width) );
9057
img_param.push( parseInt(p.childNodes[i].height) );
9058
img_param.push( parseInt(p.childNodes[i].naturalWidth) );
9059
img_param.push( parseInt(p.childNodes[i].naturalHeight) );
9060
img_param.push( parseInt(p.childNodes[i].offsetLeft) + parseInt(p.childNodes[i].clientLeft) );
9061
img_param.push( parseInt(p.childNodes[i].offsetTop) + parseInt(p.childNodes[i].clientTop) );
9062
var promise = image_grid_show_region_shape( img_index, img_param );
9063
all_promises.push( promise );
9064
}
9065
// @todo: ensure that all promises are fulfilled
9066
}
9067
}
9068
9069
function image_grid_is_region_in_current_group(r) {
9070
var i, n;
9071
n = _via_image_grid_group_var.length;
9072
if ( n === 0 ) {
9073
return true;
9074
}
9075
9076
for ( i = 0; i < n; ++i ) {
9077
if ( _via_image_grid_group_var[i].type === 'region' ) {
9078
var group_value = _via_image_grid_group_var[i].values[ _via_image_grid_group_var[i].current_value_index ];
9079
if ( r[_via_image_grid_group_var[i].name] != group_value ) {
9080
return false;
9081
}
9082
}
9083
}
9084
return true;
9085
}
9086
9087
function image_grid_show_region_shape(img_index, img_param) {
9088
return new Promise( function(ok_callback, err_callback) {
9089
var i;
9090
var img_id = _via_image_id_list[img_index];
9091
var html_img_id = image_grid_get_html_img_id(img_index);
9092
var n = _via_img_metadata[img_id].regions.length;
9093
var is_in_group = false;
9094
for ( i = 0; i < n; ++i ) {
9095
if ( ! 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
9097
continue;
9098
}
9099
9100
var r = _via_img_metadata[img_id].regions[i].shape_attributes;
9101
var dimg; // region coordinates in original image space
9102
switch( r.name ) {
9103
case VIA_REGION_SHAPE.RECT:
9104
dimg = [ r['x'], r['y'], r['x']+r['width'], r['y']+r['height'] ];
9105
break;
9106
case VIA_REGION_SHAPE.CIRCLE:
9107
dimg = [ r['cx'], r['cy'], r['cx']+r['r'], r['cy']+r['r'] ];
9108
break;
9109
case VIA_REGION_SHAPE.ELLIPSE:
9110
dimg = [ r['cx'], r['cy'], r['cx']+r['rx'], r['cy']+r['ry'] ];
9111
break;
9112
case VIA_REGION_SHAPE.POLYLINE: // handled by POLYGON
9113
case VIA_REGION_SHAPE.POLYGON:
9114
var j;
9115
dimg = [];
9116
for ( j = 0; j < r['all_points_x'].length; ++j ) {
9117
dimg.push( r['all_points_x'][j] );
9118
dimg.push( r['all_points_y'][j] );
9119
}
9120
break;
9121
case VIA_REGION_SHAPE.POINT:
9122
dimg = [ r['cx'], r['cy'] ];
9123
break;
9124
}
9125
var scale_factor = img_param[1] / img_param[3]; // new_height / original height
9126
var offset_x = img_param[4];
9127
var offset_y = img_param[5];
9128
var r2 = new _via_region( r.name, i, dimg, scale_factor, offset_x, offset_y);
9129
var r2_svg = r2.get_svg_element();
9130
r2_svg.setAttribute('id', image_grid_get_html_region_id(img_index, i));
9131
r2_svg.setAttribute('class', html_img_id);
9132
r2_svg.setAttribute('fill', _via_settings.ui.image_grid.rshape_fill);
9133
//r2_svg.setAttribute('fill-opacity', _via_settings.ui.image_grid.rshape_fill_opacity);
9134
r2_svg.setAttribute('stroke', _via_settings.ui.image_grid.rshape_stroke);
9135
r2_svg.setAttribute('stroke-width', _via_settings.ui.image_grid.rshape_stroke_width);
9136
9137
document.getElementById('image_grid_content_rshape').appendChild(r2_svg);
9138
}
9139
});
9140
}
9141
9142
function image_grid_image_size_increase() {
9143
var 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;
9147
image_grid_update();
9148
}
9149
9150
function image_grid_image_size_decrease() {
9151
var new_img_height = _via_settings.ui.image_grid.img_height - VIA_IMAGE_GRID_IMG_HEIGHT_CHANGE;
9152
if ( new_img_height > 1 ) {
9153
_via_settings.ui.image_grid.img_height = new_img_height;
9154
_via_image_grid_page_last_index = null;
9155
image_grid_update();
9156
}
9157
}
9158
9159
function image_grid_image_size_reset() {
9160
var new_img_height = _via_settings.ui.image_grid.img_height;
9161
if ( new_img_height > 1 ) {
9162
_via_settings.ui.image_grid.img_height = new_img_height;
9163
_via_image_grid_page_last_index = null;
9164
image_grid_update();
9165
}
9166
}
9167
9168
function image_grid_mousedown_handler(e) {
9169
e.preventDefault();
9170
_via_image_grid_mousedown_img_index = image_grid_parse_html_img_id(e.target.id);
9171
}
9172
9173
function image_grid_mouseup_handler(e) {
9174
e.preventDefault();
9175
var 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);
9177
if ( isNaN(_via_image_grid_mousedown_img_index) ||
9178
isNaN(_via_image_grid_mouseup_img_index)) {
9179
last_mouseup_img_index = _via_image_grid_img_index_list[0];
9180
image_grid_group_select_none();
9181
return;
9182
}
9183
9184
var mousedown_img_arr_index = _via_image_grid_img_index_list.indexOf(_via_image_grid_mousedown_img_index);
9185
var mouseup_img_arr_index = _via_image_grid_img_index_list.indexOf(_via_image_grid_mouseup_img_index);
9186
9187
var start = -1;
9188
var end = -1;
9189
var operation = 'select'; // {'select', 'unselect', 'toggle'}
9190
if ( mousedown_img_arr_index === mouseup_img_arr_index ) {
9191
if ( e.shiftKey ) {
9192
// select all elements until this element
9193
start = _via_image_grid_img_index_list.indexOf(last_mouseup_img_index) + 1;
9194
end = mouseup_img_arr_index + 1;
9195
} else {
9196
// toggle selection of single image
9197
start = mousedown_img_arr_index;
9198
end = start + 1;
9199
operation = 'toggle';
9200
}
9201
} else {
9202
if ( mousedown_img_arr_index < mouseup_img_arr_index ) {
9203
start = mousedown_img_arr_index;
9204
end = mouseup_img_arr_index + 1;
9205
} else {
9206
start = mouseup_img_arr_index + 1;
9207
end = mousedown_img_arr_index;
9208
}
9209
operation = 'toggle';
9210
}
9211
9212
if ( start > end ) {
9213
return;
9214
}
9215
9216
var i, img_index;
9217
for ( i = start; i < end; ++i ) {
9218
img_index = _via_image_grid_img_index_list[i];
9219
image_grid_update_img_select(img_index, operation);
9220
}
9221
image_grid_update_sel_count_html();
9222
annotation_editor_update_content();
9223
}
9224
9225
function image_grid_update_sel_count_html() {
9226
document.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'}
9230
function image_grid_update_img_select(img_index, state) {
9231
var html_img_id = image_grid_get_html_img_id(img_index);
9232
var is_selected = ( _via_image_grid_selected_img_index_list.indexOf(img_index) !== -1 );
9233
if (state === 'toggle' ) {
9234
if ( is_selected ) {
9235
state = 'unselect';
9236
} else {
9237
state = 'select';
9238
}
9239
}
9240
9241
switch(state) {
9242
case 'select':
9243
if ( ! is_selected ) {
9244
_via_image_grid_selected_img_index_list.push(img_index);
9245
}
9246
if ( _via_image_grid_visible_img_index_list.indexOf(img_index) !== -1 ) {
9247
document.getElementById(html_img_id).classList.remove('not_sel');
9248
}
9249
break;
9250
case 'unselect':
9251
if ( is_selected ) {
9252
var 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
}
9255
if ( _via_image_grid_visible_img_index_list.indexOf(img_index) !== -1 ) {
9256
document.getElementById(html_img_id).classList.add('not_sel');
9257
}
9258
break;
9259
}
9260
}
9261
9262
function image_grid_group_select_all() {
9263
image_grid_group_set_all_selection_state('select');
9264
image_grid_update_sel_count_html();
9265
annotation_editor_update_content();
9266
show_message('Selected all images in the current group');
9267
}
9268
9269
function image_grid_group_select_none() {
9270
image_grid_group_set_all_selection_state('unselect');
9271
image_grid_update_sel_count_html();
9272
annotation_editor_update_content();
9273
show_message('Removed selection of all images in the current group');
9274
}
9275
9276
function image_grid_group_set_all_selection_state(state) {
9277
var i, img_index;
9278
for ( i = 0; i < _via_image_grid_img_index_list.length; ++i ) {
9279
img_index = _via_image_grid_img_index_list[i];
9280
image_grid_update_img_select(img_index, state);
9281
}
9282
}
9283
9284
function image_grid_group_toggle_select_all() {
9285
if ( _via_image_grid_selected_img_index_list.length === _via_image_grid_img_index_list.length ) {
9286
image_grid_group_select_none();
9287
} else {
9288
image_grid_group_select_all();
9289
}
9290
}
9291
9292
function image_grid_parse_html_img_id(html_img_id) {
9293
var img_index = html_img_id.substr(2);
9294
return parseInt(img_index);
9295
}
9296
9297
function image_grid_get_html_img_id(img_index) {
9298
return 'im' + img_index;
9299
}
9300
9301
function image_grid_parse_html_region_id(html_region_id) {
9302
var chunks = html_region_id.split('_');
9303
if ( chunks.length === 2 ) {
9304
var img_index = parseInt(chunks[0].substr(2));
9305
var region_id = parseInt(chunks[1].substr(2));
9306
return {'img_index':img_index, 'region_id':region_id};
9307
} else {
9308
console.log('image_grid_parse_html_region_id(): invalid html_region_id');
9309
return {};
9310
}
9311
}
9312
9313
function image_grid_get_html_region_id(img_index, region_id) {
9314
return image_grid_get_html_img_id(img_index) + '_rs' + region_id;
9315
}
9316
9317
function image_grid_dblclick_handler(e) {
9318
_via_image_index = image_grid_parse_html_img_id(e.target.id);
9319
show_single_image_view();
9320
}
9321
9322
function image_grid_toolbar_update_group_by_select() {
9323
var p = document.getElementById('image_grid_toolbar_group_by_select');
9324
p.innerHTML = '';
9325
9326
var o = document.createElement('option');
9327
o.setAttribute('value', '');
9328
o.setAttribute('selected', 'selected');
9329
o.innerHTML = 'All Images';
9330
p.appendChild(o);
9331
9332
// add file attributes
9333
var fattr;
9334
for ( fattr in _via_attributes['file'] ) {
9335
var o = document.createElement('option');
9336
o.setAttribute('value', image_grid_toolbar_group_by_select_get_html_id('file', fattr));
9337
o.innerHTML = '[file] ' + fattr;
9338
p.appendChild(o);
9339
}
9340
9341
// add region attributes
9342
var rattr;
9343
for ( rattr in _via_attributes['region'] ) {
9344
var o = document.createElement('option');
9345
o.setAttribute('value', image_grid_toolbar_group_by_select_get_html_id('region', rattr));
9346
o.innerHTML = '[region] ' + rattr;
9347
p.appendChild(o);
9348
}
9349
}
9350
9351
function image_grid_toolbar_group_by_select_get_html_id(type, name) {
9352
if ( type === 'file' ) {
9353
return 'f_' + name;
9354
}
9355
if ( type === 'region' ) {
9356
return 'r_' + name;
9357
}
9358
}
9359
9360
function image_grid_toolbar_group_by_select_parse_html_id(id) {
9361
if ( id.startsWith('f_') ) {
9362
return { 'attr_type':'file', 'attr_name':id.substr(2) };
9363
}
9364
if ( id.startsWith('r_') ) {
9365
return { 'attr_type':'region', 'attr_name':id.substr(2) };
9366
}
9367
}
9368
9369
function image_grid_toolbar_onchange_group_by_select(p) {
9370
if ( p.options[p.selectedIndex].value === '' ) {
9371
image_grid_show_all_project_images();
9372
return;
9373
}
9374
9375
var v = image_grid_toolbar_group_by_select_parse_html_id( p.options[p.selectedIndex].value );
9376
var attr_type = v.attr_type;
9377
var attr_name = v.attr_name;
9378
image_grid_group_by(attr_type, attr_name);
9379
9380
image_grid_group_by_select_set_disabled(attr_type, attr_name, true);
9381
p.blur(); // to avoid adding new groups using keyboard keys as dropdown is still in focus
9382
}
9383
9384
function image_grid_remove_html_group_panel(d) {
9385
var p = document.getElementById('group_toolbar_' + d.group_index);
9386
document.getElementById('image_grid_group_panel').removeChild(p);
9387
}
9388
9389
function image_grid_add_html_group_panel(d) {
9390
var p = document.createElement('div');
9391
p.classList.add('image_grid_group_toolbar');
9392
p.setAttribute('id', 'group_toolbar_' + d.group_index);
9393
9394
var del = document.createElement('span');
9395
del.classList.add('text_button');
9396
del.setAttribute('onclick', 'image_grid_remove_group_by(this)');
9397
del.innerHTML = '&times;';
9398
p.appendChild(del);
9399
9400
var prev = document.createElement('button');
9401
prev.innerHTML = '<';
9402
prev.setAttribute('value', d.group_index);
9403
prev.setAttribute('onclick', 'image_grid_group_prev(this)');
9404
p.appendChild(prev);
9405
9406
var sel = document.createElement('select');
9407
sel.setAttribute('id', image_grid_group_select_get_html_id(d.group_index));
9408
sel.setAttribute('onchange', 'image_grid_group_value_onchange(this)');
9409
var i, value;
9410
var n = d.values.length;
9411
var current_value = d.values[ d.current_value_index ];
9412
for ( i = 0; i < n; ++i ) {
9413
value = d.values[i];
9414
var o = document.createElement('option');
9415
o.setAttribute('value', value);
9416
o.innerHTML = (i+1) + '/' + n + ': ' + d.name + ' = ' + value;
9417
if ( value === current_value ) {
9418
o.setAttribute('selected', 'selected');
9419
}
9420
9421
sel.appendChild(o);
9422
}
9423
p.appendChild(sel);
9424
9425
var next = document.createElement('button');
9426
next.innerHTML = '>';
9427
next.setAttribute('value', d.group_index);
9428
next.setAttribute('onclick', 'image_grid_group_next(this)');
9429
p.appendChild(next);
9430
9431
document.getElementById('image_grid_group_panel').appendChild(p);
9432
}
9433
9434
function image_grid_group_panel_set_selected_value(group_index) {
9435
var sel = document.getElementById(image_grid_group_select_get_html_id(group_index));
9436
sel.selectedIndex = _via_image_grid_group_var[group_index].current_value_index;
9437
}
9438
9439
function image_grid_group_panel_set_options(group_index) {
9440
var sel = document.getElementById(image_grid_group_select_get_html_id(group_index));
9441
sel.innerHTML = '';
9442
9443
var i, value;
9444
var n = _via_image_grid_group_var[group_index].values.length;
9445
var name = _via_image_grid_group_var[group_index].name;
9446
var current_value = _via_image_grid_group_var[group_index].values[ _via_image_grid_group_var[group_index].current_value_index ]
9447
for ( i = 0; i < n; ++i ) {
9448
value = _via_image_grid_group_var[group_index].values[i];
9449
var o = document.createElement('option');
9450
o.setAttribute('value', value);
9451
o.innerHTML = (i+1) + '/' + n + ': ' + name + ' = ' + value;
9452
if ( value === current_value ) {
9453
o.setAttribute('selected', 'selected');
9454
}
9455
sel.appendChild(o);
9456
}
9457
}
9458
9459
function image_grid_group_select_get_html_id(group_index) {
9460
return 'gi_' + group_index;
9461
}
9462
9463
function image_grid_group_select_parse_html_id(id) {
9464
return parseInt(id.substr(3));
9465
}
9466
9467
function image_grid_group_by_select_set_disabled(type, name, is_disabled) {
9468
var p = document.getElementById('image_grid_toolbar_group_by_select');
9469
var sel_option_value = image_grid_toolbar_group_by_select_get_html_id(type, name);
9470
9471
var n = p.options.length;
9472
var option_value;
9473
var i;
9474
for ( i = 0; i < n; ++i ) {
9475
if ( sel_option_value === p.options[i].value ) {
9476
if ( is_disabled ) {
9477
p.options[i].setAttribute('disabled', 'disabled');
9478
} else {
9479
p.options[i].removeAttribute('disabled');
9480
}
9481
break;
9482
}
9483
}
9484
}
9485
9486
function image_grid_remove_group_by(p) {
9487
var prefix = 'group_toolbar_';
9488
var group_index = parseInt( p.parentNode.id.substr( prefix.length ) );
9489
9490
if ( group_index === 0 ) {
9491
image_grid_show_all_project_images();
9492
} else {
9493
// merge all groups that are child of group_index
9494
image_grid_group_by_merge(_via_image_grid_group, 0, group_index);
9495
9496
var n = _via_image_grid_group_var.length;
9497
var p = document.getElementById('image_grid_group_panel');
9498
var group_panel_id;
9499
var i;
9500
for ( i = group_index; i < n; ++i ) {
9501
image_grid_remove_html_group_panel( _via_image_grid_group_var[i] );
9502
image_grid_group_by_select_set_disabled( _via_image_grid_group_var[i].type,
9503
_via_image_grid_group_var[i].name,
9504
false);
9505
}
9506
_via_image_grid_group_var.splice(group_index);
9507
9508
image_grid_set_content_to_current_group();
9509
}
9510
}
9511
9512
function image_grid_group_by(type, name) {
9513
if ( Object.keys(_via_image_grid_group).length === 0 ) {
9514
// first group
9515
var img_index_array = [];
9516
var n = _via_img_fn_list_img_index_list.length;
9517
var i;
9518
for ( i = 0; i < n; ++i ) {
9519
img_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);
9523
var 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
9527
image_grid_add_html_group_panel(_via_image_grid_group_var[0]);
9528
} else {
9529
image_grid_group_split_all_arrays( _via_image_grid_group, type, name );
9530
9531
var i, n, value;
9532
var current_group_value = _via_image_grid_group;
9533
n = _via_image_grid_group_var.length;
9534
9535
for ( i = 0; i < n; ++i ) {
9536
value = _via_image_grid_group_var[i].values[ _via_image_grid_group_var[i].current_value_index ];
9537
current_group_value = current_group_value[ value ];
9538
}
9539
var new_group_values = Object.keys(current_group_value);
9540
var 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 } );
9542
image_grid_add_html_group_panel( _via_image_grid_group_var[group_var_index] );
9543
}
9544
9545
image_grid_set_content_to_current_group();
9546
}
9547
9548
function image_grid_group_by_merge(group, current_level, target_level) {
9549
var child_value;
9550
var group_data = [];
9551
if ( current_level === target_level ) {
9552
return image_grid_group_by_collapse(group);
9553
} else {
9554
for ( child_value in group ) {
9555
group[child_value] = image_grid_group_by_merge(group[child_value], current_level + 1, target_level);
9556
}
9557
}
9558
}
9559
9560
function image_grid_group_by_collapse(group) {
9561
var child_value;
9562
var child_collapsed_value;
9563
var group_data = [];
9564
for ( child_value in group ) {
9565
if ( Array.isArray(group[child_value]) ) {
9566
group_data = group_data.concat(group[child_value]);
9567
} else {
9568
group_data = group_data.concat(image_grid_group_by_collapse(group[child_value]));
9569
}
9570
}
9571
return group_data;
9572
}
9573
9574
// recursively collapse all arrays to list
9575
function image_grid_group_split_all_arrays(group, type, name) {
9576
if ( Array.isArray(group) ) {
9577
return image_grid_split_array_to_group(group, type, name);
9578
} else {
9579
var group_value;
9580
for ( group_value in group ) {
9581
if ( Array.isArray( group[group_value] ) ) {
9582
group[group_value] = image_grid_split_array_to_group(group[group_value], type, name);
9583
} else {
9584
image_grid_group_split_all_arrays(group[group_value], type, name);
9585
}
9586
}
9587
}
9588
}
9589
9590
function image_grid_split_array_to_group(img_index_array, attr_type, attr_name) {
9591
var grp = {};
9592
var img_index, img_id, i;
9593
var n = img_index_array.length;
9594
var attr_value;
9595
9596
switch(attr_type) {
9597
case 'file':
9598
for ( i = 0; i < n; ++i ) {
9599
img_index = img_index_array[i];
9600
img_id = _via_image_id_list[img_index];
9601
if ( _via_img_metadata[img_id].file_attributes.hasOwnProperty(attr_name) ) {
9602
attr_value = _via_img_metadata[img_id].file_attributes[attr_name];
9603
9604
if ( ! grp.hasOwnProperty(attr_value) ) {
9605
grp[attr_value] = [];
9606
}
9607
grp[attr_value].push(img_index);
9608
}
9609
}
9610
break;
9611
case 'region':
9612
var j;
9613
var region_count;
9614
for ( i = 0; i < n; ++i ) {
9615
img_index = img_index_array[i];
9616
img_id = _via_image_id_list[img_index];
9617
region_count = _via_img_metadata[img_id].regions.length;
9618
for ( j = 0; j < region_count; ++j ) {
9619
if ( _via_img_metadata[img_id].regions[j].region_attributes.hasOwnProperty(attr_name) ) {
9620
attr_value = _via_img_metadata[img_id].regions[j].region_attributes[attr_name];
9621
9622
if ( ! grp.hasOwnProperty(attr_value) ) {
9623
grp[attr_value] = [];
9624
}
9625
if ( grp[attr_value].includes(img_index) ) {
9626
continue;
9627
} else {
9628
grp[attr_value].push(img_index);
9629
}
9630
}
9631
}
9632
}
9633
break;
9634
}
9635
return grp;
9636
}
9637
9638
function image_grid_group_next(p) {
9639
var group_index = parseInt( p.value );
9640
var group_value_list = _via_image_grid_group_var[group_index].values;
9641
var n = group_value_list.length;
9642
var current_index = _via_image_grid_group_var[group_index].current_value_index;
9643
var next_index = current_index + 1;
9644
if ( next_index >= n ) {
9645
if ( group_index === 0 ) {
9646
next_index = next_index - n;
9647
image_grid_jump_to_group(group_index, next_index);
9648
} else {
9649
// next of parent group
9650
var parent_group_index = group_index - 1;
9651
var parent_current_val_index = _via_image_grid_group_var[parent_group_index].current_value_index;
9652
var parent_next_val_index = parent_current_val_index + 1;
9653
while ( parent_group_index !== 0 ) {
9654
if ( parent_next_val_index >= _via_image_grid_group_var[parent_group_index].values.length ) {
9655
parent_group_index = group_index - 1;
9656
parent_current_val_index = _via_image_grid_group_var[parent_group_index].current_value_index;
9657
parent_next_val_index = parent_current_val_index + 1;
9658
} else {
9659
break;
9660
}
9661
}
9662
9663
if ( parent_next_val_index >= _via_image_grid_group_var[parent_group_index].values.length ) {
9664
parent_next_val_index = 0;
9665
}
9666
image_grid_jump_to_group(parent_group_index, parent_next_val_index);
9667
}
9668
} else {
9669
image_grid_jump_to_group(group_index, next_index);
9670
}
9671
image_grid_set_content_to_current_group();
9672
}
9673
9674
function image_grid_group_prev(p) {
9675
var group_index = parseInt( p.value );
9676
var group_value_list = _via_image_grid_group_var[group_index].values;
9677
var n = group_value_list.length;
9678
var current_index = _via_image_grid_group_var[group_index].current_value_index;
9679
var prev_index = current_index - 1;
9680
if ( prev_index < 0 ) {
9681
if ( group_index === 0 ) {
9682
prev_index = n + prev_index;
9683
image_grid_jump_to_group(group_index, prev_index);
9684
} else {
9685
// prev of parent group
9686
var parent_group_index = group_index - 1;
9687
var parent_current_val_index = _via_image_grid_group_var[parent_group_index].current_value_index;
9688
var parent_prev_val_index = parent_current_val_index - 1;
9689
while ( parent_group_index !== 0 ) {
9690
if ( parent_prev_val_index < 0 ) {
9691
parent_group_index = group_index - 1;
9692
parent_current_val_index = _via_image_grid_group_var[parent_group_index].current_value_index;
9693
parent_prev_val_index = parent_current_val_index - 1;
9694
} else {
9695
break;
9696
}
9697
}
9698
9699
if ( parent_prev_val_index < 0 ) {
9700
parent_prev_val_index = _via_image_grid_group_var[parent_group_index].values.length - 1;
9701
}
9702
image_grid_jump_to_group(parent_group_index, parent_prev_val_index);
9703
}
9704
} else {
9705
image_grid_jump_to_group(group_index, prev_index);
9706
}
9707
image_grid_set_content_to_current_group();
9708
}
9709
9710
9711
function image_grid_group_value_onchange(p) {
9712
var group_index = image_grid_group_select_parse_html_id(p.id);
9713
image_grid_jump_to_group(group_index, p.selectedIndex);
9714
image_grid_set_content_to_current_group();
9715
}
9716
9717
function image_grid_jump_to_group(group_index, value_index) {
9718
var n = _via_image_grid_group_var[group_index].values.length;
9719
if ( value_index >=n || value_index < 0 ) {
9720
return;
9721
}
9722
9723
_via_image_grid_group_var[group_index].current_value_index = value_index;
9724
image_grid_group_panel_set_selected_value( group_index );
9725
9726
// reset the value of lower groups
9727
var i, value;
9728
if ( group_index + 1 < _via_image_grid_group_var.length ) {
9729
var e = _via_image_grid_group;
9730
for ( i = 0; i <= group_index; ++i ) {
9731
value = _via_image_grid_group_var[i].values[ _via_image_grid_group_var[i].current_value_index ];
9732
e = e[ value ];
9733
}
9734
9735
for ( i = group_index + 1; i < _via_image_grid_group_var.length; ++i ) {
9736
_via_image_grid_group_var[i].values = Object.keys(e);
9737
if ( _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);
9740
image_grid_group_panel_set_options(i);
9741
break;
9742
} else {
9743
_via_image_grid_group_var[i].current_value_index = 0;
9744
value = _via_image_grid_group_var[i].values[0]
9745
e = e[value];
9746
image_grid_group_panel_set_options(i);
9747
}
9748
}
9749
}
9750
}
9751
9752
function image_grid_set_content_to_current_group() {
9753
var n = _via_image_grid_group_var.length;
9754
9755
if ( n === 0 ) {
9756
image_grid_show_all_project_images();
9757
} else {
9758
var group_img_index_list = [];
9759
var img_index_list = _via_image_grid_group;
9760
var i, n, value, current_value_index;
9761
for ( i = 0; i < n; ++i ) {
9762
value = _via_image_grid_group_var[i].values[ _via_image_grid_group_var[i].current_value_index ];
9763
img_index_list = img_index_list[ value ];
9764
}
9765
9766
if ( Array.isArray(img_index_list) ) {
9767
image_grid_set_content(img_index_list);
9768
} else {
9769
console.log('Error: image_grid_set_content_to_current_group(): expected array while got ' + typeof(img_index_list));
9770
}
9771
}
9772
}
9773
9774
function 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
9778
image_grid_clear_content();
9779
_via_image_grid_load_ongoing = true;
9780
image_grid_page_nav_show_cancel();
9781
image_grid_content_append_img( _via_image_grid_page_first_index );
9782
}
9783
9784
function 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
9788
image_grid_clear_content();
9789
_via_image_grid_load_ongoing = true;
9790
image_grid_page_nav_show_cancel();
9791
image_grid_content_append_img( _via_image_grid_page_first_index );
9792
}
9793
9794
function image_grid_page_nav_show_cancel() {
9795
var info = document.getElementById('image_grid_nav');
9796
var html = [];
9797
html.push('<span>Loading images ... </span>');
9798
html.push('<span class="text_button" onclick="image_grid_cancel_load_ongoing()">Cancel</span>');
9799
info.innerHTML = html.join('');
9800
}
9801
9802
function image_grid_cancel_load_ongoing() {
9803
_via_image_grid_load_ongoing = false;
9804
}
9805
9806
9807
// everything to do with image zooming
9808
function 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
9838
function _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
9870
this.shape = shape;
9871
this.id = id;
9872
this.scale_factor = view_scale_factor;
9873
this.offset_x = view_offset_x;
9874
this.offset_y = view_offset_y;
9875
this.recompute_svg = false;
9876
this.attributes = {};
9877
9878
var n = data_img_space.length;
9879
var i;
9880
this.dview = new Array(n);
9881
this.dimg = new Array(n);
9882
9883
if ( 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
9890
for ( i = 0; i < n; i++ ) {
9891
this.dimg[i] = data_img_space[i];
9892
9893
var offset = this.offset_x;
9894
if ( i % 2 !== 0 ) {
9895
// y coordinate
9896
offset = this.offset_y;
9897
}
9898
this.dview[i] = Math.round( this.dimg[i] * this.scale_factor ) + offset;
9899
}
9900
}
9901
9902
// set svg attributes for each shape
9903
switch( this.shape ) {
9904
case "rect":
9905
_via_region_rect.call( this );
9906
this.svg_attributes = ['x', 'y', 'width', 'height'];
9907
break;
9908
case "circle":
9909
_via_region_circle.call( this );
9910
this.svg_attributes = ['cx', 'cy', 'r'];
9911
break;
9912
case "ellipse":
9913
_via_region_ellipse.call( this );
9914
this.svg_attributes = ['cx', 'cy', 'rx', 'ry','transform'];
9915
break;
9916
case "line":
9917
_via_region_line.call( this );
9918
this.svg_attributes = ['x1', 'y1', 'x2', 'y2'];
9919
break;
9920
case "polyline":
9921
_via_region_polyline.call( this );
9922
this.svg_attributes = ['points'];
9923
break;
9924
case "polygon":
9925
_via_region_polygon.call( this );
9926
this.svg_attributes = ['points'];
9927
break;
9928
case "point":
9929
_via_region_point.call( this );
9930
// point is a special circle with minimal radius required for visualization
9931
this.shape = 'circle';
9932
this.svg_attributes = ['cx', 'cy', 'r'];
9933
break;
9934
}
9935
9936
this.initialize();
9937
}
9938
9939
9940
_via_region.prototype.prepare_svg_element = function() {
9941
var _VIA_SVG_NS = "http://www.w3.org/2000/svg";
9942
this.svg_element = document.createElementNS(_VIA_SVG_NS, this.shape);
9943
this.svg_string = '<' + this.shape;
9944
this.svg_element.setAttributeNS(null, 'id', this.id);
9945
9946
var n = this.svg_attributes.length;
9947
for ( var i = 0; i < n; i++ ) {
9948
this.svg_element.setAttributeNS(null, this.svg_attributes[i], this[this.svg_attributes[i]]);
9949
this.svg_string += ' ' + this.svg_attributes[i] + '="' + this[this.svg_attributes[i]] + '"';
9950
}
9951
this.svg_string += '/>';
9952
}
9953
9954
_via_region.prototype.get_svg_element = function() {
9955
if ( this.recompute_svg ) {
9956
this.prepare_svg_element();
9957
this.recompute_svg = false;
9958
}
9959
return this.svg_element;
9960
}
9961
9962
_via_region.prototype.get_svg_string = function() {
9963
if ( this.recompute_svg ) {
9964
this.prepare_svg_element();
9965
this.recompute_svg = false;
9966
}
9967
return this.svg_string;
9968
}
9969
9970
///
9971
/// Region shape : rectangle
9972
///
9973
function _via_region_rect() {
9974
this.is_inside = _via_region_rect.prototype.is_inside;
9975
this.is_on_edge = _via_region_rect.prototype.is_on_edge;
9976
this.move = _via_region_rect.prototype.move;
9977
this.resize = _via_region_rect.prototype.resize;
9978
this.initialize = _via_region_rect.prototype.initialize;
9979
this.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
9985
if ( this.dview[0] < this.dview[2] ) {
9986
this.x = this.dview[0];
9987
this.x2 = this.dview[2];
9988
} else {
9989
this.x = this.dview[2];
9990
this.x2 = this.dview[0];
9991
}
9992
if ( this.dview[1] < this.dview[3] ) {
9993
this.y = this.dview[1];
9994
this.y2 = this.dview[3];
9995
} else {
9996
this.y = this.dview[3];
9997
this.y2 = this.dview[1];
9998
}
9999
this.width = this.x2 - this.x;
10000
this.height = this.y2 - this.y;
10001
this.recompute_svg = true;
10002
}
10003
10004
///
10005
/// Region shape : circle
10006
///
10007
function _via_region_circle() {
10008
this.is_inside = _via_region_circle.prototype.is_inside;
10009
this.is_on_edge = _via_region_circle.prototype.is_on_edge;
10010
this.move = _via_region_circle.prototype.move;
10011
this.resize = _via_region_circle.prototype.resize;
10012
this.initialize = _via_region_circle.prototype.initialize;
10013
this.dist_to_nearest_edge = _via_region_circle.prototype.dist_to_nearest_edge;
10014
}
10015
10016
_via_region_circle.prototype.initialize = function() {
10017
this.cx = this.dview[0];
10018
this.cy = this.dview[1];
10019
var dx = this.dview[2] - this.dview[0];
10020
var dy = this.dview[3] - this.dview[1];
10021
this.r = Math.round( Math.sqrt(dx * dx + dy * dy) );
10022
this.r2 = this.r * this.r;
10023
this.recompute_svg = true;
10024
}
10025
10026
10027
///
10028
/// Region shape : ellipse
10029
///
10030
function _via_region_ellipse() {
10031
this.is_inside = _via_region_ellipse.prototype.is_inside;
10032
this.is_on_edge = _via_region_ellipse.prototype.is_on_edge;
10033
this.move = _via_region_ellipse.prototype.move;
10034
this.resize = _via_region_ellipse.prototype.resize;
10035
this.initialize = _via_region_ellipse.prototype.initialize;
10036
this.dist_to_nearest_edge = _via_region_ellipse.prototype.dist_to_nearest_edge;
10037
}
10038
10039
_via_region_ellipse.prototype.initialize = function() {
10040
this.cx = this.dview[0];
10041
this.cy = this.dview[1];
10042
this.rx = Math.abs(this.dview[2] - this.dview[0]);
10043
this.ry = Math.abs(this.dview[3] - this.dview[1]);
10044
10045
this.inv_rx2 = 1 / (this.rx * this.rx);
10046
this.inv_ry2 = 1 / (this.ry * this.ry);
10047
10048
this.recompute_svg = true;
10049
}
10050
10051
10052
10053
///
10054
/// Region shape : line
10055
///
10056
function _via_region_line() {
10057
this.is_inside = _via_region_line.prototype.is_inside;
10058
this.is_on_edge = _via_region_line.prototype.is_on_edge;
10059
this.move = _via_region_line.prototype.move;
10060
this.resize = _via_region_line.prototype.resize;
10061
this.initialize = _via_region_line.prototype.initialize;
10062
this.dist_to_nearest_edge = _via_region_line.prototype.dist_to_nearest_edge;
10063
}
10064
10065
_via_region_line.prototype.initialize = function() {
10066
this.x1 = this.dview[0];
10067
this.y1 = this.dview[1];
10068
this.x2 = this.dview[2];
10069
this.y2 = this.dview[3];
10070
this.dx = this.x1 - this.x2;
10071
this.dy = this.y1 - this.y2;
10072
this.mconst = (this.x1 * this.y2) - (this.x2 * this.y1);
10073
10074
this.recompute_svg = true;
10075
}
10076
10077
10078
///
10079
/// Region shape : polyline
10080
///
10081
function _via_region_polyline() {
10082
this.is_inside = _via_region_polyline.prototype.is_inside;
10083
this.is_on_edge = _via_region_polyline.prototype.is_on_edge;
10084
this.move = _via_region_polyline.prototype.move;
10085
this.resize = _via_region_polyline.prototype.resize;
10086
this.initialize = _via_region_polyline.prototype.initialize;
10087
this.dist_to_nearest_edge = _via_region_polyline.prototype.dist_to_nearest_edge;
10088
}
10089
10090
_via_region_polyline.prototype.initialize = function() {
10091
var n = this.dview.length;
10092
var points = new Array(n/2);
10093
var points_index = 0;
10094
for ( var i = 0; i < n; i += 2 ) {
10095
points[points_index] = ( this.dview[i] + ' ' + this.dview[i+1] );
10096
points_index++;
10097
}
10098
this.points = points.join(',');
10099
this.recompute_svg = true;
10100
}
10101
10102
10103
///
10104
/// Region shape : polygon
10105
///
10106
function _via_region_polygon() {
10107
this.is_inside = _via_region_polygon.prototype.is_inside;
10108
this.is_on_edge = _via_region_polygon.prototype.is_on_edge;
10109
this.move = _via_region_polygon.prototype.move;
10110
this.resize = _via_region_polygon.prototype.resize;
10111
this.initialize = _via_region_polygon.prototype.initialize;
10112
this.dist_to_nearest_edge = _via_region_polygon.prototype.dist_to_nearest_edge;
10113
}
10114
10115
_via_region_polygon.prototype.initialize = function() {
10116
var n = this.dview.length;
10117
var points = new Array(n/2);
10118
var points_index = 0;
10119
for ( var i = 0; i < n; i += 2 ) {
10120
points[points_index] = ( this.dview[i] + ' ' + this.dview[i+1] );
10121
points_index++;
10122
}
10123
this.points = points.join(',');
10124
this.recompute_svg = true;
10125
}
10126
10127
10128
///
10129
/// Region shape : point
10130
///
10131
function _via_region_point() {
10132
this.is_inside = _via_region_point.prototype.is_inside;
10133
this.is_on_edge = _via_region_point.prototype.is_on_edge;
10134
this.move = _via_region_point.prototype.move;
10135
this.resize = _via_region_point.prototype.resize
10136
this.initialize = _via_region_point.prototype.initialize;
10137
this.dist_to_nearest_edge = _via_region_point.prototype.dist_to_nearest_edge;
10138
}
10139
10140
_via_region_point.prototype.initialize = function() {
10141
this.cx = this.dview[0];
10142
this.cy = this.dview[1];
10143
this.r = 2;
10144
this.r2 = this.r * this.r;
10145
this.recompute_svg = true;
10146
}
10147
10148
//
10149
// image buffering
10150
//
10151
10152
function _via_cancel_current_image_loading() {
10153
var panel = document.getElementById('project_panel_title');
10154
panel.innerHTML = 'Project';
10155
_via_is_loading_current_image = false;
10156
}
10157
10158
function _via_show_img(img_index) {
10159
if ( _via_is_loading_current_image ) {
10160
return;
10161
}
10162
10163
var img_id = _via_image_id_list[img_index];
10164
10165
if ( ! _via_img_metadata.hasOwnProperty(img_id) ) {
10166
console.log('_via_show_img(): [' + img_index + '] ' + img_id + ' does not exist!')
10167
show_message('The requested image does not exist!')
10168
return;
10169
}
10170
10171
// file_resolve() is not necessary for files selected using browser's file selector
10172
if ( typeof(_via_img_fileref[img_id]) === 'undefined' || ! _via_img_fileref[img_id] instanceof File ) {
10173
// try preload from local file or url
10174
if ( typeof(_via_img_src[img_id]) === 'undefined' || _via_img_src[img_id] === '' ) {
10175
if ( 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);
10178
return;
10179
} else {
10180
var search_path_list = _via_file_get_search_path_list();
10181
if ( search_path_list.length === 0 ) {
10182
search_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) {
10188
show_page_404(img_index);
10189
});
10190
return;
10191
}
10192
}
10193
}
10194
10195
if ( _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
10200
Promise.all(_via_preload_img_promise_list).then( function(values) {
10201
_via_preload_img_promise_list = [];
10202
var 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) {
10206
console.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;
10212
img_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;
10215
img_loading_spinbar(img_index, false);
10216
_via_show_img(img_index);
10217
}, function(err_img_index) {
10218
_via_is_loading_current_image = false;
10219
img_loading_spinbar(img_index, false);
10220
show_page_404(img_index);
10221
console.log('_via_img_buffer_add_image() failed for file: ' + _via_image_filename_list[err_img_index]);
10222
});
10223
}
10224
}
10225
10226
function _via_buffer_hide_current_image() {
10227
img_fn_list_ith_entry_selected(_via_image_index, false);
10228
_via_clear_reg_canvas(); // clear old region shapes
10229
if ( _via_current_image ) {
10230
_via_current_image.classList.remove('visible');
10231
}
10232
}
10233
10234
function _via_show_img_from_buffer(img_index) {
10235
return new Promise( function(ok_callback, err_callback) {
10236
_via_buffer_hide_current_image();
10237
10238
var cimg_html_id = _via_img_buffer_get_html_id(img_index);
10239
_via_current_image = document.getElementById(cimg_html_id);
10240
if ( ! _via_current_image ) {
10241
// the said image is not present in buffer, which could be because
10242
// the image got removed from the buffer
10243
err_callback(img_index);
10244
return;
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
10253
var 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
10269
if ( _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
10277
var de = document.documentElement;
10278
var image_panel_width = de.clientWidth - leftsidebar.clientWidth - 20;
10279
if ( leftsidebar.style.display === 'none' ) {
10280
image_panel_width = de.clientWidth;
10281
}
10282
var 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
10287
if ( _via_canvas_width > image_panel_width ) {
10288
// resize image to match the panel width
10289
var 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
}
10293
if ( _via_canvas_height > image_panel_height ) {
10294
// resize further image if its height is larger than the image panel
10295
var 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;
10303
set_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
10307
toggle_all_regions_selection(false);
10308
10309
// ensure that all the canvas are visible
10310
set_display_area_content( VIA_DISPLAY_AREA_CONTENT_NAME.IMAGE );
10311
10312
// update img_fn_list
10313
img_fn_list_ith_entry_selected(_via_image_index, true);
10314
img_fn_list_scroll_to_current_file();
10315
10316
// refresh the annotations panel
10317
annotation_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
10324
if (_via_is_canvas_zoomed) {
10325
set_zoom( _via_canvas_zoom_level_index );
10326
}
10327
ok_callback(img_index);
10328
});
10329
}
10330
10331
function _via_img_buffer_add_image(img_index) {
10332
return new Promise( function(ok_callback, err_callback) {
10333
if ( _via_buffer_img_index_list.includes(img_index) ) {
10334
//console.log('_via_img_buffer_add_image(): image ' + img_index + ' already exists in buffer!')
10335
ok_callback(img_index);
10336
return;
10337
}
10338
10339
var img_id = _via_image_id_list[img_index];
10340
var img_filename = _via_img_metadata[img_id].filename;
10341
if ( !_via_img_metadata.hasOwnProperty(img_id)) {
10342
err_callback(img_index);
10343
return;
10344
}
10345
10346
// check if user has given access to local file using
10347
// browser's file selector
10348
if ( _via_img_fileref[img_id] instanceof File ) {
10349
var tmp_file_object_url = URL.createObjectURL(_via_img_fileref[img_id]);
10350
var img_id = _via_image_id_list[img_index];
10351
var bimg = document.createElement('img');
10352
bimg.setAttribute('id', _via_img_buffer_get_html_id(img_index));
10353
bimg.setAttribute('src', tmp_file_object_url);
10354
bimg.setAttribute('alt', 'Image loaded from base64 data of a local file selected by user.');
10355
bimg.addEventListener('error', function() {
10356
URL.revokeObjectURL(tmp_file_object_url);
10357
project_file_load_on_fail(img_index);
10358
err_callback(img_index);
10359
});
10360
bimg.addEventListener('load', function() {
10361
URL.revokeObjectURL(tmp_file_object_url);
10362
img_stat_set(img_index, [bimg.naturalWidth, bimg.naturalHeight]);
10363
_via_img_panel.insertBefore(bimg, _via_reg_canvas);
10364
project_file_load_on_success(img_index);
10365
img_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
10368
var 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
10371
ok_callback(img_index);
10372
});
10373
return;
10374
}
10375
10376
if ( typeof(_via_img_src[img_id]) === 'undefined' || _via_img_src[img_id] === '' ) {
10377
err_callback(img_index);
10378
} else {
10379
var img_id = _via_image_id_list[img_index];
10380
10381
var bimg = document.createElement('img');
10382
bimg.setAttribute('id', _via_img_buffer_get_html_id(img_index));
10383
_via_img_src[img_id] = _via_img_src[img_id].replace('#', '%23');
10384
bimg.setAttribute('src', _via_img_src[img_id]);
10385
if ( _via_img_src[img_id].startsWith('data:image') ) {
10386
bimg.setAttribute('alt', 'Source: image data in base64 format');
10387
} else {
10388
bimg.setAttribute('alt', 'Source: ' + _via_img_src[img_id]);
10389
}
10390
10391
bimg.addEventListener('abort', function() {
10392
project_file_load_on_fail(img_index);
10393
err_callback(img_index);
10394
});
10395
bimg.addEventListener('error', function() {
10396
project_file_load_on_fail(img_index);
10397
err_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.
10402
bimg.addEventListener('load', function() {
10403
img_stat_set(img_index, [bimg.naturalWidth, bimg.naturalHeight]);
10404
_via_img_panel.insertBefore(bimg, _via_reg_canvas);
10405
10406
project_file_load_on_success(img_index);
10407
img_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
10410
var 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
10413
ok_callback(img_index);
10414
}, false);
10415
}
10416
}, false);
10417
}
10418
10419
function _via_img_buffer_get_html_id(img_index) {
10420
return 'bim' + img_index;
10421
}
10422
10423
function _via_img_buffer_parse_html_id(html_id) {
10424
return parseInt( html_id.substr(3) );
10425
}
10426
10427
function _via_img_buffer_start_preload(img_index, preload_index) {
10428
return 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) {
10431
ok_callback(ok_img_index_list);
10432
});
10433
});
10434
}
10435
10436
function _via_img_buffer_preload_img(img_index, preload_index) {
10437
return new Promise( function(ok_callback, err_callback) {
10438
var preload_img_index = _via_img_buffer_get_preload_img_index(img_index, preload_index);
10439
10440
if ( _via_buffer_preload_img_index !== _via_image_index ) {
10441
ok_callback([]);
10442
return;
10443
}
10444
10445
// ensure that there is sufficient buffer space left for preloading image
10446
if ( _via_buffer_img_index_list.length > _via_settings.core.buffer_size ) {
10447
while( _via_buffer_img_index_list.length > _via_settings.core.buffer_size ) {
10448
_via_img_buffer_remove_least_useful_img();
10449
if ( _via_image_index !== _via_buffer_preload_img_index ) {
10450
// current image has changed therefore, we need to cancel this preload operation
10451
ok_callback([]);
10452
return;
10453
}
10454
}
10455
}
10456
10457
_via_img_buffer_add_image(preload_img_index).then( function(ok_img_index) {
10458
if ( _via_image_index !== _via_buffer_preload_img_index ) {
10459
ok_callback( [ok_img_index] );
10460
return;
10461
}
10462
10463
var next_preload_index = preload_index + 1;
10464
if ( next_preload_index !== VIA_IMG_PRELOAD_COUNT ) {
10465
_via_img_buffer_preload_img(img_index, next_preload_index).then( function(ok_img_index_list) {
10466
ok_img_index_list.push( ok_img_index )
10467
ok_callback( ok_img_index_list );
10468
});
10469
} else {
10470
ok_callback( [ok_img_index] );
10471
}
10472
}, function(err_img_index) {
10473
// continue with preload of other images in sequence
10474
var next_preload_index = preload_index + 1;
10475
if ( next_preload_index !== VIA_IMG_PRELOAD_COUNT ) {
10476
_via_img_buffer_preload_img(img_index, next_preload_index).then( function(ok_img_index_list) {
10477
ok_callback( ok_img_index_list );
10478
});
10479
} else {
10480
ok_callback([]);
10481
}
10482
});
10483
});
10484
}
10485
10486
function _via_img_buffer_get_preload_img_index(img_index, preload_index) {
10487
var preload_img_index = img_index + VIA_IMG_PRELOAD_INDICES[preload_index];
10488
if ( (preload_img_index < 0) || (preload_img_index >= _via_img_count) ) {
10489
if ( preload_img_index < 0 ) {
10490
preload_img_index = _via_img_count + preload_img_index;
10491
} else {
10492
preload_img_index = preload_img_index - _via_img_count;
10493
}
10494
}
10495
return 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
10502
function _via_img_buffer_remove_least_useful_img() {
10503
var not_in_preload_list = _via_buffer_img_not_in_preload_list();
10504
var oldest_buffer_index = _via_buffer_get_oldest_in_list(not_in_preload_list);
10505
10506
if ( _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 {
10510
var furthest_buffer_index = _via_buffer_get_buffer_furthest_from_current_img();
10511
_via_buffer_remove(furthest_buffer_index);
10512
}
10513
}
10514
10515
function _via_buffer_remove( buffer_index ) {
10516
var img_index = _via_buffer_img_index_list[buffer_index];
10517
var bimg_html_id = _via_img_buffer_get_html_id(img_index);
10518
var bimg = document.getElementById(bimg_html_id);
10519
if ( 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
10524
img_fn_list_ith_entry_remove_css_class(img_index, 'buffered')
10525
}
10526
}
10527
10528
function _via_buffer_remove_all() {
10529
var i, n;
10530
n = _via_buffer_img_index_list.length;
10531
for ( i = 0 ; i < n; ++i ) {
10532
var img_index = _via_buffer_img_index_list[i];
10533
var bimg_html_id = _via_img_buffer_get_html_id(img_index);
10534
var bimg = document.getElementById(bimg_html_id);
10535
if ( bimg ) {
10536
_via_img_panel.removeChild(bimg);
10537
}
10538
}
10539
_via_buffer_img_index_list = [];
10540
_via_buffer_img_shown_timestamp = [];
10541
}
10542
10543
function _via_buffer_get_oldest_in_list(not_in_preload_list) {
10544
var i;
10545
var n = not_in_preload_list.length;
10546
var oldest_buffer_index = -1;
10547
var oldest_buffer_timestamp = Date.now();
10548
10549
for ( i = 0; i < n; ++i ) {
10550
var _via_buffer_index = not_in_preload_list[i];
10551
if ( _via_buffer_img_shown_timestamp[_via_buffer_index] < oldest_buffer_timestamp ) {
10552
oldest_buffer_timestamp = _via_buffer_img_shown_timestamp[i];
10553
oldest_buffer_index = i;
10554
}
10555
}
10556
return oldest_buffer_index;
10557
}
10558
10559
function _via_buffer_get_buffer_furthest_from_current_img() {
10560
var now_img_index = _via_image_index;
10561
var i, dist1, dist2, dist;
10562
var n = _via_buffer_img_index_list.length;
10563
var furthest_buffer_index = 0;
10564
dist1 = Math.abs( _via_buffer_img_index_list[0] - now_img_index );
10565
dist2 = _via_img_count - dist1; // assuming the list is circular
10566
var furthest_buffer_dist = Math.min(dist1, dist2);
10567
10568
for ( i = 1; i < n; ++i ) {
10569
dist1 = Math.abs( _via_buffer_img_index_list[i] - now_img_index );
10570
dist2 = _via_img_count - dist1; // assuming the list is circular
10571
dist = Math.min(dist1, dist2);
10572
// image has been seen by user at least once
10573
if ( dist > furthest_buffer_dist ) {
10574
furthest_buffer_dist = dist;
10575
furthest_buffer_index = i;
10576
}
10577
}
10578
return furthest_buffer_index;
10579
}
10580
10581
function _via_buffer_img_not_in_preload_list() {
10582
var preload_list = _via_buffer_get_current_preload_list();
10583
var i;
10584
var not_in_preload_list = [];
10585
for ( i = 0; i < _via_buffer_img_index_list.length; ++i ) {
10586
if ( ! preload_list.includes( _via_buffer_img_index_list[i] ) ) {
10587
not_in_preload_list.push( i );
10588
}
10589
}
10590
return not_in_preload_list;
10591
}
10592
10593
function _via_buffer_get_current_preload_list() {
10594
var i;
10595
var preload_list = [_via_image_index];
10596
var img_index = _via_image_index;
10597
for ( i = 0; i < VIA_IMG_PRELOAD_COUNT; ++i ) {
10598
var preload_index = img_index + VIA_IMG_PRELOAD_INDICES[i];
10599
if ( preload_index < 0 ) {
10600
preload_index = _via_img_count + preload_index;
10601
}
10602
if ( preload_index >= _via_img_count ) {
10603
preload_index = preload_index - _via_img_count;
10604
}
10605
preload_list.push(preload_index);
10606
}
10607
return preload_list;
10608
}
10609
10610
//
10611
// settings
10612
//
10613
function settings_panel_toggle() {
10614
if ( _via_display_area_content_name === VIA_DISPLAY_AREA_CONTENT_NAME.SETTINGS ) {
10615
if ( _via_display_area_content_name_prev !== '' ) {
10616
set_display_area_content(_via_display_area_content_name_prev);
10617
} else {
10618
show_single_image_view();
10619
_via_redraw_rleg_canvas();
10620
}
10621
}
10622
else {
10623
settings_init();
10624
set_display_area_content(VIA_DISPLAY_AREA_CONTENT_NAME.SETTINGS);
10625
}
10626
}
10627
10628
function settings_init() {
10629
settings_region_visualisation_update_options();
10630
settings_filepath_update_html();
10631
settings_show_current_value();
10632
}
10633
10634
function settings_save() {
10635
// check if default path was updated
10636
var default_path_updated = false;
10637
if ( document.getElementById('_via_settings.core.default_filepath').value !== _via_settings.core.default_filepath ) {
10638
default_path_updated = true;
10639
}
10640
10641
var p = document.getElementById('settings_panel');
10642
var vl = p.getElementsByClassName('value');
10643
var n = vl.length;
10644
var i;
10645
for ( i = 0; i < n; ++i ) {
10646
var s = vl[i].childNodes[1];
10647
var sid_parts = s.id.split('.');
10648
if ( sid_parts[0] === '_via_settings' ) {
10649
var el = _via_settings;
10650
var found = true;
10651
var j;
10652
for ( j = 1; j < sid_parts.length - 1; ++j ) {
10653
if ( el.hasOwnProperty( sid_parts[j] ) ) {
10654
el = el[ sid_parts[j] ];
10655
} else {
10656
// unrecognized setting
10657
found = false;
10658
break;
10659
}
10660
}
10661
if ( found ) {
10662
var param = sid_parts[ sid_parts.length - 1 ];
10663
if ( s.value !== '' || typeof(s.value) !== 'undefined' ) {
10664
el[param] = s.value;
10665
}
10666
}
10667
}
10668
}
10669
10670
// non-standard settings
10671
var p;
10672
p = document.getElementById('settings_input_new_filepath');
10673
if ( p.value !== '' ) {
10674
project_filepath_add(p.value.trim());
10675
}
10676
p = document.getElementById('project_name');
10677
if ( p.value !== _via_settings.project.name ) {
10678
p.value = _via_settings.project.name;
10679
}
10680
10681
if ( default_path_updated ) {
10682
_via_file_resolve_all_to_default_filepath();
10683
_via_show_img(_via_image_index);
10684
}
10685
10686
show_message('Settings saved.');
10687
settings_panel_toggle();
10688
}
10689
10690
function settings_show_current_value() {
10691
var p = document.getElementById('settings_panel');
10692
var vl = p.getElementsByClassName('value');
10693
var n = vl.length;
10694
var i;
10695
for ( i = 0; i < n; ++i ) {
10696
var s = vl[i].childNodes[1];
10697
var sid_parts = s.id.split('.');
10698
if ( sid_parts[0] === '_via_settings' ) {
10699
var el = _via_settings;
10700
var found = true;
10701
var j;
10702
for ( j = 1; j < sid_parts.length; ++j ) {
10703
if ( el.hasOwnProperty( sid_parts[j] ) ) {
10704
el = el[ sid_parts[j] ];
10705
} else {
10706
// unrecognized setting
10707
found = false;
10708
break;
10709
}
10710
}
10711
10712
if ( found ) {
10713
s.value = el;
10714
}
10715
}
10716
}
10717
}
10718
10719
function settings_region_visualisation_update_options() {
10720
var 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
10730
for ( var setting in region_setting_list ) {
10731
var select = document.getElementById('_via_settings.ui.image.' + setting);
10732
select.innerHTML = '';
10733
var default_option = document.createElement('option');
10734
default_option.setAttribute('value', region_setting_list[setting]['default_option']);
10735
if ( _via_settings.ui.image[setting] === region_setting_list[setting]['default_option'] ) {
10736
default_option.setAttribute('selected', 'selected');
10737
}
10738
default_option.innerHTML = region_setting_list[setting]['default_label'];
10739
select.appendChild(default_option);
10740
10741
// options: add region attributes
10742
var rattr;
10743
for ( rattr in _via_attributes['region'] ) {
10744
var o = document.createElement('option');
10745
o.setAttribute('value', rattr);
10746
o.innerHTML = region_setting_list[setting]['label_prefix'] + rattr;
10747
if ( _via_settings.ui.image.region_label === rattr ) {
10748
o.setAttribute('selected', 'selected');
10749
}
10750
select.appendChild(o);
10751
}
10752
}
10753
}
10754
10755
function settings_filepath_update_html() {
10756
var p = document.getElementById('_via_settings.core.filepath');
10757
p.innerHTML = '';
10758
var i, path, order;
10759
for ( path in _via_settings.core.filepath ) {
10760
order = _via_settings.core.filepath[path]
10761
if ( order !== 0 ) {
10762
var li = document.createElement('li');
10763
li.innerHTML = path + '<span class="text_button" title="Delete image path" onclick="project_filepath_del(\"' + path + '\"); settings_filepath_update_html();">&times;</span>';
10764
p.appendChild(li);
10765
}
10766
}
10767
}
10768
10769
//
10770
// find location of file
10771
//
10772
10773
function _via_file_resolve_all_to_default_filepath() {
10774
var img_id;
10775
for ( img_id in _via_img_metadata ) {
10776
if ( _via_img_metadata.hasOwnProperty(img_id) ) {
10777
_via_file_resolve_file_to_default_filepath(img_id);
10778
}
10779
}
10780
}
10781
10782
function _via_file_resolve_file_to_default_filepath(img_id) {
10783
if ( _via_img_metadata.hasOwnProperty(img_id) ) {
10784
if ( typeof(_via_img_fileref[img_id]) === 'undefined' || ! _via_img_fileref[img_id] instanceof File ) {
10785
if ( 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
10794
function _via_file_resolve_all() {
10795
return new Promise( function(ok_callback, err_callback) {
10796
var all_promises = [];
10797
10798
var search_path_list = _via_file_get_search_path_list();
10799
var i, img_id;
10800
for ( i = 0; i < _via_img_count; ++i ) {
10801
img_id = _via_image_id_list[i];
10802
if ( typeof(_via_img_src[img_id]) === 'undefined' || _via_img_src[img_id] === '' ) {
10803
var p = _via_file_resolve(i, search_path_list);
10804
all_promises.push(p);
10805
}
10806
}
10807
10808
Promise.all( all_promises ).then( function(ok_file_index_list) {
10809
console.log(ok_file_index_list);
10810
ok_callback();
10811
//project_file_load_on_success(ok_file_index);
10812
}, function(err_file_index_list) {
10813
console.log(err_file_index_list);
10814
err_callback();
10815
//project_file_load_on_fail(err_file_index);
10816
});
10817
10818
});
10819
}
10820
10821
function _via_file_get_search_path_list() {
10822
var search_path_list = [];
10823
var path;
10824
for ( path in _via_settings.core.filepath ) {
10825
if ( _via_settings.core.filepath[path] !== 0 ) {
10826
search_path_list.push(path);
10827
}
10828
}
10829
return search_path_list;
10830
}
10831
10832
function _via_file_resolve(file_index, search_path_list) {
10833
return new Promise( function(ok_callback, err_callback) {
10834
var path_index = 0;
10835
var p = _via_file_resolve_check_path(file_index, path_index, search_path_list).then(function(ok) {
10836
ok_callback(ok);
10837
}, function(err) {
10838
err_callback(err);
10839
});
10840
}, false);
10841
}
10842
10843
function _via_file_resolve_check_path(file_index, path_index, search_path_list) {
10844
return new Promise( function(ok_callback, err_callback) {
10845
var img_id = _via_image_id_list[file_index];
10846
var img = new Image(0,0);
10847
10848
var img_path = search_path_list[path_index] + _via_img_metadata[img_id].filename;
10849
if ( is_url( _via_img_metadata[img_id].filename ) ) {
10850
if ( search_path_list[path_index] !== '' ) {
10851
// we search for the the image filename pointed by URL in local search paths
10852
img_path = search_path_list[path_index] + get_filename_from_url( _via_img_metadata[img_id].filename );
10853
}
10854
}
10855
10856
img.setAttribute('src', img_path);
10857
10858
img.addEventListener('load', function() {
10859
_via_img_src[img_id] = img_path;
10860
ok_callback(file_index);
10861
}, false);
10862
img.addEventListener('abort', function() {
10863
err_callback(file_index);
10864
});
10865
img.addEventListener('error', function() {
10866
var new_path_index = path_index + 1;
10867
if ( new_path_index < search_path_list.length ) {
10868
_via_file_resolve_check_path(file_index, new_path_index, search_path_list).then( function(ok) {
10869
ok_callback(file_index);
10870
}, function(err) {
10871
err_callback(file_index);
10872
});
10873
} else {
10874
err_callback(file_index);
10875
}
10876
}, false);
10877
}, false);
10878
}
10879
10880
//
10881
// page 404 (file not found)
10882
//
10883
function show_page_404(img_index) {
10884
_via_buffer_hide_current_image();
10885
10886
set_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;
10891
img_fn_list_ith_entry_selected(_via_image_index, true);
10892
10893
document.getElementById('page_404_filename').innerHTML = '[' + (_via_image_index+1) + ']' + _via_img_metadata[_via_image_id].filename;
10894
}
10895
10896
10897
//
10898
// utils
10899
//
10900
10901
function is_url( s ) {
10902
// @todo: ensure that this is sufficient to capture all image url
10903
if ( s.startsWith('http://') || s.startsWith('https://') || s.startsWith('www.') ) {
10904
return true;
10905
} else {
10906
return false;
10907
}
10908
}
10909
10910
function get_filename_from_url( url ) {
10911
return url.substring( url.lastIndexOf('/') + 1 );
10912
}
10913
10914
function fixfloat(x) {
10915
return parseFloat( x.toFixed(VIA_FLOAT_PRECISION) );
10916
}
10917
10918
function shape_attribute_fixfloat(sa) {
10919
for ( var attr in sa ) {
10920
switch(attr) {
10921
case 'x':
10922
case 'y':
10923
case 'width':
10924
case 'height':
10925
case 'r':
10926
case 'rx':
10927
case 'ry':
10928
sa[attr] = fixfloat( sa[attr] );
10929
break;
10930
case 'all_points_x':
10931
case 'all_points_y':
10932
for ( var i in sa[attr] ) {
10933
sa[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
10941
function array_intersect( array_list ) {
10942
if ( array_list.length === 0 ) {
10943
return [];
10944
}
10945
if ( array_list.length === 1 ) {
10946
return array_list[0];
10947
}
10948
10949
var shortest_array = array_list[0];
10950
var shortest_array_index = 0;
10951
var i;
10952
for ( i = 1; i < array_list.length; ++i ) {
10953
if ( array_list[i].length < shortest_array.length ) {
10954
shortest_array = array_list[i];
10955
shortest_array_index = i;
10956
}
10957
}
10958
10959
var intersect = [];
10960
var element_count = {};
10961
10962
var array_index_i;
10963
for ( i = 0; i < array_list.length; ++i ) {
10964
if ( i === 0 ) {
10965
// in the first iteration, process the shortest element array
10966
array_index_i = shortest_array_index;
10967
} else {
10968
array_index_i = i;
10969
}
10970
10971
var j;
10972
for ( j = 0; j < array_list[array_index_i].length; ++j ) {
10973
if ( element_count[ array_list[array_index_i][j] ] === (i-1) ) {
10974
if ( i === array_list.length - 1 ) {
10975
intersect.push( array_list[array_index_i][j] );
10976
element_count[ array_list[array_index_i][j] ] = 0;
10977
} else {
10978
element_count[ array_list[array_index_i][j] ] = i;
10979
}
10980
} else {
10981
element_count[ array_list[array_index_i][j] ] = 0;
10982
}
10983
}
10984
}
10985
return intersect;
10986
}
10987
10988
function generate_img_index_list(input) {
10989
var all_img_index_list = [];
10990
10991
// condition: count format a,b
10992
var count_format_img_index_list = [];
10993
if ( input.prev_next_count.value !== '' ) {
10994
var prev_next_split = input.prev_next_count.value.split(',');
10995
if ( prev_next_split.length === 2 ) {
10996
var prev = parseInt( prev_next_split[0] );
10997
var next = parseInt( prev_next_split[1] );
10998
var i;
10999
for ( i = (_via_image_index - prev); i <= (_via_image_index + next); i++ ) {
11000
count_format_img_index_list.push(i);
11001
}
11002
}
11003
}
11004
if ( count_format_img_index_list.length !== 0 ) {
11005
all_img_index_list.push(count_format_img_index_list);
11006
}
11007
11008
//condition: image index list expression
11009
var expr_img_index_list = [];
11010
if ( input.img_index_list.value !== '' ) {
11011
var img_index_expr = input.img_index_list.value.split(',');
11012
if ( img_index_expr.length !== 0 ) {
11013
var i;
11014
for ( i = 0; i < img_index_expr.length; ++i ) {
11015
if ( img_index_expr[i].includes('-') ) {
11016
var ab = img_index_expr[i].split('-');
11017
var a = parseInt( ab[0] ) - 1; // 0 based indexing
11018
var b = parseInt( ab[1] ) - 1;
11019
var j;
11020
for ( j = a; j <= b; ++j ) {
11021
expr_img_index_list.push(j);
11022
}
11023
} else {
11024
expr_img_index_list.push( parseInt(img_index_expr[i]) - 1 );
11025
}
11026
}
11027
}
11028
}
11029
if ( expr_img_index_list.length !== 0 ) {
11030
all_img_index_list.push(expr_img_index_list);
11031
}
11032
11033
11034
// condition: regular expression
11035
var regex_img_index_list = [];
11036
if ( input.regex.value !== '' ) {
11037
var regex = input.regex.value;
11038
for ( var i=0; i < _via_image_filename_list.length; ++i ) {
11039
var filename = _via_image_filename_list[i];
11040
if ( filename.match(regex) !== null ) {
11041
regex_img_index_list.push(i);
11042
}
11043
}
11044
}
11045
if ( regex_img_index_list.length !== 0 ) {
11046
all_img_index_list.push(regex_img_index_list);
11047
}
11048
11049
var intersect = array_intersect(all_img_index_list);
11050
return intersect;
11051
}
11052
11053
if ( ! _via_is_debug_mode ) {
11054
// warn user of possible loss of data
11055
window.onbeforeunload = function (e) {
11056
e = e || window.event;
11057
11058
// For IE and Firefox prior to version 4
11059
if (e) {
11060
e.returnValue = 'Did you save your data?';
11061
}
11062
11063
// For Safari
11064
return 'Did you save your data?';
11065
};
11066
}
11067
11068
//
11069
// keep a record of image statistics (e.g. width, height, ...)
11070
//
11071
function img_stat_set(img_index, stat) {
11072
if ( stat.length ) {
11073
_via_img_stat[img_index] = stat;
11074
} else {
11075
delete _via_img_stat[img_index];
11076
}
11077
}
11078
11079
function img_stat_set_all() {
11080
return new Promise( function(ok_callback, err_callback) {
11081
var promise_list = [];
11082
var img_id;
11083
for ( var img_index in _via_image_id_list ) {
11084
if ( ! _via_img_stat.hasOwnProperty(img_index) ) {
11085
img_id = _via_image_id_list[img_index];
11086
if ( _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 {
11093
promise_list.push( img_stat_get(img_index) );
11094
}
11095
}
11096
}
11097
if ( promise_list.length ) {
11098
Promise.all(promise_list).then( function(ok) {
11099
ok_callback();
11100
}.bind(this), function(err) {
11101
console.warn('Failed to read statistics of all images!');
11102
err_callback();
11103
});
11104
} else {
11105
ok_callback();
11106
}
11107
}.bind(this));
11108
}
11109
11110
function img_stat_get(img_index) {
11111
return new Promise( function(ok_callback, err_callback) {
11112
var img_id = _via_image_id_list[img_index];
11113
var tmp_img = document.createElement('img');
11114
var tmp_file_object_url = null;
11115
tmp_img.addEventListener('load', function() {
11116
_via_img_stat[img_index] = [tmp_img.naturalWidth, tmp_img.naturalHeight];
11117
if ( tmp_file_object_url !== null ) {
11118
URL.revokeObjectURL(tmp_file_object_url);
11119
}
11120
ok_callback();
11121
}.bind(this));
11122
tmp_img.addEventListener('error', function() {
11123
_via_img_stat[img_index] = [-1, -1];
11124
if ( tmp_file_object_url !== null ) {
11125
URL.revokeObjectURL(tmp_file_object_url);
11126
}
11127
ok_callback();
11128
}.bind(this));
11129
11130
if ( _via_img_fileref[img_id] instanceof File ) {
11131
tmp_file_object_url = URL.createObjectURL(_via_img_fileref[img_id]);
11132
tmp_img.src = tmp_file_object_url;
11133
} else {
11134
tmp_img.src = _via_img_src[img_id];
11135
}
11136
}.bind(this));
11137
}
11138
11139
11140
// pts = [x0,y0,x1,y1,....]
11141
function polygon_to_bbox(pts) {
11142
var xmin = +Infinity;
11143
var xmax = -Infinity;
11144
var ymin = +Infinity;
11145
var ymax = -Infinity;
11146
for ( var i = 0; i < pts.length; i = i + 2 ) {
11147
if ( pts[i] > xmax ) {
11148
xmax = pts[i];
11149
}
11150
if ( pts[i] < xmin ) {
11151
xmin = pts[i];
11152
}
11153
if ( pts[i+1] > ymax ) {
11154
ymax = pts[i+1];
11155
}
11156
if ( pts[i+1] < ymin ) {
11157
ymin = pts[i+1];
11158
}
11159
}
11160
return [xmin, ymin, xmax-xmin, ymax-ymin];
11161
}
11162
</script>
11163
<!-- END: Contents of file: via.js-->
11164
</body>
11165
</html>
11166