/** @file File containing the video class
* Video is a mirror class of image.js. Most of the functions are
* similar and could be joined
*/
/**
* Creates a video object
* @constructor
* @param {string} id - The id of the dom element containing the video
*/
function video(id) {
// *******************************************
// Private variables:
// *******************************************
this.page_in_use = 0; // Describes if we already see a video.
this.id = id;
this.dir_name = null;
this.im = document.getElementById("im");
this.dir_name;
this.video_name = null;
this.width_orig;
this.height_orig;
this.width_curr; //current width and height of the image itself
this.height_curr;
this.im_ratio; // Ratio of (displayed image dims) / (orig image dims)
this.browser_im_ratio; // Initial im_ratio; this should not get changed!!
this.curr_frame_width; // Current width of main_media.
this.curr_frame_height; // Current height of main_media.
// *******************************************
// Public methods:
// *******************************************
/** Fetches a new video based on the URL string. Creates a div container to store the video */
this.GetNewVideo = function(onload_helper) {
var videodiv = '<div id="'+id+'" videosrc="" videoautoplay="true" style="vertical-align:bottom;z-index:-4;"></div>';
$('#main_section').append(videodiv);
$('#main_media').detach().appendTo('#'+id);
document.getElementById('loading').style.display = '';
if(IsMicrosoft()) this.im.style.visibility = 'hidden';
else this.im.style.display = '';
wait_for_input = 0;
edit_popup_open = 0;
this.SetImageDimensions();
console.time('Load LabelMe XML file');
oVP = new JSVideo();
console.time('Load video');
oVP.loadChunk(1, 1, true, false);
};
/** Returns the ratio of the available width/height to the original
* width/height. For now, it is always 1 */
this.GetImRatio = function() {
return this.im_ratio;
};
/** Returns self object in order to be compliant with the functions from file_info*/
this.GetFileInfo = function() {
return this;
};
/** Sets the dimensions of the image based on browser setup. For now it sets 640x480 */
this.SetImageDimensions = function() {
this.im_ratio = 1.;
this.width_curr = 640;
this.height_curr = 480;
this.width_orig = 640;
this.height_orig = 480;
$("#myCanvas_bg").width(this.width_curr).height(this.height_curr);
$("#select_canvas").width(this.width_curr).height(this.height_curr);
$("#draw_canvas").width(this.width_curr).height(this.height_curr);
$("#query_canvas").width(this.width_curr).height(this.height_curr);
$("#main_media").width(this.width_curr).height(this.height_curr);
this.curr_frame_width = this.width_curr;
this.curr_frame_height = this.height_curr;
document.getElementById('loading').style.visibility = 'hidden';
document.getElementById('main_media').style.visibility = 'visible';
if(IsMicrosoft()) {
this.im.style.visibility = '';
document.getElementById('main_media').style.overflow = 'visible';
this.ScaleFrame();
}
else this.im.style.display = '';
};
/** If (x,y) is not in view, then scroll it into view. Return adjusted
* (x,y) point that takes into account the slide offset.
* @param {int} x
* @param {int} y
* @returns {intarray}
*/
this.SlideWindow = function (x,y) {
var pt = Array(2);
if(!this.IsPointVisible(x,y)) {
document.getElementById('main_media').scrollLeft = x-100;
document.getElementById('main_media').scrollTop = y-100;
}
pt[0] = x-$("#main_media").scrollLeft();
pt[1] = y-$("#main_media").scrollTop();
return pt;
};
/** Turn off image scrollbars if zoomed in. */
this.ScrollbarsOff = function () {
if(!this.IsFitImage()) {
document.getElementById('main_media').style.overflow = 'hidden';
}
};
/** Turn on image scrollbars if zoomed in. */
this.ScrollbarsOn = function () {
if(!this.IsFitImage()) {
document.getElementById('main_media').style.overflow = 'auto';
}
};
/** Not working now */
this.Zoom = function(amt) {
// if a new polygon is being added while the user press the zoom button then do nothing.
if(wait_for_input) return;
// if an old polygon is being edited while the user press the zoom button then close the polygon and zoom.
if(edit_popup_open) StopEditEvent();
if(amt=='fitted') {
this.im_ratio = this.browser_im_ratio;
} else {
this.im_ratio = this.im_ratio * amt;
}
// if the scale factor is bellow the original scale, then do nothing (do not make the image too small)
if(this.im_ratio < this.browser_im_ratio) {this.im_ratio=this.browser_im_ratio; return;}
// New width and height of the rescaled picture
this.width_curr = Math.round(this.im_ratio*this.width_orig);
this.height_curr = Math.round(this.im_ratio*this.height_orig);
// Scale and scroll the image so that the center stays in the center of the visible area
this.ScaleFrame(amt);
// Remove polygon from draw canvas:
var anno = null;
if(draw_anno) {
draw_anno.DeletePolygon();
anno = draw_anno;
draw_anno = null;
}
// set the size of the image (this.im is the image object)
this.im.width = this.width_curr;
this.im.height = this.height_curr;
// set the size of all the canvases
$("#myCanvas_bg").width(this.width_curr).height(this.height_curr);
$("#select_canvas").width(this.width_curr).height(this.height_curr);
$("#draw_canvas").width(this.width_curr).height(this.height_curr);
$("#query_canvas").width(this.width_curr).height(this.height_curr);
// Redraw polygons.
main_canvas.RenderAnnotations();
if(anno) {
// Draw polyline:
draw_anno = anno;
draw_anno.SetDivAttach('draw_canvas');
draw_anno.DrawPolyLine();
}
/*************************************************************/
/*************************************************************/
// Scribble:
if (drawing_mode == 1){
scribble_canvas.redraw();
scribble_canvas.drawMask();
}
/*************************************************************/
/*************************************************************/
};
// *******************************************
// Private methods:
// *******************************************
/** Tells the picture to take up the available space in the browser, if it needs it. 6.29.06*/
this.ScaleFrame = function(amt) {
// Look at the available browser (height,width) and the image (height,width),
// and use the smaller of the two for the main_media (height,width).
// also center the image so that after rescaling, the center pixels visible stays at the same location
//var avail_height = this.GetAvailHeight();
this.curr_frame_height = Math.min(this.GetAvailHeight(), this.height_curr);
//var avail_width = this.GetAvailWidth();
this.curr_frame_width = Math.min(this.GetAvailWidth(), this.width_curr);
// also center the image so that after rescaling, the center pixels visible stays at the same location
cx = $("#main_media").scrollLeft()+this.curr_frame_width/2.0; // current center
cy = $("#main_media").scrollTop()+this.curr_frame_height/2.0;
Dx = Math.max(0, $("#main_media").scrollLeft()+(amt-1.0)*cx); // displacement needed
Dy = Math.max(0, $("#main_media").scrollTop()+(amt-1.0)*cy);
// set the width and height and scrolls
$("#main_media").scrollLeft(Dx).scrollTop(Dy);
$("#main_media").width(this.curr_frame_width).height(this.curr_frame_height);
};
/** Retrieves and sets the original image's dimensions (width/height).
* @param {image} im
*/
this.SetOrigImDims = function (im) {
this.width_orig = im.width;
this.height_orig = im.height;
return;
};
/** gets available width (6.14.06) */
this.GetAvailWidth = function() {
return $(window).width() - $("#main_media").offset().left -10 - 200;
// we could include information about the size of the object box using $("#anno_list").offset().left
};
/** gets available height (6.14.06) */
this.GetAvailHeight = function() {
var m = main_media.GetFileInfo().GetMode();
if(m=='mt') {
return $(window).height() - $("#main_media").offset().top -75;
}
return $(window).height() - $("#main_media").offset().top -10;
};
/** Returns true if the image is zoomed to the original (fitted) resolution. */
this.IsFitImage = function () {
return (this.im_ratio < 0.01+this.browser_im_ratio);
};
/** Returns true if (x,y) is viewable. */
this.IsPointVisible = function (x,y) {
var scrollLeft = $("#main_media").scrollLeft();
var scrollTop = $("#main_media").scrollTop();
if(((x*this.im_ratio < scrollLeft) ||
(x*this.im_ratio - scrollLeft > this.curr_frame_width - 160)) ||
((y*this.im_ratio < scrollTop) ||
(y*this.im_ratio - scrollTop > this.curr_frame_height)))
return false; //the 160 is about the width of the right-side div
return true;
};
/** Parses url again, knowing that is a video. The function is almost a replica of that in file_info */
this.ParseURL = function () {
var labelme_url = document.URL;
var idx = labelme_url.indexOf('?');
if((idx != -1) && (this.page_in_use == 0)) {
this.page_in_use = 1;
var par_str = labelme_url.substring(idx+1,labelme_url.length);
var isMT = false; // In MT mode?
var default_view_ObjList = false;
do {
idx = par_str.indexOf('&');
var par_tag;
if(idx == -1) par_tag = par_str;
else par_tag = par_str.substring(0,idx);
var par_field = this.GetURLField(par_tag);
var par_value = this.GetURLValue(par_tag);
if(par_field=='mode'){
this.mode = par_value;
if(this.mode=='im' || this.mode=='mt') view_ObjList = false;
}
if(par_field=='username') {
username = par_value;
}
if(par_field=='folder') {
this.dir_name = par_value;
console.log(par_value);
}
if(par_field=='videoname') {
this.video_name = par_value;
}
if((par_field=='scribble')&&(par_value=='true')) {
scribble_mode = true;
}
if((par_field=='video')&&(par_value=='true')) {
video_mode = true;
}
par_str = par_str.substring(idx+1,par_str.length);
} while(idx != -1);
if((this.mode=='i') || (this.mode=='c') || (this.mode=='f')) {
document.getElementById('body').style.visibility = 'visible';
}
else {
this.mode = 'i';
document.getElementById('body').style.visibility = 'visible';
}
if(!view_ObjList) {
var p = document.getElementById('anno_anchor');
p.parentNode.removeChild(p);
}
}
return 1;
};
/** Gets mode */
this.GetDirName = function () {
return this.dir_name;
};
/** Gets image name */
this.GetImName = function () {
return this.video_name;
};
/** Gets image path */
this.GetImagePath = function () {
if((this.mode=='i') || (this.mode=='c') || (this.mode=='f') || (this.mode=='im') || (this.mode=='mt')) return 'VLMVideos/' + this.dir_name + '/' + this.video_name;
};
/** Gets full image name */
this.GetFullName = function () {
if((this.mode=='i') || (this.mode=='c') || (this.mode=='f') || (this.mode=='im') || (this.mode=='mt')) return this.dir_name + '/' + this.video_name;
};
/** Gets template path */
this.GetTemplatePath = function () {
if(!this.dir_name) return 'annotationCache/XMLTemplates/labelme.xml';
return 'annotationCache/XMLTemplates/' + this.dir_name + '.xml';
};
/** String is assumed to have field=value form. Parses string to
return the field. */
this.GetURLField = function (str) {
var idx = str.indexOf('=');
return str.substring(0,idx);
};
/** String is assumed to have field=value form. Parses string to
return the value. */
this.GetURLValue = function (str) {
var idx = str.indexOf('=');
return str.substring(idx+1,str.length);
};
/** Computes a set of points representing a linearly interpolated polygon.
* @param {array} xinit - xcoordinates of the polygon at tinit
* @param {array} yinit - ycoordinates of the polygon at tinit
* @param {array} xend - xcoordinates of the polygon at tend
* @param {array} yend - ycoordinates of the polygon at tend
* @param {int} tinit - first ground truth frame number
* @param {int} tend - last ground truth frame number
* @param {int} tcurrent - frame that has to be interpolated
*/
this.GetInterpolatedPoints = function (xinit, yinit, xend, yend, tinit, tend, tcurrent){
Xresp = Array(xinit.length);
Yresp = Array(xinit.length);
for (var i = 0; i <Xresp.length; i++){
alfa = (tend - tcurrent)/(tend-tinit);
Xresp[i] = alfa*xinit[i] + (1-alfa)*xend[i];
Yresp[i] = alfa*yinit[i] + (1-alfa)*yend[i];
}
return [Xresp, Yresp];
}
/** Update the position of an annotation. Modifies the xml file information about a given annotation.
* @param {annotation} anno - annotation containing the info about the polygon
*/
this.UpdateObjectPosition = function (anno){
console.log(LM_xml);
var obj_ndx = anno.anno_id;
var curr_obj = $(LM_xml).children("annotation").children("object").eq(obj_ndx);
var framestamps = (curr_obj.children("polygon").children("t").text()).split(',');
var userlabeledframes = (curr_obj.children("polygon").children("userlabeled").text()).split(',');
var pts_x = (curr_obj.children("polygon").children("x").text()).split(';');
var pts_y = (curr_obj.children("polygon").children("y").text()).split(';');
for(var ti=0; ti<framestamps.length; ti++) {
framestamps[ti] = parseInt(framestamps[ti], 10);
}
for (var ti = 0; ti < userlabeledframes.length; ti++){
userlabeledframes[ti] = parseInt(userlabeledframes[ti],10);
}
while (framestamps[0] > oVP.getcurrentFrame()){
framestamps.unshift(framestamps[0]-1);
pts_x.unshift(pts_x[0]);
pts_y.unshift(pts_y[0]);
}
var ti = 0;
while (ti < userlabeledframes.length && userlabeledframes[ti] <= oVP.getcurrentFrame()) ti++;
var ti2 = 0;
while (ti2 < userlabeledframes.length && userlabeledframes[ti2] < oVP.getcurrentFrame()) ti2++;
ti2--;//
var framenext = framestamps.length;
var frameprior = -1;
if (ti2 >= 0) frameprior = framestamps.indexOf(userlabeledframes[ti2]);
if (ti < userlabeledframes.length) framenext = framestamps.indexOf(userlabeledframes[ti]);
var objectind = framestamps.indexOf(oVP.getcurrentFrame());
// backward interpolation
for (var i = frameprior+1; i < objectind; i++){
var coords = [anno.pts_x, anno.pts_y];
if (frameprior > -1){
var Xref = pts_x[frameprior].split(',');
var Yref = pts_y[frameprior].split(',');
coords = this.GetInterpolatedPoints(Xref, Yref, anno.pts_x, anno.pts_y, framestamps[frameprior], framestamps[objectind], framestamps[i]);
}
pts_x[i] = coords[0].join();
pts_y[i] = coords[1].join();
}
pts_y[objectind] = anno.pts_y;
pts_x[objectind] = anno.pts_x;
// forward interpolation
for (var i = objectind+1; i < framenext; i++){
var coords = [anno.pts_x, anno.pts_y];
if (framenext < framestamps.length){
var Xref = pts_x[framenext].split(',');
var Yref = pts_y[framenext].split(',');
coords = this.GetInterpolatedPoints(anno.pts_x, anno.pts_y, Xref, Yref, framestamps[objectind], framestamps[framenext], framestamps[i]);
}
pts_x[i] = coords[0].join();
pts_y[i] = coords[1].join();
}
userlabeledframes.push(oVP.getcurrentFrame());
jQuery.unique(userlabeledframes);
userlabeledframes.sort(function(a, b){return a-b});
new_x_str = pts_x.join(';');
new_y_str = pts_y.join(';');
curr_obj.children("polygon").children("t").text(framestamps.join(','));
curr_obj.children("polygon").children("x").text(new_x_str);
curr_obj.children("polygon").children("y").text(new_y_str);
curr_obj.children("polygon").children("userlabeled").text(userlabeledframes.join());
}
/** Submits an edited object, stored in select_anno */
this.SubmitEditObject = function (){
submission_edited = 1;
var anno = select_anno;
// object name
old_name = LMgetObjectField(LM_xml,anno.anno_id,'name');
if(document.getElementById('objEnter')) new_name = RemoveSpecialChars(document.getElementById('objEnter').value);
else new_name = RemoveSpecialChars(adjust_objEnter);
var re = /[a-zA-Z0-9]/;
if(!re.test(new_name)) {
alert('Please enter an object name');
return;
}
if (use_attributes) {
// occlusion field
if (document.getElementById('occluded')) new_occluded = RemoveSpecialChars(document.getElementById('occluded').value);
else new_occluded = RemoveSpecialChars(adjust_occluded);
// attributes field
if(document.getElementById('attributes')) new_attributes = RemoveSpecialChars(document.getElementById('attributes').value);
else new_attributes = RemoveSpecialChars(adjust_attributes);
}
StopEditEvent();
// Object index:
var obj_ndx = anno.anno_id;
// Pointer to object:
var curr_obj = $(LM_xml).children("annotation").children("object").eq(obj_ndx);
// Set fields:
curr_obj.children("name").text(new_name);
if(curr_obj.children("automatic").length > 0) curr_obj.children("automatic").text("0");
// Insert attributes (and create field if it is not there):
if(curr_obj.children("attributes").length>0) curr_obj.children("attributes").text(new_attributes);
else curr_obj.append("<attributes>" + new_attributes + "</attributes>");
if(curr_obj.children("occluded").length>0) curr_obj.children("occluded").text(new_occluded);
else curr_obj.append("<occluded>" + new_occluded + "</occluded>");
this.UpdateObjectPosition(anno);
oVP.DisplayFrame(oVP.getcurrentFrame());
}
/** Submits an object for the first time. It is the equivalent of SubmitQuery for video */
this.SubmitObject = function (){
var nn;
var anno;
if (use_attributes) {
// get attributes (is the field exists)
if(document.getElementById('attributes')) new_attributes = RemoveSpecialChars(document.getElementById('attributes').value);
else new_attributes = "";
// get occlusion field (is the field exists)
if (document.getElementById('occluded')) new_occluded = RemoveSpecialChars(document.getElementById('occluded').value);
else new_occluded = "";
}
if((object_choices!='...') && (object_choices.length==1)) {
nn = RemoveSpecialChars(object_choices[0]);
active_canvas = REST_CANVAS;
// Move draw canvas to the back:
document.getElementById('draw_canvas').style.zIndex = -2;
document.getElementById('draw_canvas_div').style.zIndex = -2;
var anno = null;
if(draw_anno) {
anno = draw_anno;
draw_anno = null;
}
}
else {
nn = RemoveSpecialChars(document.getElementById('objEnter').value);
anno = main_handler.QueryToRest();
}
var re = /[a-zA-Z0-9]/;
if(!re.test(nn)) {
alert('Please enter an object name');
return;
}
// Update old and new object names for logfile:
submission_edited = 0;
global_count++;
// Insert data into XML:
var html_str = '<object>';
html_str += '<name>' + nn + '</name>';
if(use_attributes) {
html_str += '<occluded>' + new_occluded + '</occluded>';
html_str += '<attributes>' + new_attributes + '</attributes>';
}
html_str += '<parts><hasparts></hasparts><ispartof></ispartof></parts>';
var ts = 0;//GetTimeStamp();
if(ts.length==20) html_str += '<date>' + ts + '</date>';
html_str += '<id>' + anno.anno_id + '</id>';
html_str += '<polygon>';
html_str += '<username>' + username + '</username>';
var t_str = '<t>';
var x_str = '<x>';
var y_str = '<y>';
for (var fr = oVP.getcurrentFrame(); fr < oVP.getnumFrames(); fr++){
if (fr > oVP.getcurrentFrame()){
t_str += ', ';
x_str += '; ';
y_str += '; ';
}
t_str += fr;
for(var jj=0; jj < anno.GetPtsX().length; jj++) {
if (jj > 0){
x_str += ', ';
y_str += ', ';
}
x_str += anno.GetPtsX()[jj];
y_str += anno.GetPtsY()[jj];
}
}
t_str += '</t>';
x_str += '</x>';
y_str += '</y>';
html_str += t_str;
html_str += x_str;
html_str += y_str;
html_str += '<userlabeled>'+oVP.getcurrentFrame()+'</userlabeled>';
html_str += '</polygon>';
html_str += '<parts>'
html_str += '<hasparts/>'
html_str += '<ispartof/>'
html_str += '</parts>'
html_str += '</object>';
$(LM_xml).children("annotation").append($(html_str));
//ChangeLinkColorFG(anno.GetAnnoID());
$('#select_canvas').css('z-index','0');
$('#select_canvas_div').css('z-index','0');
$('#'+this.polygon_id).remove();
select_anno = anno;
// var anno = main_canvas.DetachAnnotation(anno.anno_id);
adjust_event = new AdjustEvent('select_canvas',anno.pts_x,anno.pts_y,LMgetObjectField(LM_xml,anno.anno_id,'name'),function(x,y,_editedControlPoints) {
// Submit username:
if(username_flag) submit_username();
// Redraw polygon:
anno = select_anno;
anno.DrawPolygon(main_media.GetImRatio());
// Set polygon (x,y) points:
anno.pts_x = x;
anno.pts_y = y;
// Set global variable whether the control points have been edited:
editedControlPoints = _editedControlPoints;
// Submit annotation:
StopEditEvent();
},main_media.GetImRatio());
// Start adjust event:
adjust_event.StartEvent();
};
}