在图像R上方绘制矩形 [英] Drawing rectangles on top of image R shiny

查看:101
本文介绍了在图像R上方绘制矩形的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想详细说明对此



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:

  1. Choosing a different image url will remove previous rectangles
  2. 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
  3. 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('&#215;').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上方绘制矩形的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆