问题描述
我想详细说明对此
ui.R
#改编自https://github.com/ kyamagu / bbox-annotator /
#编辑原始JS以添加color_list作为选项
#...应与标签
#相同长度并控制矩形的颜色
#...可能会因input_method = fixed或 text而损坏
#还为每个矩形条目中的值添加了颜色
js<-'
$(document).ready(function(){
//定义传递给边界框构造函数的选项
var options = {
url: https://www.r-project.org/logo/Rlogo.svg,
input_method:选择,
标签:[],
color_list:[],
onchange:函数(条目){
Shiny.onInputChange( rectCoord,JSON.stringify(entries,null,));
}
};
//初始化边界框注释器。
var annotator = new BBoxAnnotator(options);
//初始化重置按钮。
$(#reset_button)。click(function(e){
annotator.clear_all();
})
//定义函数以重置bbox
// //选择新标签类别或新url
函数reset_bbox(options){
document.getElementById( bbox_annotator)。setAttribute( style, display:内联块);
$(。image_frame)。remove();
annotator = new BBoxAnnotator(options);
}
//从闪亮的
更新图片网址Shiny.addCustomMessageHandler( change-img-url,function(url){
options.url = url ;
options.width = null;
options.height = null;
reset_bbox(options);
});
//从闪亮的
中更新颜色和类别Shiny.addCustomMessageHandler( update-category-list,function(vals){
options.labels = Object.values(vals );
options.color_list = Object.keys(vals);
reset_bbox(options;
});
//根据条目列表重新绘制矩形
Shiny.addCustomMessageHandler( redraw-rects,function(vals){
var arr = JSON.parse(vals);
arr.forEach(function(rect){
annotator.add_entry(rect);
});
if(annotator.onchange){
annotator.onchange( annotator.entries);
}
});
});
'
ui<-fluidPage(
tags $ head(tags $ script(HTML(js)),
tags $ head(
tags) $ script(src = bbox_annotation.js)
)),
titlePanel(边界框注释器演示),
sidebarLayout(
sidebarPanel(
selectInput(
img_url,
URLs,
c(
https://www.r-project.org/logo/Rlogo.svg,
https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
)
),
selectInput( category_type,标签类别,c( animals, fruits)),
div(HTML(
'< input id = reset_button type = reset />'
)),
HTML(
'< input id = annotation_data name = annotation_data type = hidden />'
),
hr(),
h4( Entries),
verbatimTextOutput( rectCoordOutput)
),
mainPanel(div(id = bbox_annotator,style = display:inline-block))
)
)
server.R
服务器<-功能(输入,输出,会话){
#用户选择
output $ rectCoordOutput< ;-renderPrint({
if(!is.null(input $ rectCoord)){
as.data.frame(jsonlite :: fromJSON(input $ rectCoord))
}
})
#将选定的URL从闪亮发送到JS
watchEvent(input $ img_url,{
session $ sendCustomMessage( change-img-url,input $ img_url)
})
#将选择的类别列表从闪亮发送到JS
watchEvent(input $ category_type,{
vals<-switch(input $ category_type,
水果=列表(黄色 =香蕉,
橙色 =菠萝,
粉红色 =葡萄柚),
动物= list(灰色 =浣熊,
棕色 =狗,
棕褐色 =猫)
)
#更新类别列表
session $ sendCustomMessage( update-category-list,vals)
#重新绘制矩形
session $ sendCustomMessage( redraw-rects ,输入$ rectCoord)
})
}
www / bbox_annotation.js
//由CoffeeScript 2.5.0生成
(function(){
// https://github.com/kyamagu/bbox-annotator/blob/master/bbox_annotator.coffee
//使用coffee-script编译器来获取javascript文件。
//咖啡-c bbox_annotator.coffee
//参见http://coffeescript.org/
// BBox选择窗口。
var BBoxSelector;
BBoxSelector = class BBoxSelector {
//在图像帧中初始化选择器。
构造函数(image_frame,options){
if(options == null){
options = {};
}
options.input_method || (options.input_method =文本);
this.image_frame = image_frame;
this.border_width = options.border_width || 2;
this.selector = $(’< div class = bbox_selector>< / div>’));
this.selector.css({
//拖动
border时为矩形颜色:this.border_width + px点缀rgb(127,255,127),
位置: 绝对
});
this.image_frame.append(this.selector);
this.selector.css({
border-width:this.border_width
});
this.selector.hide();
this.create_label_box(options);
}
//初始化标签输入框。
create_label_box(options){
var i,label,len,ref;
options.labels || (options.labels = [ object]);
this.label_box = $(’< div class = label_box style = z-index:1000>< / div>’);
this.label_box.css({
position: absolute
});
this.image_frame.append(this.label_box);
开关(options.input_method){
case'select':
if(typeof options.labels === string){
options.labels = [options.labels ];
}
this.label_input = $(’< select class = label_input name = label>< / select>’);
this.label_box.append(this.label_input);
this.label_input.append($(’< option value>选择一个项目< / option>’)));
ref = options.labels;
for(i = 0,len = ref.length; i< len; i ++){
label = ref [i];
this.label_input.append(’< option value =’+标签+’>’+标签+’< / option>’));
}
this.label_input.change(function(e){
return this.blur();
});
休息时间;
case‘text’:
if(typeof options.labels === string){
options.labels = [options.labels];
}
this.label_input = $(’< input class = label_input name = label’+‘type = text value>’);
this.label_box.append(this.label_input);
this.label_input.autocomplete({
source:options.labels || [’’,
autoFocus:true
});
休息时间;
的情况为固定:
if($ .isArray(options.labels)){
options.labels = options.labels [0];
}
this.label_input = $(’< input class = label_input name = label type = text>’);
this.label_box.append(this.label_input);
this.label_input.val(options.labels);
休息时间;
默认值:
throw‘无效的label_input参数:’+ options.input_method;
}
返回this.label_box.hide();
}
//将x和y裁剪为图像大小。
作物(pageX,pageY){
var point;
返回点= {
x:Math.min(Math.max(Math.round(pageX-this.image_frame.offset()。left),0),Math.round(this.image_frame.width ()-1)),
y:Math.min(Math.max(Math.round(pageY-this.image_frame.offset()。top),0),Math.round(this.image_frame.height( )-1))
};
}
//进行新选择时。
start(pageX,pageY){
this.pointer = this.crop(pageX,pageY);
this.offset = this.pointer;
this.refresh();
this.selector.show();
$('body')。css('cursor','crosshair');
返回document.onselectstart = function(){
return false;
};
}
//选区更新时。
update_rectangle(pageX,pageY){
this.pointer = this.crop(pageX,pageY);
返回this.refresh();
}
//开始输入标签时。
input_label(options){
$('body')。css('cursor','default');
document.onselectstart = function(){
返回true;
};
this.label_box.show();
返回this.label_input.focus();
}
//完成并返回注释。
finish(options){
var data;
this.label_box.hide();
this.selector.hide();
data = this.rectangle();
data.label = $ .trim(this.label_input.val()。toLowerCase());
if(options.input_method!=='fixed'){
this.label_input.val(’);
}
返回数据;
}
//获取一个矩形。
angle(){
var rect,x1,x2,y1,y2;
x1 = Math.min(this.offset.x,this.pointer.x);
y1 = Math.min(this.offset.y,this.pointer.y);
x2 = Math.max(this.offset.x,this.pointer.x);
y2 = Math.max(this.offset.y,this.pointer.y);
return rect = {
左:x1,
顶部:y1,
宽度:x2-x1 + 1,
高度:y2-y1 + 1
};
}
//更新方块的CSS。
refresh(){
var rect;
rect = this.rectangle();
this.selector.css({
left:(rect.left-this.border_width)+'px',
top:(rect.top-this.border_width)+'px' ,
宽度:rect.width +'px',
高度:rect.height +'px'
});
返回this.label_box.css({
left:(rect.left-this.border_width)+'px',
top:(rect.top + rect.height + this.border_width )+'px'
});
}
//返回输入元素。
get_input_element(){
返回this.label_input;
}
};
//注释器对象定义。
this.BBoxAnnotator = class BBoxAnnotator {
//初始化注释器布局和事件。
构造函数(选项){
var注释器,image_element;
注释者= this;
this.annotator_element = $(options.id || #bbox_annotator);
//允许我们在以后的步骤中访问颜色和标签
this.color_list = options.color_list;
this.label_list = options.labels;
this.border_width = options.border_width || 2;
this.show_label = options.show_label || (options.input_method!==固定);
if(options.multiple!= null){
this.multiple = options.multiple;
} else {
this.multiple = true;
}
this.image_frame = $(’< div class = image_frame>< / div>’);
this.annotator_element.append(this.image_frame);
if(options.guide){
annotator.initialize_guide(options.guide);
}
image_element = new Image();
image_element.src = options.url;
image_element.onload = function(){
options.width || (options.width = image_element.width);
options.height || (options.height = image_element.height);
annotator.annotator_element.css({
width:(options.width + annotator.border_width)+'px',
height:(options.height + annotator.border_width) +'px',
padding-left:(annotator.border_width / 2)+'px',
padding-top:(annotator.border_width / 2)+'px',
cursor:十字准线,
overflow:隐藏
});
annotator.image_frame.css({
background-image: url(' + image_element.src +'),
width:options.width + px ,
height:options.height + px,
position:相对
});
annotator.selector = new BBoxSelector(annotator.image_frame,options);
return annotator.initialize_events(options);
};
image_element.onerror = function(){
return annotator.annotator_element.text(无效的图片网址: + options.url);
};
this.entries = [];
this.onchange = options.onchange;
}
//初始化事件。
initialize_events(options){
var注释器,选择器,状态;
状态=免费;
this.hit_menuitem = false;
注释者= this;
选择器= annotator.selector;
this.annotator_element.mousedown(function(e){
if(!annotator.hit_menuitem){
switch(status){
case'free':
case '输入':
if(状态==='输入'){
selector.get_input_element()。blur();
}
if(e.which === 1){//左键
selector.start(e.pageX,e.pageY);
status ='hold';
}
}
}
annotator.hit_menuitem = false;
返回true;
});
$ {window).mousemove(function(e){
var offset;
switch(status){
case'hold':
selector.update_rectangle(e .pageX,e.pageY);
}
if(annotator.guide_h){
offset = annotator.image_frame.offset();
annotator.guide_h.css('top ',Math.floor(e.pageY-offset.top)+'px');
annotator.guide_v.css('left',Math.floor(e.pageX-offset.left)+'px' );
}
返回true;
});
$ {window).mouseup(function(e){
switch(status){
case'hold':
selector.update_rectangle(e.pageX,e.pageY) ;
selector.input_label(options);
status ='input';
if(options.input_method ==='fixed'){
selector.get_input_element()。blur ();
}
}
返回true;
});
selector.get_input_element()。blur(function(e){
var data;
switch(status){
case'input':
data =选择器。 finish(options);
if(data.label){
//使用条目
存储颜色// ...因此我们可以在更改标签类别
时重绘矩形data.color = annotator.color_list [annotator.label_list.indexOf(data.label)];
annotator.add_entry(data);
if(annotator.onchange){
annotator.onchange( annotator.entries);
}
}
status ='free';
}
返回true;
});
selector.get_input_element()。keypress(function(e){
switch(status){
case'input':
if(e.which === 13){
selector.get_input_element()。blur();
}
}
return e.which!== 13;
});
selector.get_input_element()。mousedown(function(e){
return annotator.hit_menuitem = true;
});
selector.get_input_element()。mousemove(function(e){
return annotator.hit_menuitem = true;
});
selector.get_input_element()。mouseup(function(e){
return annotator.hit_menuitem = true;
});
return selector.get_input_element()。parent()。mousedown(function(e){
return annotator.hit_menuitem = true;
});
}
//添加新条目。
add_entry(entry){
var注释器,box_element,close_button,text_box;
if(!this.multiple){
this.annotator_element.find(。annotated_bounding_box)。detach();
this.entries.splice(0);
}
this.entries.push(entry);
box_element = $(’< div class = annotated_bounding_box>< / div>’));
box_element.appendTo(this.image_frame).css({
//矩形颜色-停止拖动
border时:this.border_width + px solid + entry.color,
位置:绝对,
顶部:(entry.top-this.border_width)+ px,
左:(entry.left-this.border_width )+ px,
width:entry.width + px,
height:entry.height + px,
//停止拖动$时的文本颜色b $ b color:entry.color,
font-family: monospace,
font-size: small
});
close_button = $('< div>< / div>')。appendTo(box_element).css({
position: absolute,
top: -8px,
right: -8px,
width: 16px,
height: 0,
padding: 16px 0 0 0,
溢出:隐藏,
颜色: #fff,
背景色:#030,
border: 2px实心#fff,
-moz-border-radius: 18px,
-webkit-border-radius: 18px,
border-radius: 18px,
cursor:指针,
-moz-user-select:无,
-webkit-user-select : none,
user-select: none,
text-align: center
});
$(< div>< / div>)。appendTo(close_button).html('&#215;')。css({
display: block,
text-align:居中,
width: 16px,
position:绝对,
top: -2px ,
left: 0,
font-size: 16px,
line-height: 16px,
font-family : Helvetica Neue,Consolas,Verdana,Tahoma,Calibri, + Helvetica,Menlo, Droid Sans,sans-serif
});
text_box = $(’< div>< / div>’)。appendTo(box_element).css({
overflow: hidden
});
if(this.show_label){
text_box.text(entry.label);
}
注释者= this;
box_element.hover((function(e){
return close_button.show();
}),(function(e){
return close_button.hide();
}));
close_button.mousedown(function(e){
return annotator.hit_menuitem = true;
});
close_button.click(function(e){
var clicked_box,index;
clicked_box = close_button.parent(。annotated_bounding_box);
index = clicked_box.prevAll(。 annotated_bounding_box)。length;
clicked_box.detach();
annotator.entries.splice(index,1);
return annotator.onchange(annotator.entries);
} );
return close_button.hide();
}
//清除所有条目。
clear_all(e){
this.annotator_element.find(。annotated_bounding_box)。detach();
this.entries.splice(0);
返回this.onchange(this.entries);
}
//添加十字准线。
initialize_guide(options){
this.guide_h = $('< div class = guide_h>< / div>')。appendTo(this.image_frame).css({
border:点缀为1px +((options.color ||'#000'),
height: 0,
width: 100%,
position: absolute,
top: 0,
left: 0
});
返回this.guide_v = $('< div class = guide_v>< / div>')。appendTo(this.image_frame).css({
border: 1px点分 +(options.color ||'#000'),
height: 100%,
width: 0,
position:绝对,
top: 0,
left: 0
});
}
};
})。call(this);
I'd like to elaborate on the accepted answer to this question.
I'm looking at improving the minimal shiny app below (extracted from the accepted answer) with the following features:
- 1) draw the rectangle + a text label. The label comes from R (
input$foo
), e.g., from a dropdown. To avoid the edge cases where the labels fall outside the images, labels should be placed inside their rectangles. - 2) use a different color for the rectangles and their labels depending on the label
- 3) ability for the user to delete a rectangle by double-clicking inside it. In the case of multiple matches (overlap, nested), the rectangle with the smallest area should be deleted.
Brownie points for 1): the dropdown could appear next to the cursor like is done here (code here). If possible, the dropdown list should be passed from server.R and not be fixed/hardcoded. The reason is that depending on some user input, a different dropdown could be shown. E.g., we might have one dropdown for fruits c('banana','pineapple','grapefruit')
, one dropdown for animals c('raccoon','dog','cat')
, etc.
# JS and CSS modified from: https://stackoverflow.com/a/17409472/8099834
css <- "
#canvas {
width:2000px;
height:2000px;
border: 10px solid transparent;
}
.rectangle {
border: 5px solid #FFFF00;
position: absolute;
}
"
js <-
"function initDraw(canvas) {
var mouse = {
x: 0,
y: 0,
startX: 0,
startY: 0
};
function setMousePosition(e) {
var ev = e || window.event; //Moz || IE
if (ev.pageX) { //Moz
mouse.x = ev.pageX + window.pageXOffset;
mouse.y = ev.pageY + window.pageYOffset;
} else if (ev.clientX) { //IE
mouse.x = ev.clientX + document.body.scrollLeft;
mouse.y = ev.clientY + document.body.scrollTop;
}
};
var element = null;
canvas.onmousemove = function (e) {
setMousePosition(e);
if (element !== null) {
element.style.width = Math.abs(mouse.x - mouse.startX) + 'px';
element.style.height = Math.abs(mouse.y - mouse.startY) + 'px';
element.style.left = (mouse.x - mouse.startX < 0) ? mouse.x + 'px' : mouse.startX + 'px';
element.style.top = (mouse.y - mouse.startY < 0) ? mouse.y + 'px' : mouse.startY + 'px';
}
}
canvas.onclick = function (e) {
if (element !== null) {
var coord = {
left: element.style.left,
top: element.style.top,
width: element.style.width,
height: element.style.height
};
Shiny.onInputChange('rectCoord', coord);
element = null;
canvas.style.cursor = \"default\";
} else {
mouse.startX = mouse.x;
mouse.startY = mouse.y;
element = document.createElement('div');
element.className = 'rectangle'
element.style.left = mouse.x + 'px';
element.style.top = mouse.y + 'px';
canvas.appendChild(element);
canvas.style.cursor = \"crosshair\";
}
}
};
$(document).on('shiny:sessioninitialized', function(event) {
initDraw(document.getElementById('canvas'));
});
"
library(shiny)
ui <- fluidPage(
tags$head(
tags$style(css),
tags$script(HTML(js))
),
fluidRow(
column(width = 6,
# inline is necessary
# ...otherwise we can draw rectangles over entire fluidRow
uiOutput("canvas", inline = TRUE)),
column(
width = 6,
verbatimTextOutput("rectCoordOutput")
)
)
)
server <- function(input, output, session) {
output$canvas <- renderUI({
tags$img(src = "https://www.r-project.org/logo/Rlogo.png")
})
output$rectCoordOutput <- renderPrint({
input$rectCoord
})
}
shinyApp(ui, server)
This solution uses kyamagu's bbox_annotator and is based on demo.html. I'm not familiar with JS, so it's not the prettiest. Limitations are:
- Choosing a different image url will remove previous rectangles
- I edited the JS a bit to change the rectangle/text color, so you won't be able to pull directly from the original repo
- My changes probably broke input_method = "fixed" and "text", I only tested input_method = "select"
ui.R
# Adapted from https://github.com/kyamagu/bbox-annotator/
# Edited original JS to add color_list as an option
# ...should be the same length as labels
# ...and controls the color of the rectangle
# ...will probably be broken for input_method = "fixed" or "text"
# Also added color as a value in each rectangle entry
js <- '
$(document).ready(function() {
// define options to pass to bounding box constructor
var options = {
url: "https://www.r-project.org/logo/Rlogo.svg",
input_method: "select",
labels: [""],
color_list: [""],
onchange: function(entries) {
Shiny.onInputChange("rectCoord", JSON.stringify(entries, null, " "));
}
};
// Initialize the bounding-box annotator.
var annotator = new BBoxAnnotator(options);
// Initialize the reset button.
$("#reset_button").click(function(e) {
annotator.clear_all();
})
// define function to reset the bbox
// ...upon choosing new label category or new url
function reset_bbox(options) {
document.getElementById("bbox_annotator").setAttribute("style", "display:inline-block");
$(".image_frame").remove();
annotator = new BBoxAnnotator(options);
}
// update image url from shiny
Shiny.addCustomMessageHandler("change-img-url", function(url) {
options.url = url;
options.width = null;
options.height = null;
reset_bbox(options);
});
// update colors and categories from shiny
Shiny.addCustomMessageHandler("update-category-list", function(vals) {
options.labels = Object.values(vals);
options.color_list = Object.keys(vals);
reset_bbox(options);
});
// redraw rectangles based on list of entries
Shiny.addCustomMessageHandler("redraw-rects", function(vals) {
var arr = JSON.parse(vals);
arr.forEach(function(rect){
annotator.add_entry(rect);
});
if (annotator.onchange) {
annotator.onchange(annotator.entries);
}
});
});
'
ui <- fluidPage(
tags$head(tags$script(HTML(js)),
tags$head(
tags$script(src = "bbox_annotation.js")
)),
titlePanel("Bounding box annotator demo"),
sidebarLayout(
sidebarPanel(
selectInput(
"img_url",
"URLs",
c(
"https://www.r-project.org/logo/Rlogo.svg",
"https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
)
),
selectInput("category_type", "Label Category", c("animals", "fruits")),
div(HTML(
'<input id="reset_button" type="reset" />'
)),
HTML(
'<input id="annotation_data" name="annotation_data" type="hidden" />'
),
hr(),
h4("Entries"),
verbatimTextOutput("rectCoordOutput")
),
mainPanel(div(id = "bbox_annotator", style = "display:inline-block"))
)
)
server.R
server <- function(input, output, session) {
# user choices
output$rectCoordOutput <- renderPrint({
if(!is.null(input$rectCoord)) {
as.data.frame(jsonlite::fromJSON(input$rectCoord))
}
})
# send chosen URL from shiny to JS
observeEvent(input$img_url, {
session$sendCustomMessage("change-img-url", input$img_url)
})
# send chosen category list from shiny to JS
observeEvent(input$category_type, {
vals <- switch(input$category_type,
fruits = list("yellow" = "banana",
"orange" = "pineapple",
"pink" = "grapefruit"),
animals = list("grey" = "raccoon",
"brown" = "dog",
"tan" = "cat")
)
# update category list
session$sendCustomMessage("update-category-list", vals)
# redraw rectangles
session$sendCustomMessage("redraw-rects", input$rectCoord)
})
}
www/bbox_annotation.js
// Generated by CoffeeScript 2.5.0
(function() {
// https://github.com/kyamagu/bbox-annotator/blob/master/bbox_annotator.coffee
// Use coffee-script compiler to obtain a javascript file.
// coffee -c bbox_annotator.coffee
// See http://coffeescript.org/
// BBox selection window.
var BBoxSelector;
BBoxSelector = class BBoxSelector {
// Initializes selector in the image frame.
constructor(image_frame, options) {
if (options == null) {
options = {};
}
options.input_method || (options.input_method = "text");
this.image_frame = image_frame;
this.border_width = options.border_width || 2;
this.selector = $('<div class="bbox_selector"></div>');
this.selector.css({
// rectangle color when dragging
"border": this.border_width + "px dotted rgb(127,255,127)",
"position": "absolute"
});
this.image_frame.append(this.selector);
this.selector.css({
"border-width": this.border_width
});
this.selector.hide();
this.create_label_box(options);
}
// Initializes a label input box.
create_label_box(options) {
var i, label, len, ref;
options.labels || (options.labels = ["object"]);
this.label_box = $('<div class="label_box" style="z-index: 1000"></div>');
this.label_box.css({
"position": "absolute"
});
this.image_frame.append(this.label_box);
switch (options.input_method) {
case 'select':
if (typeof options.labels === "string") {
options.labels = [options.labels];
}
this.label_input = $('<select class="label_input" name="label"></select>');
this.label_box.append(this.label_input);
this.label_input.append($('<option value>choose an item</option>'));
ref = options.labels;
for (i = 0, len = ref.length; i < len; i++) {
label = ref[i];
this.label_input.append('<option value="' + label + '">' + label + '</option>');
}
this.label_input.change(function(e) {
return this.blur();
});
break;
case 'text':
if (typeof options.labels === "string") {
options.labels = [options.labels];
}
this.label_input = $('<input class="label_input" name="label" ' + 'type="text" value>');
this.label_box.append(this.label_input);
this.label_input.autocomplete({
source: options.labels || [''],
autoFocus: true
});
break;
case 'fixed':
if ($.isArray(options.labels)) {
options.labels = options.labels[0];
}
this.label_input = $('<input class="label_input" name="label" type="text">');
this.label_box.append(this.label_input);
this.label_input.val(options.labels);
break;
default:
throw 'Invalid label_input parameter: ' + options.input_method;
}
return this.label_box.hide();
}
// Crop x and y to the image size.
crop(pageX, pageY) {
var point;
return point = {
x: Math.min(Math.max(Math.round(pageX - this.image_frame.offset().left), 0), Math.round(this.image_frame.width() - 1)),
y: Math.min(Math.max(Math.round(pageY - this.image_frame.offset().top), 0), Math.round(this.image_frame.height() - 1))
};
}
// When a new selection is made.
start(pageX, pageY) {
this.pointer = this.crop(pageX, pageY);
this.offset = this.pointer;
this.refresh();
this.selector.show();
$('body').css('cursor', 'crosshair');
return document.onselectstart = function() {
return false;
};
}
// When a selection updates.
update_rectangle(pageX, pageY) {
this.pointer = this.crop(pageX, pageY);
return this.refresh();
}
// When starting to input label.
input_label(options) {
$('body').css('cursor', 'default');
document.onselectstart = function() {
return true;
};
this.label_box.show();
return this.label_input.focus();
}
// Finish and return the annotation.
finish(options) {
var data;
this.label_box.hide();
this.selector.hide();
data = this.rectangle();
data.label = $.trim(this.label_input.val().toLowerCase());
if (options.input_method !== 'fixed') {
this.label_input.val('');
}
return data;
}
// Get a rectangle.
rectangle() {
var rect, x1, x2, y1, y2;
x1 = Math.min(this.offset.x, this.pointer.x);
y1 = Math.min(this.offset.y, this.pointer.y);
x2 = Math.max(this.offset.x, this.pointer.x);
y2 = Math.max(this.offset.y, this.pointer.y);
return rect = {
left: x1,
top: y1,
width: x2 - x1 + 1,
height: y2 - y1 + 1
};
}
// Update css of the box.
refresh() {
var rect;
rect = this.rectangle();
this.selector.css({
left: (rect.left - this.border_width) + 'px',
top: (rect.top - this.border_width) + 'px',
width: rect.width + 'px',
height: rect.height + 'px'
});
return this.label_box.css({
left: (rect.left - this.border_width) + 'px',
top: (rect.top + rect.height + this.border_width) + 'px'
});
}
// Return input element.
get_input_element() {
return this.label_input;
}
};
// Annotator object definition.
this.BBoxAnnotator = class BBoxAnnotator {
// Initialize the annotator layout and events.
constructor(options) {
var annotator, image_element;
annotator = this;
this.annotator_element = $(options.id || "#bbox_annotator");
// allow us to access colors and labels in future steps
this.color_list = options.color_list;
this.label_list = options.labels;
this.border_width = options.border_width || 2;
this.show_label = options.show_label || (options.input_method !== "fixed");
if (options.multiple != null) {
this.multiple = options.multiple;
} else {
this.multiple = true;
}
this.image_frame = $('<div class="image_frame"></div>');
this.annotator_element.append(this.image_frame);
if (options.guide) {
annotator.initialize_guide(options.guide);
}
image_element = new Image();
image_element.src = options.url;
image_element.onload = function() {
options.width || (options.width = image_element.width);
options.height || (options.height = image_element.height);
annotator.annotator_element.css({
"width": (options.width + annotator.border_width) + 'px',
"height": (options.height + annotator.border_width) + 'px',
"padding-left": (annotator.border_width / 2) + 'px',
"padding-top": (annotator.border_width / 2) + 'px',
"cursor": "crosshair",
"overflow": "hidden"
});
annotator.image_frame.css({
"background-image": "url('" + image_element.src + "')",
"width": options.width + "px",
"height": options.height + "px",
"position": "relative"
});
annotator.selector = new BBoxSelector(annotator.image_frame, options);
return annotator.initialize_events(options);
};
image_element.onerror = function() {
return annotator.annotator_element.text("Invalid image URL: " + options.url);
};
this.entries = [];
this.onchange = options.onchange;
}
// Initialize events.
initialize_events(options) {
var annotator, selector, status;
status = 'free';
this.hit_menuitem = false;
annotator = this;
selector = annotator.selector;
this.annotator_element.mousedown(function(e) {
if (!annotator.hit_menuitem) {
switch (status) {
case 'free':
case 'input':
if (status === 'input') {
selector.get_input_element().blur();
}
if (e.which === 1) { // left button
selector.start(e.pageX, e.pageY);
status = 'hold';
}
}
}
annotator.hit_menuitem = false;
return true;
});
$(window).mousemove(function(e) {
var offset;
switch (status) {
case 'hold':
selector.update_rectangle(e.pageX, e.pageY);
}
if (annotator.guide_h) {
offset = annotator.image_frame.offset();
annotator.guide_h.css('top', Math.floor(e.pageY - offset.top) + 'px');
annotator.guide_v.css('left', Math.floor(e.pageX - offset.left) + 'px');
}
return true;
});
$(window).mouseup(function(e) {
switch (status) {
case 'hold':
selector.update_rectangle(e.pageX, e.pageY);
selector.input_label(options);
status = 'input';
if (options.input_method === 'fixed') {
selector.get_input_element().blur();
}
}
return true;
});
selector.get_input_element().blur(function(e) {
var data;
switch (status) {
case 'input':
data = selector.finish(options);
if (data.label) {
// store color with the entry
// ...so we can redraw the rectangle upon changing label category
data.color = annotator.color_list[annotator.label_list.indexOf(data.label)];
annotator.add_entry(data);
if (annotator.onchange) {
annotator.onchange(annotator.entries);
}
}
status = 'free';
}
return true;
});
selector.get_input_element().keypress(function(e) {
switch (status) {
case 'input':
if (e.which === 13) {
selector.get_input_element().blur();
}
}
return e.which !== 13;
});
selector.get_input_element().mousedown(function(e) {
return annotator.hit_menuitem = true;
});
selector.get_input_element().mousemove(function(e) {
return annotator.hit_menuitem = true;
});
selector.get_input_element().mouseup(function(e) {
return annotator.hit_menuitem = true;
});
return selector.get_input_element().parent().mousedown(function(e) {
return annotator.hit_menuitem = true;
});
}
// Add a new entry.
add_entry(entry) {
var annotator, box_element, close_button, text_box;
if (!this.multiple) {
this.annotator_element.find(".annotated_bounding_box").detach();
this.entries.splice(0);
}
this.entries.push(entry);
box_element = $('<div class="annotated_bounding_box"></div>');
box_element.appendTo(this.image_frame).css({
// rectangle color -- when stopped dragging
"border": this.border_width + "px solid " + entry.color,
"position": "absolute",
"top": (entry.top - this.border_width) + "px",
"left": (entry.left - this.border_width) + "px",
"width": entry.width + "px",
"height": entry.height + "px",
// text color when stopped dragging
"color": entry.color,
"font-family": "monospace",
"font-size": "small"
});
close_button = $('<div></div>').appendTo(box_element).css({
"position": "absolute",
"top": "-8px",
"right": "-8px",
"width": "16px",
"height": "0",
"padding": "16px 0 0 0",
"overflow": "hidden",
"color": "#fff",
"background-color": "#030",
"border": "2px solid #fff",
"-moz-border-radius": "18px",
"-webkit-border-radius": "18px",
"border-radius": "18px",
"cursor": "pointer",
"-moz-user-select": "none",
"-webkit-user-select": "none",
"user-select": "none",
"text-align": "center"
});
$("<div></div>").appendTo(close_button).html('×').css({
"display": "block",
"text-align": "center",
"width": "16px",
"position": "absolute",
"top": "-2px",
"left": "0",
"font-size": "16px",
"line-height": "16px",
"font-family": '"Helvetica Neue", Consolas, Verdana, Tahoma, Calibri, ' + 'Helvetica, Menlo, "Droid Sans", sans-serif'
});
text_box = $('<div></div>').appendTo(box_element).css({
"overflow": "hidden"
});
if (this.show_label) {
text_box.text(entry.label);
}
annotator = this;
box_element.hover((function(e) {
return close_button.show();
}), (function(e) {
return close_button.hide();
}));
close_button.mousedown(function(e) {
return annotator.hit_menuitem = true;
});
close_button.click(function(e) {
var clicked_box, index;
clicked_box = close_button.parent(".annotated_bounding_box");
index = clicked_box.prevAll(".annotated_bounding_box").length;
clicked_box.detach();
annotator.entries.splice(index, 1);
return annotator.onchange(annotator.entries);
});
return close_button.hide();
}
// Clear all entries.
clear_all(e) {
this.annotator_element.find(".annotated_bounding_box").detach();
this.entries.splice(0);
return this.onchange(this.entries);
}
// Add crosshair guide.
initialize_guide(options) {
this.guide_h = $('<div class="guide_h"></div>').appendTo(this.image_frame).css({
"border": "1px dotted " + (options.color || '#000'),
"height": "0",
"width": "100%",
"position": "absolute",
"top": "0",
"left": "0"
});
return this.guide_v = $('<div class="guide_v"></div>').appendTo(this.image_frame).css({
"border": "1px dotted " + (options.color || '#000'),
"height": "100%",
"width": "0",
"position": "absolute",
"top": "0",
"left": "0"
});
}
};
}).call(this);
这篇关于在图像R上方绘制矩形的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!