First version
This commit is contained in:
commit
1702cce9d1
12 changed files with 825 additions and 0 deletions
22
README.md
Normal file
22
README.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
Build array of images sorted by size:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python zoom_animation.py --annotations ../../datasets/COCO/annotations/instances_train2017.json --output zoom --category_id 18
|
||||||
|
```
|
||||||
|
|
||||||
|
Turn into png
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd zoom/dog
|
||||||
|
for file in *.svg; do inkscape -z -f "${file}" -w 640 -e "../dog_png/${file}.png"; done
|
||||||
|
```
|
||||||
|
|
||||||
|
Turn png into mp4
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ../dog_png
|
||||||
|
#ffmpeg -r 1 -i %d_*.png -pix_fmt yuv420p bloch2.mp4
|
||||||
|
ffmpeg -f image2 -pattern_type glob -i '*.png' ../dog.mp4
|
||||||
|
|
||||||
|
```
|
44
coco.sql
Normal file
44
coco.sql
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
CREATE TABLE IF NOT EXISTS "segments" (
|
||||||
|
"id" INTEGER UNIQUE,
|
||||||
|
"annotation_id" INTEGER,
|
||||||
|
"points" TEXT,
|
||||||
|
PRIMARY KEY("id")
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
CREATE TABLE IF NOT EXISTS "categories" (
|
||||||
|
"id" INTEGER UNIQUE,
|
||||||
|
"supercategory" TEXT,
|
||||||
|
"name" TEXT UNIQUE,
|
||||||
|
PRIMARY KEY("id")
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
CREATE TABLE IF NOT EXISTS "images" (
|
||||||
|
"id" INTEGER UNIQUE,
|
||||||
|
"flickr_url" TEXT,
|
||||||
|
"coco_url" TEXT,
|
||||||
|
"width" FLOAT,
|
||||||
|
"height" FLOAT,
|
||||||
|
"date_captured" DATETIME,
|
||||||
|
PRIMARY KEY("id","id")
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
CREATE TABLE IF NOT EXISTS "annotations" (
|
||||||
|
"id" INTEGER UNIQUE,
|
||||||
|
"image_id" INTEGER,
|
||||||
|
"category_id" INTEGER,
|
||||||
|
"iscrowd" BOOL,
|
||||||
|
"area" FLOAT,
|
||||||
|
"bbox_top" FLOAT,
|
||||||
|
"bbox_left" FLOAT,
|
||||||
|
"bbox_width" FLOAT,
|
||||||
|
"bbox_height" FLOAT,
|
||||||
|
PRIMARY KEY("id")
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
CREATE INDEX IF NOT EXISTS "segments_annotation" ON "segments" (
|
||||||
|
"annotation_id"
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS "annotations_image" ON "annotations" (
|
||||||
|
"image_id"
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS "annotations_category" ON "annotations" (
|
||||||
|
"category_id"
|
||||||
|
);
|
||||||
|
COMMIT;
|
36
generate_lonely_segments.py
Normal file
36
generate_lonely_segments.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import pycocotools.coco
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import tqdm
|
||||||
|
import urllib.request
|
||||||
|
import os
|
||||||
|
|
||||||
|
logger = logging.getLogger("coco")
|
||||||
|
|
||||||
|
argParser = argparse.ArgumentParser(description='Find all images with single segments and generate svg')
|
||||||
|
argParser.add_argument(
|
||||||
|
'--annotations',
|
||||||
|
type=str,
|
||||||
|
default='../../datasets/COCO/annotations/instances_train2017.json'
|
||||||
|
)
|
||||||
|
argParser.add_argument(
|
||||||
|
'--output',
|
||||||
|
type=str,
|
||||||
|
help='Output directory'
|
||||||
|
)
|
||||||
|
args = argParser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
logger.info(f"Load {args.annotations}")
|
||||||
|
coco = pycocotools.coco.COCO(args.annotations)
|
||||||
|
|
||||||
|
|
||||||
|
for img_id, annotations in tqdm.tqdm(coco.imgToAnns.items()):
|
||||||
|
if len(annotations) != 1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
annotation = annotations[0] # we have only one
|
||||||
|
category = coco.cats[annotation['category_id']]['name']
|
||||||
|
fn = os.path.join(args.output, f"{category}_{img_id}.jpg")
|
||||||
|
if not os.path.exists(fn):
|
||||||
|
urllib.request.urlretrieve(coco.imgs[img_id]['coco_url'], fn)
|
125
server.py
Normal file
125
server.py
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
import tornado.ioloop
|
||||||
|
import tornado.web
|
||||||
|
import tornado.websocket
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import coloredlogs
|
||||||
|
from coco.storage import COCOStorage
|
||||||
|
import json
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
logger = logging.getLogger('coco.server')
|
||||||
|
|
||||||
|
class JsonEncoder(json.JSONEncoder):
|
||||||
|
def default(self, obj):
|
||||||
|
method = getattr(obj, "forJson", None)
|
||||||
|
if callable(method ):
|
||||||
|
return obj.forJson()
|
||||||
|
# Let the base class default method raise the TypeError
|
||||||
|
return json.JSONEncoder.default(self, obj)
|
||||||
|
|
||||||
|
class RestHandler(tornado.web.RequestHandler):
|
||||||
|
def initialize(self, storage: COCOStorage):
|
||||||
|
self.storage = storage
|
||||||
|
self.set_header("Content-Type", "application/json")
|
||||||
|
|
||||||
|
def get(self, *params):
|
||||||
|
self.write(json.dumps(self.getData(*params), cls=JsonEncoder))
|
||||||
|
|
||||||
|
class CategoryHandler(RestHandler):
|
||||||
|
def getData(self):
|
||||||
|
return self.storage.getCategories()
|
||||||
|
|
||||||
|
class AnnotationHandler(RestHandler):
|
||||||
|
def getData(self):
|
||||||
|
# get specific annotation
|
||||||
|
annotation_id = self.get_argument('id', None)
|
||||||
|
annotation_id = None if not annotation_id else int(annotation_id)
|
||||||
|
|
||||||
|
# get by category id
|
||||||
|
category_id = self.get_argument('category', None)
|
||||||
|
category_id = None if not category_id else int(category_id)
|
||||||
|
|
||||||
|
normalise = self.get_argument('normalise', False)
|
||||||
|
normalise = int(normalise) if normalise is not False else False
|
||||||
|
|
||||||
|
# category_id = None if not category_id else int(category_id)
|
||||||
|
|
||||||
|
logger.debug(f'Get annotation id: {annotation_id}, category: {category_id}, normalised: {normalise}')
|
||||||
|
|
||||||
|
annotation = self.storage.getRandomAnnotation(annotation_id=annotation_id, category_id=category_id)
|
||||||
|
if normalise:
|
||||||
|
return annotation.getNormalised(normalise, normalise)
|
||||||
|
return annotation
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
||||||
|
CORS_ORIGINS = ['localhost', 'coco.local', 'r3.local']
|
||||||
|
|
||||||
|
def check_origin(self, origin):
|
||||||
|
parsed_origin = urlparse(origin)
|
||||||
|
# parsed_origin.netloc.lower() gives localhost:3333
|
||||||
|
valid = parsed_origin.hostname in self.CORS_ORIGINS
|
||||||
|
return valid
|
||||||
|
|
||||||
|
# the client connected
|
||||||
|
def open(self, p = None):
|
||||||
|
WebSocketHandler.connections.add(self)
|
||||||
|
logger.info("New client connected")
|
||||||
|
self.write_message("hello!")
|
||||||
|
|
||||||
|
# the client sent the message
|
||||||
|
def on_message(self, message):
|
||||||
|
logger.debug(f"recieve: {message}")
|
||||||
|
|
||||||
|
|
||||||
|
class StaticFileWithHeaderHandler(tornado.web.StaticFileHandler):
|
||||||
|
def set_extra_headers(self, path):
|
||||||
|
"""For subclass to add extra headers to the response"""
|
||||||
|
if path[-5:] == '.html':
|
||||||
|
self.set_header("Access-Control-Allow-Origin", "*")
|
||||||
|
|
||||||
|
def make_app(db_filename, debug):
|
||||||
|
storage = COCOStorage(db_filename)
|
||||||
|
|
||||||
|
return tornado.web.Application([
|
||||||
|
(r"/ws(.*)", WebSocketHandler),
|
||||||
|
(r"/categories.json", CategoryHandler, {'storage': storage}),
|
||||||
|
(r"/annotation.json", AnnotationHandler, {'storage': storage}),
|
||||||
|
(r"/(.*)", StaticFileWithHeaderHandler,
|
||||||
|
{"path": 'www', "default_filename": 'index.html'}),
|
||||||
|
], debug=debug)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
argParser = argparse.ArgumentParser(description='Server for COCO web interface')
|
||||||
|
argParser.add_argument(
|
||||||
|
'--port',
|
||||||
|
'-P',
|
||||||
|
type=int,
|
||||||
|
default=8888,
|
||||||
|
help='Port to listen on'
|
||||||
|
)
|
||||||
|
argParser.add_argument(
|
||||||
|
'--db',
|
||||||
|
type=str,
|
||||||
|
metavar='DATABASE',
|
||||||
|
required=True,
|
||||||
|
help='Database to serve from'
|
||||||
|
)
|
||||||
|
argParser.add_argument(
|
||||||
|
'--verbose',
|
||||||
|
'-v',
|
||||||
|
action='store_true',
|
||||||
|
help='Increase log level'
|
||||||
|
)
|
||||||
|
args = argParser.parse_args()
|
||||||
|
|
||||||
|
loglevel = logging.DEBUG if args.verbose else logging.INFO
|
||||||
|
coloredlogs.install(
|
||||||
|
level=loglevel,
|
||||||
|
fmt="%(asctime)s %(hostname)s %(name)s[%(process)d] %(levelname)s %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
|
app = make_app(args.db, debug=args.verbose )
|
||||||
|
app.listen(args.port)
|
||||||
|
tornado.ioloop.IOLoop.current().start()
|
106
tools.py
Normal file
106
tools.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import pycocotools.coco
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pprint
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger("coco")
|
||||||
|
|
||||||
|
argParser = argparse.ArgumentParser(description='Create shape SVG\'s')
|
||||||
|
argParser.add_argument(
|
||||||
|
'--annotations',
|
||||||
|
type=str,
|
||||||
|
default='../../datasets/COCO/annotations/instances_val2017.json'
|
||||||
|
)
|
||||||
|
argParser.add_argument(
|
||||||
|
'--categories',
|
||||||
|
action='store_true',
|
||||||
|
help='Show categories'
|
||||||
|
)
|
||||||
|
argParser.add_argument(
|
||||||
|
'--propagate',
|
||||||
|
type=str,
|
||||||
|
metavar='DATABASE',
|
||||||
|
help='Store data in sqlite db'
|
||||||
|
)
|
||||||
|
args = argParser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
logger.info(f"Load {args.annotations}")
|
||||||
|
coco = pycocotools.coco.COCO(args.annotations)
|
||||||
|
|
||||||
|
|
||||||
|
if args.categories:
|
||||||
|
cats = {}
|
||||||
|
for id, cat in coco.cats.items():
|
||||||
|
if cat['supercategory'] not in cats:
|
||||||
|
cats[cat['supercategory']] = []
|
||||||
|
cats[cat['supercategory']].append(cat)
|
||||||
|
# pp = pprint.PrettyPrinter(indent=4)
|
||||||
|
pprint.pprint(cats, sort_dicts=False)
|
||||||
|
|
||||||
|
if args.propagate:
|
||||||
|
if not os.path.exists(args.propagate):
|
||||||
|
con = sqlite3.connect(args.propagate)
|
||||||
|
cur = con.cursor()
|
||||||
|
with open('coco.sql', 'r') as fp:
|
||||||
|
cur.executescript(fp.read())
|
||||||
|
con.close()
|
||||||
|
|
||||||
|
con = sqlite3.connect(args.propagate)
|
||||||
|
logger.info("Create categories")
|
||||||
|
cur = con.cursor()
|
||||||
|
cur.executemany('INSERT OR IGNORE INTO categories(id, supercategory, name) VALUES (:id, :supercategory, :name)', coco.cats.values())
|
||||||
|
con.commit()
|
||||||
|
|
||||||
|
logger.info("Images...")
|
||||||
|
cur.executemany('''
|
||||||
|
INSERT OR IGNORE INTO images(id, flickr_url, coco_url, width, height, date_captured)
|
||||||
|
VALUES (:id, :flickr_url, :coco_url, :width, :height, :date_captured)
|
||||||
|
''', coco.imgs.values())
|
||||||
|
con.commit()
|
||||||
|
|
||||||
|
logger.info("Annotations...")
|
||||||
|
|
||||||
|
|
||||||
|
def annotation_generator():
|
||||||
|
for c in coco.anns.values():
|
||||||
|
ann = c.copy()
|
||||||
|
ann['bbox_top'] = ann['bbox'][1]
|
||||||
|
ann['bbox_left'] = ann['bbox'][0]
|
||||||
|
ann['bbox_width'] = ann['bbox'][2]
|
||||||
|
ann['bbox_height'] = ann['bbox'][3]
|
||||||
|
yield ann
|
||||||
|
|
||||||
|
cur.executemany('''
|
||||||
|
INSERT OR IGNORE INTO annotations(id, image_id, category_id, iscrowd, area, bbox_top, bbox_left, bbox_width, bbox_height)
|
||||||
|
VALUES (:id, :image_id, :category_id, :iscrowd, :area, :bbox_top, :bbox_left, :bbox_width, :bbox_height)
|
||||||
|
''', annotation_generator())
|
||||||
|
con.commit()
|
||||||
|
|
||||||
|
|
||||||
|
logger.info("Segments...")
|
||||||
|
|
||||||
|
def segment_generator():
|
||||||
|
for ann in coco.anns.values():
|
||||||
|
for i, seg in enumerate(ann['segmentation']):
|
||||||
|
yield {
|
||||||
|
'id': ann['id']*10 + i, # create a uniqe segment id, supports max 10 segments per annotation
|
||||||
|
'annotation_id': ann['id'],
|
||||||
|
'points': str(seg)[1:-1],
|
||||||
|
}
|
||||||
|
|
||||||
|
cur.executemany('''
|
||||||
|
INSERT OR IGNORE INTO segments(id, annotation_id, points)
|
||||||
|
VALUES (:id, :annotation_id, :points)
|
||||||
|
''', segment_generator())
|
||||||
|
con.commit()
|
||||||
|
|
||||||
|
|
||||||
|
logger.info("Done...")
|
||||||
|
|
||||||
|
# for id, cat in coco.cats.items():
|
||||||
|
# cur = con.cursor()
|
||||||
|
# cur.execute
|
234
www/coco.js
Normal file
234
www/coco.js
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
// 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')+"</"+this.name+">";
|
||||||
|
} 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).stroke(color);
|
||||||
|
case 8:
|
||||||
|
return textures.circles().thicker().complement().stroke(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": "#f00",
|
||||||
|
"vehicle": "#0f0",
|
||||||
|
"outdoor": "#006",
|
||||||
|
"animal": "#ff0",
|
||||||
|
"food": "#0ff",
|
||||||
|
"furniture": "#f0f",
|
||||||
|
"indoor": "#fff",
|
||||||
|
"electronic": "#390",
|
||||||
|
"kitchen": "#930",
|
||||||
|
"accessory": "#f90",
|
||||||
|
"sports": "#f09",
|
||||||
|
}
|
||||||
|
|
||||||
|
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.canvas = document.getElementById('svgCanvas');
|
||||||
|
this.loadNav()
|
||||||
|
}
|
||||||
|
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 ulEl = crel('ul');
|
||||||
|
for(let cat of categories) {
|
||||||
|
ulEl.appendChild(
|
||||||
|
crel('li', {
|
||||||
|
'id': 'category-' + cat['id'],
|
||||||
|
'on': {
|
||||||
|
'click': (e) => {
|
||||||
|
this.requestAnnotation(cat['id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, cat['name'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.catNavEl.appendChild(ulEl);
|
||||||
|
|
||||||
|
let 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);
|
||||||
|
defsEl.innerHTML += sel.toString();
|
||||||
|
}
|
||||||
|
this.canvas.appendChild(defsEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnnotation(category_id) {
|
||||||
|
let r = new Request(`/annotation.json?category=${category_id}&normalise=100`);
|
||||||
|
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) {
|
||||||
|
console.log(downE);
|
||||||
|
console.log(this)
|
||||||
|
let offset = this.getMousePosition(downE);
|
||||||
|
// offset.x -= parseFloat(downE.target.getAttributeNS(null, "x"));
|
||||||
|
// offset.y -= parseFloat(downE.target.getAttributeNS(null, "y"));
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
console.log(annEl);
|
||||||
|
this.canvas.appendChild(annEl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cc = new CocoCanvas();
|
||||||
|
cc.start();
|
127
www/coco_example.js
Normal file
127
www/coco_example.js
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
// 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')+"</"+this.name+">";
|
||||||
|
} else {
|
||||||
|
return "<"+this.name+attrs+"/>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//////////// end of faked dom ///////////////////////////////
|
||||||
|
|
||||||
|
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(10).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(7).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).stroke(color);
|
||||||
|
case 8:
|
||||||
|
return textures.circles().thicker().complement().stroke(color);
|
||||||
|
case 9:
|
||||||
|
return textures.paths().d("caps").lighter().thicker().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": "#f00",
|
||||||
|
"vehicle": "#0f0",
|
||||||
|
"outdoor": "#006",
|
||||||
|
"animal": "#ff0",
|
||||||
|
"food": "#0ff",
|
||||||
|
"furniture": "#f0f",
|
||||||
|
"indoor": "#fff",
|
||||||
|
"electronic": "#390",
|
||||||
|
"kitchen": "#930",
|
||||||
|
"accessory": "#f90",
|
||||||
|
"sports": "#f09",
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColorForSuperCategory(name) {
|
||||||
|
return categoryColors[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTextureForCategory(name, color) {
|
||||||
|
let hash = name+'-'+color;
|
||||||
|
if(!textureMap.hasOwnProperty(hash)) {
|
||||||
|
let i = Object.keys(textureMap).length;
|
||||||
|
textureMap[hash] = getColoredTexture(i, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
return textureMap[hash];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// turn HTMLCollection into Array, to prevent dynamic updating of the collection during loop
|
||||||
|
pathEls = Array.from(document.getElementsByTagName('path'));
|
||||||
|
for(pathEl of pathEls){
|
||||||
|
let defsEls = pathEl.parentNode.getElementsByTagName('defs');
|
||||||
|
let defsEl;
|
||||||
|
if(defsEls.length < 1) {
|
||||||
|
defsEl = document.createElement("DEFS");
|
||||||
|
pathEl.parentNode.appendChild(defsEl);
|
||||||
|
} else {
|
||||||
|
defsEl = defsEls[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(pathEl,pathEl.classList);
|
||||||
|
if(pathEl.classList.length != 2) continue;
|
||||||
|
let super_name = pathEl.classList.item(0).substr(6);
|
||||||
|
let cat_name = pathEl.classList.item(1).substr(4);
|
||||||
|
let color = getColorForSuperCategory(super_name)
|
||||||
|
let texture = getTextureForCategory(cat_name, color);
|
||||||
|
|
||||||
|
if(!defsEl.parentNode.getElementById(texture.id())) {
|
||||||
|
let sel = new dom();
|
||||||
|
texture(sel);
|
||||||
|
console.log(sel, sel.toString())
|
||||||
|
defsEl.innerHTML += sel.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
pathEl.style.fill = texture.url();
|
||||||
|
}
|
47
www/crel.min.js
vendored
Normal file
47
www/crel.min.js
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
!function(n,e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):n.crel=e()}(this,function(){function n(a){var d,s=arguments,p=s[1],y=2,m=s.length,x=n[o];if(a=n[u](a)?a:c.createElement(a),m>1){if((!e(p,r)||n[f](p)||Array.isArray(p))&&(--y,p=null),m-y==1&&e(s[y],"string"))a.textContent=s[y];else for(;y<m;++y)null!==(d=s[y])&&l(a,d);for(var v in p)if(x[v]){var A=x[v];e(A,t)?A(a,p[v]):a[i](A,p[v])}else e(p[v],t)?a[v]=p[v]:a[i](v,p[v])}return a}var e=function(n,e){return typeof n===e},t="function",r="object",i="setAttribute",o="attrMap",f="isNode",u="isElement",c=document,a=function(n){return n instanceof Node},d=function(n){return n instanceof Element},l=function(e,t){if(Array.isArray(t))return void t.map(function(n){l(e,n)});n[f](t)||(t=c.createTextNode(t)),e.appendChild(t)};return n[o]={},n[u]=d,n[f]=a,e(Proxy,"undefined")||(n.proxy=new Proxy(n,{get:function(e,t){return!(t in n)&&(n[t]=n.bind(null,t)),n[t]}})),n});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extended CREL, originally for PillowTalk
|
||||||
|
* - event listeners with 'on'
|
||||||
|
* - Array with 'options' for 'select'
|
||||||
|
* - input type 'checkbox' has 'checked_value', which validates as boolean to set checked state
|
||||||
|
*/
|
||||||
|
|
||||||
|
// for input type checkbox, map value to a checkbox
|
||||||
|
crel.attrMap['checked_value'] = function(element, value) {
|
||||||
|
if(value) {
|
||||||
|
element.checked = 'checked';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
crel.attrMap['on'] = function(element, value) {
|
||||||
|
for (var eventName in value) {
|
||||||
|
element.addEventListener(eventName, value[eventName]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
crel.attrMap['options'] = function(element, values, a, b) {
|
||||||
|
if(element.tagName != "SELECT") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let selectedValue = element.value ? element.value : element.attributes.value.value;
|
||||||
|
|
||||||
|
if(Array.isArray(values)) {
|
||||||
|
for (let option of values) {
|
||||||
|
if(selectedValue == option) {
|
||||||
|
element.appendChild(crel('option', {'selected': 'selected'}, option));
|
||||||
|
} else {
|
||||||
|
element.appendChild(crel('option', option));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let option in values) {
|
||||||
|
if(selectedValue == option) {
|
||||||
|
element.appendChild(crel('option', {'selected': 'selected','value': option}, values[option]));
|
||||||
|
}else{
|
||||||
|
element.appendChild(crel('option', {'value': option}, values[option]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
17
www/example.html
Normal file
17
www/example.html
Normal file
File diff suppressed because one or more lines are too long
56
www/index.html
Normal file
56
www/index.html
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
<style type='text/css'>
|
||||||
|
body{
|
||||||
|
font-family:sans-serif;
|
||||||
|
background: darkblue;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#svgCanvas{
|
||||||
|
width:100vw;
|
||||||
|
height:100vh;
|
||||||
|
}
|
||||||
|
#catNav{
|
||||||
|
position:absolute;
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
|
color:white;
|
||||||
|
}
|
||||||
|
#catNav ul{
|
||||||
|
list-style:none;
|
||||||
|
padding:10px;
|
||||||
|
}
|
||||||
|
#catNav li:hover{
|
||||||
|
cursor:pointer;
|
||||||
|
text-decoration:underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
g{
|
||||||
|
cursor:grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
g:active{
|
||||||
|
cursor:grabbing;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav id='catNav'>
|
||||||
|
</nav>
|
||||||
|
<svg
|
||||||
|
id='svgCanvas'
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:ev="http://www.w3.org/2001/xml-events"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 1000 1000">
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <script src="svg-inject.min.js"></script> -->
|
||||||
|
<script src="textures.js"></script>
|
||||||
|
<script src="crel.min.js"></script>
|
||||||
|
<script src="coco.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
10
www/svg-inject.min.js
vendored
Normal file
10
www/svg-inject.min.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
!function(o,l){var r,a,s="createElement",g="getElementsByTagName",b="length",E="style",d="title",y="undefined",k="setAttribute",w="getAttribute",x=null,A="__svgInject",C="--inject-",S=new RegExp(C+"\\d+","g"),I="LOAD_FAIL",t="SVG_NOT_SUPPORTED",L="SVG_INVALID",v=["src","alt","onload","onerror"],j=l[s]("a"),G=typeof SVGRect!=y,f={useCache:!0,copyAttributes:!0,makeIdsUnique:!0},N={clipPath:["clip-path"],"color-profile":x,cursor:x,filter:x,linearGradient:["fill","stroke"],marker:["marker",
|
||||||
|
"marker-end","marker-mid","marker-start"],mask:x,pattern:["fill","stroke"],radialGradient:["fill","stroke"]},u=1,c=2,O=1;function T(e){return(r=r||new XMLSerializer).serializeToString(e)}function P(e,r){var t,n,i,o,a=C+O++,f=/url\("?#([a-zA-Z][\w:.-]*)"?\)/g,u=e.querySelectorAll("[id]"),c=r?[]:x,l={},s=[],d=!1;if(u[b]){for(i=0;i<u[b];i++)(n=u[i].localName)in N&&(l[n]=1);for(n in l)(N[n]||[n]).forEach(function(e){s.indexOf(e)<0&&s.push(e)});s[b]&&s.push(E);var v,p,m,h=e[g]("*"),y=e;for(i=-1;y!=x;
|
||||||
|
){if(y.localName==E)(m=(p=y.textContent)&&p.replace(f,function(e,r){return c&&(c[r]=1),"url(#"+r+a+")"}))!==p&&(y.textContent=m);else if(y.hasAttributes()){for(o=0;o<s[b];o++)v=s[o],(m=(p=y[w](v))&&p.replace(f,function(e,r){return c&&(c[r]=1),"url(#"+r+a+")"}))!==p&&y[k](v,m);["xlink:href","href"].forEach(function(e){var r=y[w](e);/^\s*#/.test(r)&&(r=r.trim(),y[k](e,r+a),c&&(c[r.substring(1)]=1))})}y=h[++i]}for(i=0;i<u[b];i++)t=u[i],c&&!c[t.id]||(t.id+=a,d=!0)}return d}function V(e,r,t,n){if(r){
|
||||||
|
r[k]("data-inject-url",t);var i=e.parentNode;if(i){n.copyAttributes&&function c(e,r){for(var t,n,i,o=e.attributes,a=0;a<o[b];a++)if(n=(t=o[a]).name,-1==v.indexOf(n))if(i=t.value,n==d){var f,u=r.firstElementChild;u&&u.localName.toLowerCase()==d?f=u:(f=l[s+"NS"]("http://www.w3.org/2000/svg",d),r.insertBefore(f,u)),f.textContent=i}else r[k](n,i)}(e,r);var o=n.beforeInject,a=o&&o(e,r)||r;i.replaceChild(a,e),e[A]=u,m(e);var f=n.afterInject;f&&f(e,a)}}else D(e,n)}function p(){for(var e={},r=arguments,
|
||||||
|
t=0;t<r[b];t++){var n=r[t];for(var i in n)n.hasOwnProperty(i)&&(e[i]=n[i])}return e}function _(e,r){if(r){var t;try{t=function i(e){return(a=a||new DOMParser).parseFromString(e,"text/xml")}(e)}catch(o){return x}return t[g]("parsererror")[b]?x:t.documentElement}var n=l.createElement("div");return n.innerHTML=e,n.firstElementChild}function m(e){e.removeAttribute("onload")}function n(e){console.error("SVGInject: "+e)}function i(e,r,t){e[A]=c,t.onFail?t.onFail(e,r):n(r)}function D(e,r){m(e),i(e,L,r)
|
||||||
|
}function F(e,r){m(e),i(e,t,r)}function M(e,r){i(e,I,r)}function q(e){e.onload=x,e.onerror=x}function R(e){n("no img element")}var e=function z(e,r){var t=p(f,r),h={};function n(a,f){f=p(t,f);var e=function(r){var e=function(){var e=f.onAllFinish;e&&e(),r&&r()};if(a&&typeof a[b]!=y){var t=0,n=a[b];if(0==n)e();else for(var i=function(){++t==n&&e()},o=0;o<n;o++)u(a[o],f,i)}else u(a,f,e)};return typeof Promise==y?e():new Promise(e)}function u(u,c,e){if(u){var r=u[A];if(r)Array.isArray(r)?r.push(e
|
||||||
|
):e();else{if(q(u),!G)return F(u,c),void e();var t=c.beforeLoad,n=t&&t(u)||u[w]("src");if(!n)return""===n&&M(u,c),void e();var i=[];u[A]=i;var l=function(){e(),i.forEach(function(e){e()})},s=function f(e){return j.href=e,j.href}(n),d=c.useCache,v=c.makeIdsUnique,p=function(r){d&&(h[s].forEach(function(e){e(r)}),h[s]=r)};if(d){var o,a=function(e){if(e===I)M(u,c);else if(e===L)D(u,c);else{var r,t=e[0],n=e[1],i=e[2];v&&(t===x?(t=P(r=_(n,!1),!1),e[0]=t,e[2]=t&&T(r)):t&&(n=function o(e){
|
||||||
|
return e.replace(S,C+O++)}(i))),r=r||_(n,!1),V(u,r,s,c)}l()};if(typeof(o=h[s])!=y)return void(o.isCallbackQueue?o.push(a):a(o));(o=[]).isCallbackQueue=!0,h[s]=o}!function m(e,r,t){if(e){var n=new XMLHttpRequest;n.onreadystatechange=function(){if(4==n.readyState){var e=n.status;200==e?r(n.responseXML,n.responseText.trim()):400<=e?t():0==e&&t()}},n.open("GET",e,!0),n.send()}}(s,function(e,r){var t=e instanceof Document?e.documentElement:_(r,!0),n=c.afterLoad;if(n){var i=n(t,r)||t;if(i){
|
||||||
|
var o="string"==typeof i;r=o?i:T(t),t=o?_(i,!0):i}}if(t instanceof SVGElement){var a=x;if(v&&(a=P(t,!1)),d){var f=a&&T(t);p([a,r,f])}V(u,t,s,c)}else D(u,c),p(L);l()},function(){M(u,c),p(I),l()})}}else R()}return G&&function i(e){var r=l[g]("head")[0];if(r){var t=l[s](E);t.type="text/css",t.appendChild(l.createTextNode(e)),r.appendChild(t)}}('img[onload^="'+e+'("]{visibility:hidden;}'),n.setOptions=function(e){t=p(t,e)},n.create=z,n.err=function(e,r){e?e[A]!=c&&(q(e),G?(m(e),M(e,t)):F(e,t),r&&(m(
|
||||||
|
e),e.src=r)):R()},o[e]=n}("SVGInject");"object"==typeof module&&"object"==typeof module.exports&&(module.exports=e)}(window,document);
|
1
www/textures.js
Normal file
1
www/textures.js
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue