PageSourceSearch

http://tacodataset.org/js/annotator.js

js tacodataset.org collected 2026-10-03 21:37:24 UTC 22,955 bytes, 804 lines download raw bytes

1/*
2Author: Pedro F. Proença
3 - On page load, a new unlabeled image is requested. Require at least two urls
4 - After image submit, annotation data is posted to the server as a json
5*/
6
7// These variables should be returned by the server
8var image_id;
9var image_url; // = "https://live.staticflickr.com/65535/47066877034_dd3535e6ea_b.jpg";
10var image_url_o; // = "https://live.staticflickr.com/65535/47066877034_6fdbc4c882_o.jpg";
11var multiple_images_available;
12var nr_annotations_reviewed_so_far = 0;
13var mobile_device = false;
14
15var object_cats = new Array();
16var object_ctxs = new Array();
17var perimeters = new Array();
18var perimeter_per_object = new Array();
19var perimeter = new Array();
20var hr_enabled = false;
21var button_background = false;
22var canvas = document.getElementById("jPolygon");
23var ctx;
24var hue = ~~(Math.random() * 360);
25var object_color = "hsla(" + hue + ", 100%, 60%, 1.0)";
26var object_color_transparent = "hsla(" + hue + ", 100%, 60%, 0.5)";
27var object_colors = new Array();
28var object_colors_transparent = new Array();
29var image_original_height;
30var image_original_width;
31var cat_sorted_by_id = {};
32var object_id_selected;
33
34// Min distance between cursor and anchor point to select it
35var min_dist_2_anchor = 100;
36
37// Add key listener
38document.addEventListener('keydown', function(event) {
39      var key = event.which || event.keyCode;
40      if (key ==72){
41        change_resolution();
42      }
43      if (key == 13){
44        submit_step_1();
45      }
46}, false);
47
48// load_image() requests, fills image urls & dims
49function load_image(){
50
51  $('#spinner').show();
52  ctx = canvas.getContext("2d");
53  ctx.clearRect(0, 0, canvas.width, canvas.height);
54
55  $.ajax({
56    type: 'POST',
57    url: 'http://178.128.33.132:5901/twirp/v1.taco.annotations.Service/Unprocessed',
58    contentType: "application/json",
59    dataType: 'json',
60    data: '{}'
61  }).success( function(data) {
62    image_url = data['flick_640_url'];
63    image_url_o = data['flick_url'];
64    image_id = data['id'];
65
66    if (image_url == ' '){
67      multiple_images_available = false;
68    }else{
69      multiple_images_available = true;
70    }
71
72
73    // Get original image size
74    var img = new Image();
75    img.src = image_url_o;
76
77    img.onload = function(){
78      $('#spinner').hide();
79        image_original_width = this.width;
80        image_original_height = this.height;
81    };
82
83    clear_canvas();
84
85  });
86
87  // Use this while we work on the endpoint above
88  // $.getJSON("jsondata/unlabeled.json", function(data) {
89  //   image_url = data.url;
90  //   image_url_o = data.url_o;
91  //   image_id = data.id;
92  //
93  //   var img = new Image();
94  //   img.src = image_url_o;
95  //
96  //   // Get image meta
97  //   img.onload = function(){
98  //       image_original_width = this.width;
99  //       image_original_height = this.height;
100  //     };
101  //
102  //     clear_canvas();
103  // });
104
105}
106
107async function load_image_mobile(){
108  mobile_device = true
109  load_image();
110  await sleep(1000);
111  alert('This tool is currently experimental for mobile devices. We recommend using a computer for better user experience and results, but if you wish to continue with this device follow our tips.')
112  await sleep(1000);
113  hr_enabled = true;
114  clear_canvas();
115
116}
117
118function sleep(ms) {
119  return new Promise(resolve => setTimeout(resolve, ms));
120}
121
122function line_intersects(p0, p1, p2, p3) {
123    var s1_x, s1_y, s2_x, s2_y;
124    s1_x = p1['x'] - p0['x'];
125    s1_y = p1['y'] - p0['y'];
126    s2_x = p3['x'] - p2['x'];
127    s2_y = p3['y'] - p2['y'];
128
129    var s, t;
130    s = (-s1_y * (p0['x'] - p2['x']) + s1_x * (p0['y'] - p2['y'])) / (-s2_x * s1_y + s1_x * s2_y);
131    t = ( s2_x * (p0['y'] - p2['y']) - s2_y * (p0['x'] - p2['x'])) / (-s2_x * s1_y + s1_x * s2_y);
132
133    if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
134    {
135        // Collision detected
136        return true;
137    }
138    return false; // No collision
139}
140
141function point(x, y, color){
142    ctx.fillStyle= color;
143    ctx.strokeStyle = color;
144    ctx.fillRect(x-3,y-3,6,6);
145    ctx.fill
146    ctx.moveTo(x,y);
147}
148
149function undo(){
150    ctx = undefined;
151    perimeter.pop();
152    start(true);
153}
154
155
156function change_resolution(){
157  button_background = document.getElementById('skip_btn').style;
158    if (hr_enabled) {
159        document.getElementById('hr_button').style = button_background;
160    } else {
161        document.getElementById('hr_button').style.backgroundColor = "rgb(188,190,191)";
162    }
163    hr_enabled = !hr_enabled;
164
165    // Actual restart image
166    start(true);
167}
168
169function scale_polygons(scale_x, scale_y){
170
171  for(var j=0; j<perimeters.length; j++){
172    perimeter_j = perimeters[j]
173    for(var i=0; i<perimeter_j.length; i++){
174        perimeter_j[i]['x'] = perimeter_j[i]['x']*scale_x;
175        perimeter_j[i]['y'] = perimeter_j[i]['y']*scale_y;
176    }
177  }
178  for(var i=0; i<perimeter.length; i++){
179    perimeter[i]['x'] = perimeter[i]['x']*scale_x;
180    perimeter[i]['y'] = perimeter[i]['y']*scale_y;
181  }
182}
183
184function redraw(){
185
186  ctx.lineWidth = 2;
187
188  for(var j=0; j<perimeters.length; j++){
189    perimeter_old = perimeters[j]
190    ctx.strokeStyle = object_colors[j];
191    ctx.lineCap = "round";
192    ctx.beginPath();
193    for(var i=0; i<perimeter_old.length; i++){
194        if(i==0){
195          ctx.moveTo(perimeter_old[i]['x'],perimeter_old[i]['y']);
196        } else {
197          ctx.lineTo(perimeter_old[i]['x'],perimeter_old[i]['y']);
198        }
199    }
200    ctx.lineTo(perimeter_old[0]['x'],perimeter_old[0]['y']);
201    ctx.closePath();
202    ctx.fillStyle = object_colors_transparent[j]
203    ctx.fill();
204    ctx.stroke();
205
206    // Redraw anchor points
207    for(var i=0; i<perimeter_old.length; i++){
208      ctx.moveTo(perimeter_old[i]['x'],perimeter_old[i]['y']);
209      point(perimeter_old[i]['x'],perimeter_old[i]['y'],object_colors[j]);
210    }
211    ctx.stroke();
212  }
213}
214
215// This gets called on page loading
216function clear_canvas(){
217
218    ctx = undefined;
219    perimeter = new Array();
220    perimeters = new Array();
221    perimeter_per_object = new Array();
222    object_cats = new Array();
223    object_ctxs = new Array();
224    object_colors = new Array();
225    object_colors_transparent = new Array();
226
227    perimeter_per_object.push(0);
228
229    fill_class_list();
230
231    $('#object_btn_list').empty();
232
233    start();
234}
235
236function draw(full,end){
237    ctx.lineWidth = 2;
238    //ctx.strokeStyle = "white";
239    ctx.lineCap = "round";
240    ctx.beginPath();
241
242    // Draw only last line
243    if(!full){
244      nr_pts = perimeter.length;
245      if(nr_pts==1){
246        ctx.moveTo(perimeter[0]['x'],perimeter[0]['y']);
247        end || point(perimeter[0]['x'],perimeter[0]['y'],object_color);
248      }else{
249        point(perimeter[nr_pts-2]['x'],perimeter[nr_pts-2]['y'],object_color);
250        ctx.lineTo(perimeter[nr_pts-1]['x'],perimeter[nr_pts-1]['y']);
251        end || point(perimeter[nr_pts-1]['x'],perimeter[nr_pts-1]['y'],object_color);
252      }
253    }else{
254      // Draw all segment
255      for(var i=0; i<perimeter.length; i++){
256          if(i==0){
257              ctx.moveTo(perimeter[i]['x'],perimeter[i]['y']);
258              end || point(perimeter[i]['x'],perimeter[i]['y'],object_color);
259          } else {
260              ctx.lineTo(perimeter[i]['x'],perimeter[i]['y']);
261              end || point(perimeter[i]['x'],perimeter[i]['y'],object_color);
262          }
263      }
264      if(end){
265        ctx.lineTo(perimeter[0]['x'],perimeter[0]['y']);
266        ctx.closePath();
267        ctx.fillStyle = object_color_transparent
268        ctx.fill();
269        ctx.strokeStyle = object_color
270      }
271    }
272    ctx.stroke();
273    // print coordinates
274    // if(perimeter.length == 0){
275    //     document.getElementById('coordinates').value = '';
276    // } else {
277    //     document.getElementById('coordinates').value = JSON.stringify(perimeter);
278    // }
279}
280
281function check_intersect(x,y){
282    if(perimeter.length < 4){
283        return false;
284    }
285    var p0 = new Array();
286    var p1 = new Array();
287    var p2 = new Array();
288    var p3 = new Array();
289
290    p2['x'] = perimeter[perimeter.length-1]['x'];
291    p2['y'] = perimeter[perimeter.length-1]['y'];
292    p3['x'] = x;
293    p3['y'] = y;
294
295    for(var i=0; i<perimeter.length-1; i++){
296        p0['x'] = perimeter[i]['x'];
297        p0['y'] = perimeter[i]['y'];
298        p1['x'] = perimeter[i+1]['x'];
299        p1['y'] = perimeter[i+1]['y'];
300        if(p1['x'] == p2['x'] && p1['y'] == p2['y']){ continue; }
301        if(p0['x'] == p3['x'] && p0['y'] == p3['y']){ continue; }
302        if(line_intersects(p0,p1,p2,p3)==true){
303            return true;
304        }
305    }
306    return false;
307}
308
309function hover(event) {
310  rect = canvas.getBoundingClientRect();
311  x = event.clientX - rect.left;
312  y = event.clientY - rect.top;
313
314  if (perimeter.length>0){
315    sqr_dist_2_origin = Math.pow(x - perimeter[0]['x'],2) + Math.pow(y - perimeter[0]['y'],2);
316    sqr_dist_2_last_point = Math.pow(x - perimeter[perimeter.length-1]['x'],2) + Math.pow(y - perimeter[perimeter.length-1]['y'],2);
317
318    if (sqr_dist_2_origin<min_dist_2_anchor || sqr_dist_2_last_point<min_dist_2_anchor){
319      canvas.style.cursor="default";
320    }else{
321      canvas.style.cursor="crosshair";
322    }
323  }else{
324    canvas.style.cursor="crosshair";
325  }
326  return
327}
328
329function point_it(event) {
330
331    //TODO: canvas.style.cursor="default";
332
333    var rect, x, y;
334
335    if(event.ctrlKey || event.which === 3 || event.button === 2){
336        if(perimeter.length==2){
337            alert('You need at least three points');
338            return false;
339        }
340
341        close_polygon();
342
343	      event.preventDefault();
344        return false;
345    } else {
346        rect = canvas.getBoundingClientRect();
347        x = event.clientX - rect.left;
348        y = event.clientY - rect.top;
349
350        if (perimeter.length>0){
351
352          sqr_dist_2_last_point = Math.pow(x - perimeter[perimeter.length-1]['x'],2) + Math.pow(y - perimeter[perimeter.length-1]['y'],2);
353
354          if (sqr_dist_2_last_point < min_dist_2_anchor){
355              undo()
356              return false;
357          }
358
359          if (perimeter.length>1000){
360            alert('You have inserted too many points!');
361            return false;
362          }
363
364          sqr_dist_2_origin = Math.pow(x - perimeter[0]['x'],2) + Math.pow(y - perimeter[0]['y'],2);
365
366          if (sqr_dist_2_origin < min_dist_2_anchor){
367              close_polygon();
368              event.preventDefault();
369              // Ask user if we are done w/ this object
370              $("#modal_1").modal();
371              return false;
372          }
373          if(check_intersect(x,y)){
374              alert('The line you are drawing intersect another line');
375              return false;
376          }
377        }
378        perimeter.push({'x':x,'y':y});
379        draw(false,false);
380        return false;
381    }
382}
383
384function close_polygon(){
385
386  draw(true,true);
387  // Save segmentation and reset perimeter
388  perimeters.push(perimeter);
389  object_colors.push(object_color)
390  object_colors_transparent.push(object_color_transparent)
391  perimeter = new Array();
392
393}
394
395function add_object_step_1(){
396  // Close modal
397  $('#modal_1').modal('hide');
398
399  perimeter_per_object.push(perimeters.length)
400
401  // Open litter tag
402  $('#modal_2').modal();
403}
404
405function add_object_step_2(){
406
407  var object_cat = $("#option_cat").val();
408  //var object_ctx = $("#option2").val();
409  if (document.querySelector('input[name="contex"]:checked')){
410    var object_ctx = document.querySelector('input[name="contex"]:checked').value;
411  }else{
412    alert('Select the context first');
413    return;
414  }
415
416  if (object_cat == 'base'){
417    alert('Select the object class first');
418    return;
419  }
420
421  object_cats.push(object_cat);
422  object_ctxs.push(object_ctx);
423
424  // Close modal
425  $('#modal_2').modal('hide');
426
427  if(!mobile_device){
428    // Adding object button
429    object_id = object_cats.length-1;
430    var obj_btn =
431    '<button onclick="edit_object('+object_id+')" style="background-color:'+object_color_transparent+'" id="btn_'+object_id+'"> '+ cat_sorted_by_id[object_cats[object_id]] +' </button>';
432    $('#object_btn_list').append(obj_btn);
433  }
434
435  // Reset object color
436  var hue = ~~(Math.random() * 360);
437  object_color = "hsla(" + hue + ", 100%, 60%, 1.0)";
438  object_color_transparent = "hsla(" + hue + ", 100%, 60%, 0.5)";
439
440  // Add object
441  alert('Object added.');
442
443}
444
445function edit_object(object_id){
446
447  object_id_selected = object_id;
448
449  // Show modal with current cat value
450  $("#modal_4").modal();
451  $('#option_cat_edit').val(object_cats[object_id]).trigger('change.customSelect');
452
453  // Show ctx current selection
454  obj_ctx_selected = "#ctx_"+object_ctxs[object_id];
455  $(obj_ctx_selected).prop("checked", true);
456
457}
458
459function delete_object(){
460
461  $("#modal_4").modal("hide");
462
463  object_cats.splice(object_id_selected,1);
464  object_ctxs.splice(object_id_selected,1);
465  nr_perimeters_2_delete = perimeter_per_object[object_id_selected+1] - perimeter_per_object[object_id_selected];
466  perimeters.splice(object_id_selected,nr_perimeters_2_delete);
467  object_colors.splice(object_id_selected,1);
468  object_colors_transparent.splice(object_id_selected,1);
469
470  perimeter_per_object.splice(object_id_selected,1);
471  for(i=object_id_selected; i<perimeter_per_object.length;i++){
472    perimeter_per_object[i] = perimeter_per_object[i] - nr_perimeters_2_delete;
473  }
474
475  // Update buttons
476  $('#object_btn_list').empty();
477  for(i =0; i<object_cats.length; i++){
478    // Adding object button
479    var obj_btn =
480    '<button onclick="edit_object('+i+')" style="background-color:'+object_colors_transparent[i]+'" id="btn_'+i+'"> '+ cat_sorted_by_id[object_cats[i]] +' </button>';
481    $('#object_btn_list').append(obj_btn);
482
483  }
484  // Draw
485  start(true);
486}
487
488function change_object_class(){
489
490  // Update object tags
491  object_cats[object_id_selected] = $("#option_cat_edit").val();
492  object_ctxs[object_id_selected] = document.querySelector('input[name="contex_edit"]:checked').value;
493
494  // Update buttons
495  $('#object_btn_list').empty();
496  for(i =0; i<object_cats.length; i++){
497    // Adding object button
498    var obj_btn =
499    '<button onclick="edit_object('+i+')" style="background-color:'+object_colors_transparent[i]+'" id="btn_'+i+'"> '+ cat_sorted_by_id[object_cats[i]] +' </button>';
500    $('#object_btn_list').append(obj_btn);
501
502  }
503  $("#modal_4").modal("hide");
504
505}
506
507
508function add_object(){
509  var object_cat = $("#option_cat").val();
510  var object_ctx = document.querySelector('input[name="contex"]:checked').value;
511
512  if (object_cat == 'base'){
513    alert('Select the object class first');
514    return;
515  }
516  if (object_ctx == 'base'){
517    alert('Select the context first');
518    return;
519  }
520  if (perimeter_per_object[perimeter_per_object.length-1] == perimeters.length){
521    alert('Object not segmented');
522    return;
523  }
524
525  object_cats.push(object_cat)
526  object_ctxs.push(object_ctx)
527
528  perimeter_per_object.push(perimeters.length)
529
530  alert('Object added.');
531
532  fill_class_list();
533
534  // Reset object color
535  var hue = ~~(Math.random() * 360);
536  object_color = "hsla(" + hue + ", 100%, 60%, 1.0)";
537  object_color_transparent = "hsla(" + hue + ", 100%, 60%, 0.5)";
538}
539
540function start(with_draw) {
541
542    if(hr_enabled || !multiple_images_available){
543      url_selected = image_url_o;
544    }else{
545      url_selected= image_url;
546    }
547
548    loadImage(
549      url_selected,
550      function(img, data) {
551        if (img.type === "error") {
552          console.error("Error loading image " + imageUrl);
553          load_image();
554        } else {
555
556          img_width = img.width;
557          img_height = img.height;
558
559          console.log(url_selected);
560          // console.log("Exif data: ", data);
561
562          // Scale image if necessary
563          if(!hr_enabled && !multiple_images_available){
564            if (img_width>img_height){
565              var s = 640/img_width;
566            }else{
567              var s = 640/img_height;
568            }
569            img_width = img_width*s;
570            img_height = img_height*s;
571          }
572
573          // Scale polygons if image resolution change (hr toggle)
574          if(canvas.width != img_width){
575            scale_polygons(img_width/canvas.width,img_height/canvas.height);
576          }
577
578          ctx = canvas.getContext("2d");
579          canvas.width = img_width;
580          canvas.height = img_height;
581          ctx.drawImage(img, 0, 0, img_width, img_height);
582
583          if(with_draw == true){
584              redraw();
585              draw(true,false);
586          }
587        }
588      },
589      { orientation: true}
590    );
591}
592
593function showTutorial(){
594  $('#modal_demo').modal();
595}
596
597function fill_class_list(){
598
599    $.getJSON("jsondata/metadata.json", function(data) {
600      vals = data.cats_verbose.split(",");
601      cat_ids = data.cat_ids.split(",");
602
603      $.each(cat_ids, function(index, value) {
604        value_int = parseInt(value)
605        cat_sorted_by_id[value_int] = vals[index];
606      });
607
608      var option1 = $("#option1");
609      option1.empty();
610      option1.append("<option selected value=\"base\">Class</option>");
611      $.each(vals, function(index, value) {
612        cat_id = cat_ids[index];
613        option1.append("<option value="+cat_id+">" + value + "</option>");
614      });
615
616      // Filling modal 3:
617      var option1_modal = $("#option_cat");
618      option1_modal.empty();
619      option1_modal.append("<option selected value=\"base\">Class</option>");
620      $.each(vals, function(index, value) {
621        cat_id = cat_ids[index];
622        option1_modal.append("<option value="+cat_id+">" + value + "</option>");
623      });
624
625      // Filling modal 4:
626      if (!mobile_device){
627        var option1_modal_edit = $("#option_cat_edit");
628        option1_modal_edit.empty();
629        option1_modal_edit.append("<option selected value=\"base\">Class</option>");
630        $.each(vals, function(index, value) {
631          cat_id = cat_ids[index];
632          option1_modal_edit.append("<option value="+cat_id+">" + value + "</option>");
633        });
634      }
635
636
637    });
638
639}
640
641function submit_step_1(){
642
643  if (object_cats.length==0){
644    alert('No objects to submit.');
645    return;
646  }
647
648  $("#modal_3").modal();
649
650}
651
652function submit_step_2(){
653
654  // Close modal
655  $('#modal_3').modal('hide');
656
657  var scene_tags = new Array()
658  var vegetation = document.getElementById("vegetation");
659  if (vegetation.checked){scene_tags.push(parseInt(vegetation.value));}
660  var road = document.getElementById("road");
661  if (road.checked){scene_tags.push(parseInt(road.value));}
662  var sand = document.getElementById("sand");
663  if (sand.checked){scene_tags.push(parseInt(sand.value));}
664  var water = document.getElementById("water");
665  if (water.checked){scene_tags.push(parseInt(water.value));}
666  var trash = document.getElementById("trash");
667  if (trash.checked){scene_tags.push(parseInt(trash.value));}
668  var indoor = document.getElementById("indoor");
669  if (indoor.checked){scene_tags.push(parseInt(indoor.value));}
670
671  vegetation.checked = false;
672  road.checked = false;
673  sand.checked = false;
674  water.checked = false;
675  trash.checked = false;
676  indoor.checked = false;
677
678  var username = document.getElementById("username").value;
679  var email = document.getElementById("email").value;
680
681  // Scale segmentation if needed
682  if(!hr_enabled){
683    scale_polygons(image_original_width/canvas.width,image_original_height/canvas.height);
684  }
685
686  // Prepare submission data
687  submission = {
688    "id": image_id,
689    'images': [],
690    'scene_annotations': []
691  }
692
693  image = {
694        "id": image_id,
695        "width": image_original_width,
696        "height": image_original_height,
697        "flickr_url": image_url_o,
698        "flickr_640_url": image_url+' '+username+' '+email,
699        'segmentation': [],
700  }
701  scene_annotation = {
702    "image_id": image_id,
703    "background_ids": scene_tags
704  }
705  submission['scene_annotations'].push(scene_annotation);
706
707  // Adding objects to submssion
708  for (obj_i=0; obj_i<object_cats.length; obj_i++){
709
710    //Converting and adding polygons to object segmentation
711    segmentations = new Array();
712    for(seg_j = perimeter_per_object[obj_i]; seg_j<perimeter_per_object[obj_i+1]; seg_j++){
713      perimeter_j = perimeters[seg_j];
714      for(i = 0; i<perimeter_j.length; i++){
715        segmentations.push(perimeter_j[i]['x']);
716        segmentations.push(perimeter_j[i]['y']);
717      }
718      // Adding a flag to signal polygon end/start
719      segmentations.push(-1);
720    }
721
722    const annotation = {
723      "y": segmentations,
724      "category_id": object_cats[obj_i],
725      "context": object_ctxs[obj_i]
726    }
727    image['segmentation'].push(annotation)
728  }
729
730  submission['images'].push(image)
731
732  const submission_stringified = JSON.stringify(submission)
733
734  console.log(submission_stringified)
735
736  // Actual submission using jQuery and AssociateSegmentation endpoint
737  // This will be stored as file at the server and update the DB
738  $.ajax({
739    url: 'http://178.128.33.132:5901/twirp/v1.taco.annotations.Service/AssociateSegmentation',
740    dataType: 'json',
741    type: 'POST',
742    contentType: 'application/json',
743    data: JSON.stringify(submission),
744    success: alert('Annotation submitted. Thanks good samaritan! One more?')
745  });
746
747  // Update job counter and save a local copy of annotations
748  if(!mobile_device){
749
750    // Creating now annotations for user according to COCO format
751    annotations_alla_coco = new Array();
752    for (obj_i=0; obj_i<object_cats.length; obj_i++){
753      //Converting and adding polygons to object segmentation
754      segmentations = new Array();
755      for(seg_j = perimeter_per_object[obj_i]; seg_j<perimeter_per_object[obj_i+1]; seg_j++){
756        segmentation = new Array();
757        perimeter_j = perimeters[seg_j];
758        for(i = 0; i<perimeter_j.length; i++){
759          segmentation.push(parseInt(perimeter_j[i]['x']));
760          segmentation.push(parseInt(perimeter_j[i]['y']));
761        }
762        segmentations.push(segmentation);
763      }
764
765      const annotation = {
766        "id": object_cats.length,
767        "image_id": image_id,
768        "segmentation": segmentations,
769        "category_id": object_cats[obj_i],
770        "context": object_ctxs[obj_i]
771      }
772
773      annotations_alla_coco.push(annotation)
774    }
775
776    // Create annotation to download
777    submission_copy = {
778          'images': [{"id": image_id, "width": image_original_width, "height": image_original_height,
779            "flickr_url": image_url_o,
780            "flickr_640_url": image_url}],
781          'annotations': annotations_alla_coco,
782          'scene_annotations': [scene_annotation],
783    }
784    const submission_copy_stringified = JSON.stringify(submission_copy)
785    // Exports submission copy
786    function download(content, fileName, contentType) {
787      var a = document.createElement("a");
788      var file = new Blob([content], {type: contentType});
789      a.href = URL.createObjectURL(file);
790      a.download = fileName;
791      a.click();
792    }
793
794    var filename = image_id.toString().concat('.json')
795
796    download(submission_copy_stringified, filename, 'text/plain');
797
798    nr_annotations_reviewed_so_far++;
799    document.getElementById('jobs_counter').innerHTML = " Jobs completed during this session: <code>"+nr_annotations_reviewed_so_far+"</code>";
800  }
801
802  load_image();
803
804}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.