////////////////////////// // use texture.js without d3 to create discerning patterns on the email blocks ///////////////////////////////////// // some faked dom, see https://github.com/riccardoscalco/textures/issues/17 function dom(name) { this.name = name; this.els = []; this.attrs = {}; } dom.prototype.append = function(name) { if (name === "defs") return this; if (this.name === undefined) { this.name = name; return this; } var el = new dom(name); this.els.push(el); return el; } dom.prototype.attr = function(key, value) { this.attrs[key] = value; return this; } dom.prototype.toString = function() { var attrs = []; var k; for (k in this.attrs) { attrs += " " + k + "='" + this.attrs[k] + "'"; } if (this.els.length) { return "<"+this.name+attrs+">"+this.els.map(function(el) { return el.toString()}).join('\n')+""; } else { return "<"+this.name+attrs+"/>" } } //////////// end of faked dom /////////////////////////////// let categoryMap = {}; let textureMap = {}; function getColoredTexture(i, color) { let j = i % 11; // update when adding items to switch: switch(j) { case 0: return textures.lines().size(4).strokeWidth(1).stroke(color); case 1: return textures.circles().radius(2).size(5).fill('none').strokeWidth(1).stroke(color).complement(); case 2: return textures.lines().size(10).orientation("3/8").stroke(color); case 3: return textures.lines().heavier(4).thinner(.8).stroke(color); case 4: return textures.paths().d("hexagons").size(3).strokeWidth(1).stroke(color); case 5: return textures.lines().orientation("vertical", "horizontal").size(4).strokeWidth(1).shapeRendering("crispEdges").stroke(color); case 6: return textures.paths().d("hexagons").size(5).strokeWidth(2).stroke("rgba(0,0,0,0)").fill(color); case 7: return textures.circles().size(4).fill(color); case 8: return textures.circles().thicker().complement().fill(color); case 9: return textures.paths().d("caps").lighter().thicker().size(5).stroke(color); case 10: return textures.paths().d("hexagons").size(4).strokeWidth(2).stroke(color); // textures.lines().size(4).strokeWidth(1).orientation("-3/8"), } }; const categoryColors = { "person": "#FA0800", "vehicle": "#80FF00", "outdoor": "#00FFFF", "animal": "#FF00FF", "accessory": "#FFFF00", "food": "#00FA85", "furniture": "#BB00FA", "indoor": "#8500FA", "electronic": "#FAD900", "kitchen": "#FA8500", "sports": "#0080FF", "appliance": "#FF0080", } function getColorForSuperCategory(name) { return categoryColors[name]; } function getTextureForCategory(id) { let hash = id; if(!textureMap.hasOwnProperty(hash)) { let color = categoryColors[categoryMap[id]['supercategory']]; textureMap[hash] = getColoredTexture(id, color); } return textureMap[hash]; } class CocoCanvas { start(){ this.catNavEl = document.getElementById('catNav'); this.catNav2El = document.getElementById('catNav2'); this.canvas = document.getElementById('svgCanvas'); this.loadImagesBtnEl = document.getElementById("loadImages"); this.loadLabelsBtnEl = document.getElementById("loadLabels"); this.sceneSelectEl = document.getElementById('scene'); this.savedBtnEl = document.getElementById('save'); this.loadNav() this.annotations = []; this.loadImagesBtnEl.addEventListener('change', (e) => this.toggleImages(e)); this.loadLabelsBtnEl.addEventListener('change', (e) => this.toggleLabels(e)); this.savedBtnEl.addEventListener('click', function(e){ if(this.savedBtnEl.disabled || this.annotations.length < 1) return; this.save(); }.bind(this)); } loadNav() { let r = new Request('/categories.json'); fetch(r) .then(response => response.json()) .then(categories => { for(let cat of categories) { categoryMap[cat['id']] = cat; } this.buildNav(categories); }).catch(function(e){ console.error(e); }); } buildNav(categories) { let lastSuperCat = null; let supercategories = [] let supercategoriesEls = {} // create menu's per supercategory, divide these over left & right for(let cat of categories) { if(supercategories.indexOf(cat['supercategory']) < 0) { supercategories.push(cat['supercategory']); } } for(let supercat of supercategories) { let ulEl = crel('ul', {'data-name': supercat, 'class':'supercategory-'+supercat}); supercategoriesEls[supercat] = ulEl; // first half left, other half right side of the page if(supercategories.indexOf(supercat)/supercategories.length < .5) { this.catNavEl.appendChild(ulEl); } else { this.catNav2El.appendChild(ulEl); } } // entries for the menus for(let cat of categories) { // let firstOfType = lastSuperCat != cat['supercategory'] ? ' first-of-super' : ''; // lastSuperCat = cat['supercategory']; supercategoriesEls[cat['supercategory']].appendChild(crel('li', { 'id': 'category-' + cat['id'], // 'class': 'supercategory-' + cat['supercategory'], 'on': { 'click': (e) => { this.requestAnnotation(cat['id']); } } }, cat['name'])); } this.defsEl = document.createElementNS("http://www.w3.org/2000/svg", 'defs'); for(let cat of categories) { let texture = getTextureForCategory(cat['id']); let sel = new dom(); texture(sel); this.defsEl.innerHTML += sel.toString(); } this.canvas.appendChild(this.defsEl); } requestAnnotation(category_id) { let r = new Request(`/annotation.json?category=${category_id}&normalise=100`); return fetch(r) .then(response => response.json()) .then(annotation => { this.addAnnotationAsShape(annotation); }).catch(function(e){ console.error(e); }); } pointsToD(points) { let start = points.shift() let d = `M${start[0].toPrecision(4)} ${start[1].toPrecision(4)} L `; points = points.map((p) => `${p[0].toPrecision(4)} ${p[1].toPrecision(4)}`); d += points.join(' '); return d; } getMousePosition(evt) { // from http://www.petercollingridge.co.uk/tutorials/svg/interactive/dragging/ let CTM = this.canvas.getScreenCTM(); return { x: (evt.clientX - CTM.e) / CTM.a, y: (evt.clientY - CTM.f) / CTM.d }; } addAnnotationAsShape(annotation) { console.log('Add annotation', annotation); let category = categoryMap[annotation['category_id']] let texture = getTextureForCategory(category['id']); let x = 500 - annotation['bbox'][2]/2; let y = 500 - annotation['bbox'][3]/2; let annEl = crel(document.createElementNS("http://www.w3.org/2000/svg", 'g'), { 'data-id': annotation['id'], 'transform': `translate(${x}, ${y})`, 'on': { 'mousedown': function(downE) { // after clicking the element should be the top element // and the last svg element is drawn on top. annEl.parentNode.appendChild(annEl); // console.log(downE); // console.log(this) let offset = this.getMousePosition(downE); // Get initial translation amount // console.log(annEl.transform.baseVal); let transform = annEl.transform.baseVal.getItem(0); offset.x -= transform.matrix.e; offset.y -= transform.matrix.f; let moveEvent = (moveE) => { let coord = this.getMousePosition(moveE); transform.matrix.e = coord.x - offset.x; transform.matrix.f = coord.y - offset.y; // annEl.setAttributeNS(null, "x", coord.x - offset.x); // annEl.setAttributeNS(null, "y", coord.y - offset.y); }; document.addEventListener('mousemove', moveEvent); document.addEventListener('mouseup', (upE) => { document.removeEventListener('mousemove', moveEvent); }); }.bind(this) } }); for(let segment of annotation['segments']) { let pathEl = crel(document.createElementNS("http://www.w3.org/2000/svg", 'path'), { 'fill': texture.url(), 'd': this.pointsToD(segment) }); annEl.appendChild(pathEl); } this.canvas.appendChild(annEl); // keep info on annotation elements in CocoCanvas object annotation['element'] = annEl; this.annotations.push(annotation); this.savedBtnEl.disabled = false; // based on status of checkboxes, add label or image (bit of a later hack) this.toggleImages(); this.toggleLabels(); } // convert annotation shapes on canvas to images, which are masked by the shape convertToImages() { console.log('convert'); for(let annotation of this.annotations) { console.log(annotation); if(annotation['element'].getElementsByTagName('image').length) { //already done continue; } let scale = annotation['scale']; let bbox = annotation['is_normalised'] ? annotation['bbox_original'] : annotation['bbox']; let imgEl = crel(document.createElementNS("http://www.w3.org/2000/svg", 'image'), { 'href': annotation['image']['coco_url'], 'width': annotation['image']['width']* scale, 'height': annotation['image']['height']* scale, 'x': bbox[0] * -1 * scale, 'y': bbox[1] * -1 * scale, }); annotation['element'].prepend(imgEl ); } } addLabels() { console.log('labels'); for(let annotation of this.annotations) { console.log(annotation); if(annotation['element'].getElementsByTagName('text').length) { //already done continue; } let textEl = crel(document.createElementNS("http://www.w3.org/2000/svg", 'text'), { 'x': annotation['bbox'][2] + 5, 'y': annotation['bbox'][3], }, categoryMap[annotation['category_id']]['name']); annotation['element'].append(textEl ); } } toggleImages(e) { if(this.loadImagesBtnEl.checked) { this.convertToImages(); document.body.classList.remove('hideImages'); } else { document.body.classList.add('hideImages'); } } toggleLabels(e) { if(this.loadLabelsBtnEl.checked) { this.addLabels(); document.body.classList.remove('hideLabels'); } else { document.body.classList.add('hideLabels'); } } save() { let scene = this.sceneSelectEl.value; let annotations = []; for (let ann of this.annotations) { annotations.push({ 'id': ann['id'], 'x': ann.element.transform.baseVal.getItem(0).matrix.e, 'y': ann.element.transform.baseVal.getItem(0).matrix.f }); } let data = JSON.stringify({ 'scene': this.sceneSelectEl.value, 'annotations': annotations }); let r = new Request('/save', {'method': 'POST', 'body': data}); fetch(r) .then(response => response.json()) .then(submission => { // alert("Something went wrong when saving the file"); // todo redirect to submission console.log('saved', submission); window.location = window.location + 'saved'; }).catch(function(e){ alert("Something went wrong when saving the file"); console.error(e); }); } } let cc = new CocoCanvas(); cc.start(); //for testing only: //cc.requestAnnotation(1).then((e) => cc.convertToImages());