Version with save option

This commit is contained in:
Ruben van de Ven 2020-03-04 22:24:49 +01:00
parent 1702cce9d1
commit 7d11274be8
13 changed files with 1150 additions and 132 deletions

View file

@ -1,3 +1,20 @@
server.py
: Server for the web interface
create_shapes.py
: Create an svg file per image, with classes on the shapes according to their classes
zoom_animation.py
: Create svg frames for a category. Ordered by the area of the shapes.
generate_lonely_segments.py
: Find and download the images with only one object in them.
tools.py
: Turn a COCO json file (eg `instances_val2017.json`) into a database format (eg `coco_train.db`)
---
Build array of images sorted by size: Build array of images sorted by size:
@ -20,3 +37,16 @@ cd ../dog_png
ffmpeg -f image2 -pattern_type glob -i '*.png' ../dog.mp4 ffmpeg -f image2 -pattern_type glob -i '*.png' ../dog.mp4
``` ```
# To run as server:
```bash
cp plottingdata_coco.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable plottingdata_coco.service
systemctl start plottingdata_coco.service
```
```
rsync . --exclude zoom --exclude venv --exclude archive -av here.rubenvandeven.com:/home/ruben/coco/ --exclude shapes --exclude lonely --exclude .git --exclude __pycache__ --info progress2
```

View file

@ -30,6 +30,7 @@ CREATE TABLE IF NOT EXISTS "annotations" (
"bbox_left" FLOAT, "bbox_left" FLOAT,
"bbox_width" FLOAT, "bbox_width" FLOAT,
"bbox_height" FLOAT, "bbox_height" FLOAT,
"zerkine_moments" TEXT DEFAULT NULL,
PRIMARY KEY("id") PRIMARY KEY("id")
) WITHOUT ROWID; ) WITHOUT ROWID;
CREATE INDEX IF NOT EXISTS "segments_annotation" ON "segments" ( CREATE INDEX IF NOT EXISTS "segments_annotation" ON "segments" (

View file

@ -0,0 +1,9 @@
[Unit]
Description=PlottingData COCO interface
[Service]
ExecStart=/home/ruben/coco/venv/bin/python /home/ruben/coco/server.py --db /home/ruben/coco/coco_train.db --port 8080
WorkingDirectory=/home/ruben/coco
User=www-data
Restart=on-failure
[Install]
WantedBy=multi-user.target

5
requirements.txt Normal file
View file

@ -0,0 +1,5 @@
tornado
coloredlogs
pycocotools
numpy
mahotas

104
server.py
View file

@ -7,6 +7,9 @@ import coloredlogs
from coco.storage import COCOStorage from coco.storage import COCOStorage
import json import json
from urllib.parse import urlparse from urllib.parse import urlparse
import uuid
import os
import glob
logger = logging.getLogger('coco.server') logger = logging.getLogger('coco.server')
@ -26,6 +29,9 @@ class RestHandler(tornado.web.RequestHandler):
def get(self, *params): def get(self, *params):
self.write(json.dumps(self.getData(*params), cls=JsonEncoder)) self.write(json.dumps(self.getData(*params), cls=JsonEncoder))
def post(self, *params):
self.write(json.dumps(self.getData(*params), cls=JsonEncoder))
class CategoryHandler(RestHandler): class CategoryHandler(RestHandler):
def getData(self): def getData(self):
return self.storage.getCategories() return self.storage.getCategories()
@ -53,24 +59,89 @@ class AnnotationHandler(RestHandler):
return annotation return annotation
class WebSocketHandler(tornado.websocket.WebSocketHandler): class SaveHandler(RestHandler):
CORS_ORIGINS = ['localhost', 'coco.local', 'r3.local'] def getData(self):
"""
Save an SVG. Regenerate it on the server to prevent any maliscious input
"""
req = tornado.escape.json_decode(self.request.body)
scene = int(req['scene'])
annotations = []
def check_origin(self, origin): with open('www/canvas_patterns.json') as fp:
parsed_origin = urlparse(origin) patterns = json.load(fp)
# parsed_origin.netloc.lower() gives localhost:3333 svgGs = []
valid = parsed_origin.hostname in self.CORS_ORIGINS
return valid
# the client connected for annotation in req['annotations'][:100]: # max 200 annotations
def open(self, p = None): annId = int(annotation['id'])
WebSocketHandler.connections.add(self) ann = self.storage.getAnnotationById(annId)
logger.info("New client connected") normalisedAnn = ann.getNormalised(100,100)
self.write_message("hello!") x = float(annotation['x'])
y = float(annotation['y'])
fill = patterns[str(ann.category_id)]
segments = []
# the client sent the message textX = normalisedAnn.bbox[2]+5
def on_message(self, message): textY = normalisedAnn.bbox[3]
logger.debug(f"recieve: {message}")
cat = self.storage.getCategory(ann.category_id)
image = self.storage.getImage(ann.image_id)
for segment in normalisedAnn.segments:
d = segment.getD()
segments.append(f"""<path fill="{fill}" d="{d}"></path>""")
svgGs.append(f"""
<g data-id="{ann.id}" transform="translate({x},{y})">
<image href="{image ['coco_url']}"
width="{image['width']*normalisedAnn.scale}"
height="{image['height']*normalisedAnn.scale}"
x="{ann.bbox[0] * -1 * normalisedAnn.scale}"
y="{ann.bbox[1] * -1 * normalisedAnn.scale}"></image>
{"".join(segments)}
<text fill="white" font-size="40pt" font-family="sans-serif" x="{textX}" y="{textY}">{cat['name']}</text>
</g>
""")
annotations.append({'id': annId, 'x': x, 'y': y})
source = json.dumps({
'scene': scene,
'annotations': annotations
})
with open('www/canvas.svg') as fp:
svgContent = fp.read()
svgContent = svgContent.replace('{source}', json.dumps(source))\
.replace('</svg>', "".join(svgGs)+"</svg>")
saveId = uuid.uuid4().hex + '.svg'
filename = os.path.join('www/saved', saveId)
with open(filename, 'w') as fp:
fp.write(svgContent)
return {'submission':'/saved/'+saveId}
class SavedHandler(tornado.web.RequestHandler):
def initialize(self, storage: COCOStorage):
self.storage = storage
def get(self):
images = []
files = glob.glob("www/saved/*.svg")
files.sort(key=lambda f: -1 * os.path.getmtime(f))
for filename in files[:100]:
with open(filename, 'r') as fp:
# remove first XML line:
contents = '\n'.join(fp.read().split('\n')[1:])
images.append(contents)
with open("www/saved.html") as fp:
template = fp.read()
template = template.replace("{images}", ''.join(images))
self.write(template)
class StaticFileWithHeaderHandler(tornado.web.StaticFileHandler): class StaticFileWithHeaderHandler(tornado.web.StaticFileHandler):
@ -83,9 +154,10 @@ def make_app(db_filename, debug):
storage = COCOStorage(db_filename) storage = COCOStorage(db_filename)
return tornado.web.Application([ return tornado.web.Application([
(r"/ws(.*)", WebSocketHandler),
(r"/categories.json", CategoryHandler, {'storage': storage}), (r"/categories.json", CategoryHandler, {'storage': storage}),
(r"/annotation.json", AnnotationHandler, {'storage': storage}), (r"/annotation.json", AnnotationHandler, {'storage': storage}),
(r"/save", SaveHandler, {'storage': storage}),
(r"/saved", SavedHandler, {'storage': storage}),
(r"/(.*)", StaticFileWithHeaderHandler, (r"/(.*)", StaticFileWithHeaderHandler,
{"path": 'www', "default_filename": 'index.html'}), {"path": 'www', "default_filename": 'index.html'}),
], debug=debug) ], debug=debug)

219
tools.py
View file

@ -4,6 +4,16 @@ import logging
import os import os
import pprint import pprint
import sqlite3 import sqlite3
from coco.storage import COCOStorage, Annotation, Segment
import cv2
import mahotas
import subprocess
import tqdm
import numpy as np
import ast
import svgwrite
from svgwrite.extensions import Inkscape
from xml.etree import ElementTree
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("coco") logger = logging.getLogger("coco")
@ -20,10 +30,34 @@ argParser.add_argument(
help='Show categories' help='Show categories'
) )
argParser.add_argument( argParser.add_argument(
'--propagate', '--db',
type=str, type=str,
metavar='DATABASE', metavar='DATABASE',
help='Store data in sqlite db' help='SQLite db filename, will be created if not existing'
)
argParser.add_argument(
'--propagate',
action='store_true',
help='Store annotation data in sqlite db'
)
argParser.add_argument(
'--zerkine',
action='store_true',
help='Find and store annotation Zerkine moments for those that do not have it yet'
)
argParser.add_argument(
'--similar',
type=int,
metavar="ANNOTATION_ID",
help='Find similar shapes for annotation'
)
argParser.add_argument(
'--stickers',
type=str,
metavar="SVG_FILENAME",
help="""
Create an SVG with sticker pages (afterwards convert to EPS: \"for f in *; do echo $f; inkscape -f $f --export-eps $f.eps; done\")
"""
) )
args = argParser.parse_args() args = argParser.parse_args()
@ -41,15 +75,12 @@ if args.categories:
# pp = pprint.PrettyPrinter(indent=4) # pp = pprint.PrettyPrinter(indent=4)
pprint.pprint(cats, sort_dicts=False) pprint.pprint(cats, sort_dicts=False)
if args.propagate: storage = None
if not os.path.exists(args.propagate): if args.db:
con = sqlite3.connect(args.propagate) storage = COCOStorage(args.db)
cur = con.cursor() con = storage.con
with open('coco.sql', 'r') as fp:
cur.executescript(fp.read())
con.close()
con = sqlite3.connect(args.propagate) if args.propagate:
logger.info("Create categories") logger.info("Create categories")
cur = con.cursor() cur = con.cursor()
cur.executemany('INSERT OR IGNORE INTO categories(id, supercategory, name) VALUES (:id, :supercategory, :name)', coco.cats.values()) cur.executemany('INSERT OR IGNORE INTO categories(id, supercategory, name) VALUES (:id, :supercategory, :name)', coco.cats.values())
@ -101,6 +132,168 @@ if args.propagate:
logger.info("Done...") logger.info("Done...")
# for id, cat in coco.cats.items(): if args.zerkine:
# cur = con.cursor() nr = storage.countAnnotationsWithoutZerkine()
# cur.execute for i in tqdm.tqdm(range(nr)):
annotation = storage.getAnnotationWithoutZerkine()
normAnn = annotation.getNormalised(100, 100)
filenameRoot = '/tmp/tmp_ann_to_convert'
dwg = normAnn.asSvg(filenameRoot + '.svg', square=True, bg='black')
dwg.save()
# convert to rasterised
subprocess.call([
'inkscape',
'-f', filenameRoot + '.svg',
'-e', filenameRoot + '.png',
],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
# read with opencv
image = cv2.imread(filenameRoot + '.png', cv2.IMREAD_GRAYSCALE)
moments = mahotas.features.zernike_moments(image, 21)
storage.storeZerkineForAnnotation(annotation, moments, delayCommit = True)
if not i % 100:
storage.con.commit()
storage.con.commit()
if args.similar:
# todo find similar
annotation = storage.getAnnotationById(args.similar, withZerkine=True)
dwg = annotation.asSvg(f'tmp/source.svg', square=True)
dwg.save()
shapeA = np.array(annotation.segments[0].points)
annMoments = np.array(annotation.zerkine_moment)
distances = []
print(annotation)
# Stupid, this seems to have been superfluous
# zerkines = storage.getZerkines()
# for zerkine in tqdm.tqdm(zerkines):
# if annotation.id == zerkine['id']:
# continue
#
# diff = annMoments - np.array(Annotation.parseZerkineFromDB(zerkine['zerkine_moment']))
# distance = np.linalg.norm(diff)
# distances.append((zerkine['id'], distance))
anns = storage.getAllAnnotationPoints()
for ann in tqdm.tqdm(anns):
try:
shapeB = np.array(Segment.asCoordinates(ast.literal_eval('['+ann['points']+']')))
# fourth param is require, but according to docs does nothing
distance= cv2.matchShapes(shapeA, shapeB, cv2.CONTOURS_MATCH_I2, 1)
distances.append((ann['id'], distance))
except Exception as e:
logger.critical(f"Exception comparing {annotation.id} to {ann['id']}, points: {ann['points']}")
logger.exception(e)
distances = sorted(distances, key=lambda d: d[1])
for i in range(10):
similarAnnotation = storage.getAnnotationById(distances[i][0])
print(coco.cats[similarAnnotation.category_id])
dwg = similarAnnotation.asSvg(f'tmp/result_{i}.svg', square=True)
dwg.save()
if args.stickers:
grid = (3, 4) # items in the grid x,y
size = (105, 148) # in mm
sizeFactor = 5 # influences the size of the patterns
viewBoxSize = (size[0] * sizeFactor, size[1] * sizeFactor)
margin = 5
gridSize = (
int((viewBoxSize[0]-((grid[0]+1)*margin))/grid[0]),
int((viewBoxSize[1]-((grid[1]+1)*margin))/grid[1])
)
# see also textures.xml
textureIds = ["#siqwx","#wjnbs","#pnfez","#ejtxy","#obabs","#hehoj","#mrwjs","#ryjbw","#rkkau","#vbjcl","#zzehx","#mumke","#brhhk","#gujvh","#hfgqa","#lrbsh","#bndby","#bfnxk","#ydler","#pnxdr","#htqlj","#nunnt","#tidaw","#tcdum","#kwwja","#hgdkl","#nvkwz","#uzdqb","#fgshk","#vknil","#yeenr","#mslkw","#eibaw","#meama","#akuvz","#khkpp","#ibnow","#wivvx","#svksy","#xhmew","#jmiqu","#gfcer","#iueil","#iufvt","#ugkud","#dchzd","#nejks","#dqseb","#yhrwm","#bmiet","#qovkk","#hxoiq","#jfguh","#kbpkl","#ikarj","#nucap","#qfsqn","#bboqt","#pxkjn","#lbnx","#nxkmp","#snojb","#oioil","#hvldz","#qpscp","#oborh","#crobu","#ydhwn","#geanf","#sdfeo","#cgtma","#rjfrc","#uhcys","#lrgem","#osiho","#etssd","#esxcs","#hczhr","#nnhxw","#wrlbu"]
nr = 0
total_nr = len(coco.cats)
for category_id, cat in coco.cats.items():
nr+=1
filename = os.path.join(
args.stickers,
f"{category_id}_{cat['supercategory']}_{cat['name']}.svg")
dwg = svgwrite.Drawing(
filename,
size=(f'{size[0]}mm', f'{size[1]}mm'),
viewBox=f"0 0 {viewBoxSize[0]} {viewBoxSize[1]}"
)
annotations = storage.getRandomAnnotations(
limit = grid[0]*grid[1],
category_id = category_id
)
inkscape = Inkscape(dwg)
contourG = inkscape.layer(label='Snijlijnen')
drawingG = inkscape.layer(label='Shapes')
# dwg.add(svgwrite.container.Defs())
dwg.add(drawingG)
dwg.add(contourG)
font_size = 10
text = dwg.text(
f"{nr:02d}/{total_nr}",
insert=(margin, margin+font_size), font_size=font_size, fill='black'
)
drawingG.add(text)
text = dwg.text(
f"{category_id}. {cat['supercategory']} - {cat['name']}",
insert=(viewBoxSize[0]-margin, margin+font_size), font_size=font_size, fill='black',
style='text-anchor:end;')
drawingG.add(text)
text = dwg.text(
f"Common Objects In Context",
insert=(margin, viewBoxSize[1]-margin), font_size=font_size, fill='black',
)
drawingG.add(text)
text = dwg.text(
f"Plotting Data",
insert=(viewBoxSize[0]-margin, viewBoxSize[1]-margin), font_size=font_size, fill='black',
style='text-anchor:end;')
drawingG.add(text)
for i, annotation in enumerate(annotations):
normAnn = annotation.getNormalised(gridSize[0], gridSize[1])
translation = normAnn.getTranslationToCenter()
# print(translation)
pX = i%grid[0]
pY = int(i/grid[0])
posX = pX*gridSize[0] + (pX+1)*margin - translation[0]
posY = pY*gridSize[1] + (pY+1)*margin - translation[1]
# print(i, posX, posY, gridSize)
positionG = svgwrite.container.Group(transform=f'translate({posX}, {posY})')
normAnn.writeToDrawing(positionG, stroke='#2FEE2F', stroke_width='1pt', fill_opacity="0")
contourG.add(positionG)
position2G = svgwrite.container.Group(transform=f'translate({posX}, {posY})')
pattern_id = textureIds[category_id % len(textureIds)]
normAnn.writeToDrawing(position2G, fill=f'url({pattern_id})', stroke='blue', stroke_width='0')
drawingG.add(position2G)
xml = dwg.get_xml()
with open('textures.xml', 'r') as fp:
textureTree = ElementTree.fromstring(fp.read())
defsTree = xml.find('defs')
for pattern in textureTree:
defsTree.append(pattern)
xmlString = ElementTree.tostring(xml)
with open(filename, 'wb') as fp:
# print(xmlString)
fp.write(xmlString)
logger.info(f"Wrote to {filename}")

309
www/canvas.svg Normal file
View file

@ -0,0 +1,309 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" id="svgCanvas" viewBox="0 0 1000 1000">
<metadata><rdf:RDF><dc:source>{source}</dc:source></rdf:RDF></metadata>
<defs>
<pattern id="tprke" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="2" fill="none" stroke="#FA0800" stroke-width="1" />
<circle cx="0" cy="0" r="2" fill="none" stroke="#FA0800" stroke-width="1" />
<circle cx="0" cy="5" r="2" fill="none" stroke="#FA0800" stroke-width="1" />
<circle cx="5" cy="0" r="2" fill="none" stroke="#FA0800" stroke-width="1" />
<circle cx="5" cy="5" r="2" fill="none" stroke="#FA0800" stroke-width="1" />
</pattern>
<pattern id="szylj" patternUnits="userSpaceOnUse" width="10" height="10">
<path d="M 0,7.5 l 10,-5 M 0,2.5 l 10,-5 M 0,12.5 l 10,-5" stroke-width="2" shape-rendering="auto" stroke="#80FF00" stroke-linecap="square" />
</pattern>
<pattern id="ooumd" patternUnits="userSpaceOnUse" width="32" height="32">
<path d="M 0,32 l 32,-32 M -8,8 l 16,-16 M 24,40 l 16,-16" stroke-width="16" shape-rendering="auto" stroke="#80FF00" stroke-linecap="square" />
</pattern>
<pattern id="fheyp" patternUnits="userSpaceOnUse" width="9" height="5.196152422706632">
<path d="M 3,0 l 3,0 l 1.5,2.598076211353316 l -1.5,2.598076211353316 l -3,0 l -1.5,-2.598076211353316 Z M 0,2.598076211353316 l 1.5,0 M 9,2.598076211353316 l -1.5,0" fill="transparent" stroke="#80FF00" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="ssscs" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 2, 0 l 0, 4" stroke-width="1" shape-rendering="crispEdges" stroke="#80FF00" stroke-linecap="square" />
<path d="M 0,2 l 4,0" stroke-width="1" shape-rendering="crispEdges" stroke="#80FF00" stroke-linecap="square" />
</pattern>
<pattern id="htafw" patternUnits="userSpaceOnUse" width="15" height="8.660254037844386">
<path d="M 5,0 l 5,0 l 2.5,4.330127018922193 l -2.5,4.330127018922193 l -5,0 l -2.5,-4.330127018922193 Z M 0,4.330127018922193 l 2.5,0 M 15,4.330127018922193 l -2.5,0" fill="#80FF00" stroke="rgba(0,0,0,0)" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="mwmpe" patternUnits="userSpaceOnUse" width="4" height="4">
<circle cx="2" cy="2" r="2" fill="#80FF00" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="ipzlm" patternUnits="userSpaceOnUse" width="10" height="10">
<circle cx="5" cy="5" r="2" fill="#80FF00" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="0" r="2" fill="#80FF00" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="10" r="2" fill="#80FF00" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="0" r="2" fill="#80FF00" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="10" r="2" fill="#80FF00" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="ryfgx" patternUnits="userSpaceOnUse" width="5" height="5">
<path d="M 1.25,3.75l1.25,-2.5l1.25,2.5" fill="transparent" stroke="#80FF00" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="gktcz" patternUnits="userSpaceOnUse" width="12" height="6.928203230275509">
<path d="M 4,0 l 4,0 l 2,3.4641016151377544 l -2,3.4641016151377544 l -4,0 l -2,-3.4641016151377544 Z M 0,3.4641016151377544 l 2,0 M 12,3.4641016151377544 l -2,0" fill="transparent" stroke="#00FFFF" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="nkuai" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 0,4 l 4,-4 M -1,1 l 2,-2 M 3,5 l 2,-2" stroke-width="1" shape-rendering="auto" stroke="#00FFFF" stroke-linecap="square" />
</pattern>
<pattern id="tnder" patternUnits="userSpaceOnUse" width="10" height="10">
<path d="M 0,7.5 l 10,-5 M 0,2.5 l 10,-5 M 0,12.5 l 10,-5" stroke-width="2" shape-rendering="auto" stroke="#00FFFF" stroke-linecap="square" />
</pattern>
<pattern id="hfgks" patternUnits="userSpaceOnUse" width="32" height="32">
<path d="M 0,32 l 32,-32 M -8,8 l 16,-16 M 24,40 l 16,-16" stroke-width="16" shape-rendering="auto" stroke="#00FFFF" stroke-linecap="square" />
</pattern>
<pattern id="mrlhp" patternUnits="userSpaceOnUse" width="9" height="5.196152422706632">
<path d="M 3,0 l 3,0 l 1.5,2.598076211353316 l -1.5,2.598076211353316 l -3,0 l -1.5,-2.598076211353316 Z M 0,2.598076211353316 l 1.5,0 M 9,2.598076211353316 l -1.5,0" fill="transparent" stroke="#00FFFF" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="cmsda" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 2, 0 l 0, 4" stroke-width="1" shape-rendering="crispEdges" stroke="#FF00FF" stroke-linecap="square" />
<path d="M 0,2 l 4,0" stroke-width="1" shape-rendering="crispEdges" stroke="#FF00FF" stroke-linecap="square" />
</pattern>
<pattern id="wsewh" patternUnits="userSpaceOnUse" width="15" height="8.660254037844386">
<path d="M 5,0 l 5,0 l 2.5,4.330127018922193 l -2.5,4.330127018922193 l -5,0 l -2.5,-4.330127018922193 Z M 0,4.330127018922193 l 2.5,0 M 15,4.330127018922193 l -2.5,0" fill="#FF00FF" stroke="rgba(0,0,0,0)" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="htoli" patternUnits="userSpaceOnUse" width="4" height="4">
<circle cx="2" cy="2" r="2" fill="#FF00FF" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="aoggk" patternUnits="userSpaceOnUse" width="10" height="10">
<circle cx="5" cy="5" r="2" fill="#FF00FF" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="0" r="2" fill="#FF00FF" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="10" r="2" fill="#FF00FF" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="0" r="2" fill="#FF00FF" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="10" r="2" fill="#FF00FF" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="mfcrf" patternUnits="userSpaceOnUse" width="5" height="5">
<path d="M 1.25,3.75l1.25,-2.5l1.25,2.5" fill="transparent" stroke="#FF00FF" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="igtrq" patternUnits="userSpaceOnUse" width="12" height="6.928203230275509">
<path d="M 4,0 l 4,0 l 2,3.4641016151377544 l -2,3.4641016151377544 l -4,0 l -2,-3.4641016151377544 Z M 0,3.4641016151377544 l 2,0 M 12,3.4641016151377544 l -2,0" fill="transparent" stroke="#FF00FF" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="wgyom" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 0,4 l 4,-4 M -1,1 l 2,-2 M 3,5 l 2,-2" stroke-width="1" shape-rendering="auto" stroke="#FF00FF" stroke-linecap="square" />
</pattern>
<pattern id="qxsnm" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="2" fill="none" stroke="#FF00FF" stroke-width="1" />
<circle cx="0" cy="0" r="2" fill="none" stroke="#FF00FF" stroke-width="1" />
<circle cx="0" cy="5" r="2" fill="none" stroke="#FF00FF" stroke-width="1" />
<circle cx="5" cy="0" r="2" fill="none" stroke="#FF00FF" stroke-width="1" />
<circle cx="5" cy="5" r="2" fill="none" stroke="#FF00FF" stroke-width="1" />
</pattern>
<pattern id="gmgmx" patternUnits="userSpaceOnUse" width="10" height="10">
<path d="M 0,7.5 l 10,-5 M 0,2.5 l 10,-5 M 0,12.5 l 10,-5" stroke-width="2" shape-rendering="auto" stroke="#FF00FF" stroke-linecap="square" />
</pattern>
<pattern id="qubbv" patternUnits="userSpaceOnUse" width="32" height="32">
<path d="M 0,32 l 32,-32 M -8,8 l 16,-16 M 24,40 l 16,-16" stroke-width="16" shape-rendering="auto" stroke="#FF00FF" stroke-linecap="square" />
</pattern>
<pattern id="ttene" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 2, 0 l 0, 4" stroke-width="1" shape-rendering="crispEdges" stroke="#FFFF00" stroke-linecap="square" />
<path d="M 0,2 l 4,0" stroke-width="1" shape-rendering="crispEdges" stroke="#FFFF00" stroke-linecap="square" />
</pattern>
<pattern id="fazqm" patternUnits="userSpaceOnUse" width="15" height="8.660254037844386">
<path d="M 5,0 l 5,0 l 2.5,4.330127018922193 l -2.5,4.330127018922193 l -5,0 l -2.5,-4.330127018922193 Z M 0,4.330127018922193 l 2.5,0 M 15,4.330127018922193 l -2.5,0" fill="#FFFF00" stroke="rgba(0,0,0,0)" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="nfcwz" patternUnits="userSpaceOnUse" width="5" height="5">
<path d="M 1.25,3.75l1.25,-2.5l1.25,2.5" fill="transparent" stroke="#FFFF00" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="rwmct" patternUnits="userSpaceOnUse" width="12" height="6.928203230275509">
<path d="M 4,0 l 4,0 l 2,3.4641016151377544 l -2,3.4641016151377544 l -4,0 l -2,-3.4641016151377544 Z M 0,3.4641016151377544 l 2,0 M 12,3.4641016151377544 l -2,0" fill="transparent" stroke="#FFFF00" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="iiitd" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 0,4 l 4,-4 M -1,1 l 2,-2 M 3,5 l 2,-2" stroke-width="1" shape-rendering="auto" stroke="#FFFF00" stroke-linecap="square" />
</pattern>
<pattern id="eajyr" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="2" fill="none" stroke="#0080FF" stroke-width="1" />
<circle cx="0" cy="0" r="2" fill="none" stroke="#0080FF" stroke-width="1" />
<circle cx="0" cy="5" r="2" fill="none" stroke="#0080FF" stroke-width="1" />
<circle cx="5" cy="0" r="2" fill="none" stroke="#0080FF" stroke-width="1" />
<circle cx="5" cy="5" r="2" fill="none" stroke="#0080FF" stroke-width="1" />
</pattern>
<pattern id="aknuz" patternUnits="userSpaceOnUse" width="10" height="10">
<path d="M 0,7.5 l 10,-5 M 0,2.5 l 10,-5 M 0,12.5 l 10,-5" stroke-width="2" shape-rendering="auto" stroke="#0080FF" stroke-linecap="square" />
</pattern>
<pattern id="vhjmw" patternUnits="userSpaceOnUse" width="32" height="32">
<path d="M 0,32 l 32,-32 M -8,8 l 16,-16 M 24,40 l 16,-16" stroke-width="16" shape-rendering="auto" stroke="#0080FF" stroke-linecap="square" />
</pattern>
<pattern id="mrist" patternUnits="userSpaceOnUse" width="9" height="5.196152422706632">
<path d="M 3,0 l 3,0 l 1.5,2.598076211353316 l -1.5,2.598076211353316 l -3,0 l -1.5,-2.598076211353316 Z M 0,2.598076211353316 l 1.5,0 M 9,2.598076211353316 l -1.5,0" fill="transparent" stroke="#0080FF" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="mbbhg" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 2, 0 l 0, 4" stroke-width="1" shape-rendering="crispEdges" stroke="#0080FF" stroke-linecap="square" />
<path d="M 0,2 l 4,0" stroke-width="1" shape-rendering="crispEdges" stroke="#0080FF" stroke-linecap="square" />
</pattern>
<pattern id="hfuqj" patternUnits="userSpaceOnUse" width="15" height="8.660254037844386">
<path d="M 5,0 l 5,0 l 2.5,4.330127018922193 l -2.5,4.330127018922193 l -5,0 l -2.5,-4.330127018922193 Z M 0,4.330127018922193 l 2.5,0 M 15,4.330127018922193 l -2.5,0" fill="#0080FF" stroke="rgba(0,0,0,0)" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="jrwrh" patternUnits="userSpaceOnUse" width="4" height="4">
<circle cx="2" cy="2" r="2" fill="#0080FF" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="bncvb" patternUnits="userSpaceOnUse" width="10" height="10">
<circle cx="5" cy="5" r="2" fill="#0080FF" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="0" r="2" fill="#0080FF" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="10" r="2" fill="#0080FF" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="0" r="2" fill="#0080FF" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="10" r="2" fill="#0080FF" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="upgob" patternUnits="userSpaceOnUse" width="5" height="5">
<path d="M 1.25,3.75l1.25,-2.5l1.25,2.5" fill="transparent" stroke="#0080FF" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="krnlo" patternUnits="userSpaceOnUse" width="12" height="6.928203230275509">
<path d="M 4,0 l 4,0 l 2,3.4641016151377544 l -2,3.4641016151377544 l -4,0 l -2,-3.4641016151377544 Z M 0,3.4641016151377544 l 2,0 M 12,3.4641016151377544 l -2,0" fill="transparent" stroke="#0080FF" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="sgkgd" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 0,4 l 4,-4 M -1,1 l 2,-2 M 3,5 l 2,-2" stroke-width="1" shape-rendering="auto" stroke="#FA8500" stroke-linecap="square" />
</pattern>
<pattern id="mfhbm" patternUnits="userSpaceOnUse" width="10" height="10">
<path d="M 0,7.5 l 10,-5 M 0,2.5 l 10,-5 M 0,12.5 l 10,-5" stroke-width="2" shape-rendering="auto" stroke="#FA8500" stroke-linecap="square" />
</pattern>
<pattern id="skgis" patternUnits="userSpaceOnUse" width="32" height="32">
<path d="M 0,32 l 32,-32 M -8,8 l 16,-16 M 24,40 l 16,-16" stroke-width="16" shape-rendering="auto" stroke="#FA8500" stroke-linecap="square" />
</pattern>
<pattern id="oswbs" patternUnits="userSpaceOnUse" width="9" height="5.196152422706632">
<path d="M 3,0 l 3,0 l 1.5,2.598076211353316 l -1.5,2.598076211353316 l -3,0 l -1.5,-2.598076211353316 Z M 0,2.598076211353316 l 1.5,0 M 9,2.598076211353316 l -1.5,0" fill="transparent" stroke="#FA8500" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="lsouj" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 2, 0 l 0, 4" stroke-width="1" shape-rendering="crispEdges" stroke="#FA8500" stroke-linecap="square" />
<path d="M 0,2 l 4,0" stroke-width="1" shape-rendering="crispEdges" stroke="#FA8500" stroke-linecap="square" />
</pattern>
<pattern id="bvkgt" patternUnits="userSpaceOnUse" width="15" height="8.660254037844386">
<path d="M 5,0 l 5,0 l 2.5,4.330127018922193 l -2.5,4.330127018922193 l -5,0 l -2.5,-4.330127018922193 Z M 0,4.330127018922193 l 2.5,0 M 15,4.330127018922193 l -2.5,0" fill="#FA8500" stroke="rgba(0,0,0,0)" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="fgivr" patternUnits="userSpaceOnUse" width="4" height="4">
<circle cx="2" cy="2" r="2" fill="#FA8500" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="yfcyp" patternUnits="userSpaceOnUse" width="10" height="10">
<circle cx="5" cy="5" r="2" fill="#00FA85" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="0" r="2" fill="#00FA85" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="10" r="2" fill="#00FA85" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="0" r="2" fill="#00FA85" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="10" r="2" fill="#00FA85" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="gmbhz" patternUnits="userSpaceOnUse" width="5" height="5">
<path d="M 1.25,3.75l1.25,-2.5l1.25,2.5" fill="transparent" stroke="#00FA85" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="yfydn" patternUnits="userSpaceOnUse" width="12" height="6.928203230275509">
<path d="M 4,0 l 4,0 l 2,3.4641016151377544 l -2,3.4641016151377544 l -4,0 l -2,-3.4641016151377544 Z M 0,3.4641016151377544 l 2,0 M 12,3.4641016151377544 l -2,0" fill="transparent" stroke="#00FA85" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="gxqls" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 0,4 l 4,-4 M -1,1 l 2,-2 M 3,5 l 2,-2" stroke-width="1" shape-rendering="auto" stroke="#00FA85" stroke-linecap="square" />
</pattern>
<pattern id="cfbwj" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="2" fill="none" stroke="#00FA85" stroke-width="1" />
<circle cx="0" cy="0" r="2" fill="none" stroke="#00FA85" stroke-width="1" />
<circle cx="0" cy="5" r="2" fill="none" stroke="#00FA85" stroke-width="1" />
<circle cx="5" cy="0" r="2" fill="none" stroke="#00FA85" stroke-width="1" />
<circle cx="5" cy="5" r="2" fill="none" stroke="#00FA85" stroke-width="1" />
</pattern>
<pattern id="fsgnv" patternUnits="userSpaceOnUse" width="10" height="10">
<path d="M 0,7.5 l 10,-5 M 0,2.5 l 10,-5 M 0,12.5 l 10,-5" stroke-width="2" shape-rendering="auto" stroke="#00FA85" stroke-linecap="square" />
</pattern>
<pattern id="vakgp" patternUnits="userSpaceOnUse" width="32" height="32">
<path d="M 0,32 l 32,-32 M -8,8 l 16,-16 M 24,40 l 16,-16" stroke-width="16" shape-rendering="auto" stroke="#00FA85" stroke-linecap="square" />
</pattern>
<pattern id="oaprx" patternUnits="userSpaceOnUse" width="9" height="5.196152422706632">
<path d="M 3,0 l 3,0 l 1.5,2.598076211353316 l -1.5,2.598076211353316 l -3,0 l -1.5,-2.598076211353316 Z M 0,2.598076211353316 l 1.5,0 M 9,2.598076211353316 l -1.5,0" fill="transparent" stroke="#00FA85" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="fxicb" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 2, 0 l 0, 4" stroke-width="1" shape-rendering="crispEdges" stroke="#00FA85" stroke-linecap="square" />
<path d="M 0,2 l 4,0" stroke-width="1" shape-rendering="crispEdges" stroke="#00FA85" stroke-linecap="square" />
</pattern>
<pattern id="hjala" patternUnits="userSpaceOnUse" width="15" height="8.660254037844386">
<path d="M 5,0 l 5,0 l 2.5,4.330127018922193 l -2.5,4.330127018922193 l -5,0 l -2.5,-4.330127018922193 Z M 0,4.330127018922193 l 2.5,0 M 15,4.330127018922193 l -2.5,0" fill="#00FA85" stroke="rgba(0,0,0,0)" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="xjxrr" patternUnits="userSpaceOnUse" width="4" height="4">
<circle cx="2" cy="2" r="2" fill="#BB00FA" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="yzmaq" patternUnits="userSpaceOnUse" width="10" height="10">
<circle cx="5" cy="5" r="2" fill="#BB00FA" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="0" r="2" fill="#BB00FA" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="10" r="2" fill="#BB00FA" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="0" r="2" fill="#BB00FA" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="10" r="2" fill="#BB00FA" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="dguls" patternUnits="userSpaceOnUse" width="5" height="5">
<path d="M 1.25,3.75l1.25,-2.5l1.25,2.5" fill="transparent" stroke="#BB00FA" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="hltax" patternUnits="userSpaceOnUse" width="12" height="6.928203230275509">
<path d="M 4,0 l 4,0 l 2,3.4641016151377544 l -2,3.4641016151377544 l -4,0 l -2,-3.4641016151377544 Z M 0,3.4641016151377544 l 2,0 M 12,3.4641016151377544 l -2,0" fill="transparent" stroke="#BB00FA" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="czaje" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="2" fill="none" stroke="#BB00FA" stroke-width="1" />
<circle cx="0" cy="0" r="2" fill="none" stroke="#BB00FA" stroke-width="1" />
<circle cx="0" cy="5" r="2" fill="none" stroke="#BB00FA" stroke-width="1" />
<circle cx="5" cy="0" r="2" fill="none" stroke="#BB00FA" stroke-width="1" />
<circle cx="5" cy="5" r="2" fill="none" stroke="#BB00FA" stroke-width="1" />
</pattern>
<pattern id="julqj" patternUnits="userSpaceOnUse" width="9" height="5.196152422706632">
<path d="M 3,0 l 3,0 l 1.5,2.598076211353316 l -1.5,2.598076211353316 l -3,0 l -1.5,-2.598076211353316 Z M 0,2.598076211353316 l 1.5,0 M 9,2.598076211353316 l -1.5,0" fill="transparent" stroke="#BB00FA" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="gic" patternUnits="userSpaceOnUse" width="15" height="8.660254037844386">
<path d="M 5,0 l 5,0 l 2.5,4.330127018922193 l -2.5,4.330127018922193 l -5,0 l -2.5,-4.330127018922193 Z M 0,4.330127018922193 l 2.5,0 M 15,4.330127018922193 l -2.5,0" fill="#FAD900" stroke="rgba(0,0,0,0)" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="ohqxb" patternUnits="userSpaceOnUse" width="4" height="4">
<circle cx="2" cy="2" r="2" fill="#FAD900" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="bvwec" patternUnits="userSpaceOnUse" width="10" height="10">
<circle cx="5" cy="5" r="2" fill="#FAD900" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="0" r="2" fill="#FAD900" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="10" r="2" fill="#FAD900" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="0" r="2" fill="#FAD900" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="10" r="2" fill="#FAD900" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="jdlwh" patternUnits="userSpaceOnUse" width="5" height="5">
<path d="M 1.25,3.75l1.25,-2.5l1.25,2.5" fill="transparent" stroke="#FAD900" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="wwcdm" patternUnits="userSpaceOnUse" width="12" height="6.928203230275509">
<path d="M 4,0 l 4,0 l 2,3.4641016151377544 l -2,3.4641016151377544 l -4,0 l -2,-3.4641016151377544 Z M 0,3.4641016151377544 l 2,0 M 12,3.4641016151377544 l -2,0" fill="transparent" stroke="#FAD900" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="wcyvp" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 0,4 l 4,-4 M -1,1 l 2,-2 M 3,5 l 2,-2" stroke-width="1" shape-rendering="auto" stroke="#FAD900" stroke-linecap="square" />
</pattern>
<pattern id="ljnqw" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="2" fill="none" stroke="#FF0080" stroke-width="1" />
<circle cx="0" cy="0" r="2" fill="none" stroke="#FF0080" stroke-width="1" />
<circle cx="0" cy="5" r="2" fill="none" stroke="#FF0080" stroke-width="1" />
<circle cx="5" cy="0" r="2" fill="none" stroke="#FF0080" stroke-width="1" />
<circle cx="5" cy="5" r="2" fill="none" stroke="#FF0080" stroke-width="1" />
</pattern>
<pattern id="xcdur" patternUnits="userSpaceOnUse" width="10" height="10">
<path d="M 0,7.5 l 10,-5 M 0,2.5 l 10,-5 M 0,12.5 l 10,-5" stroke-width="2" shape-rendering="auto" stroke="#FF0080" stroke-linecap="square" />
</pattern>
<pattern id="nxkhz" patternUnits="userSpaceOnUse" width="32" height="32">
<path d="M 0,32 l 32,-32 M -8,8 l 16,-16 M 24,40 l 16,-16" stroke-width="16" shape-rendering="auto" stroke="#FF0080" stroke-linecap="square" />
</pattern>
<pattern id="cpnap" patternUnits="userSpaceOnUse" width="9" height="5.196152422706632">
<path d="M 3,0 l 3,0 l 1.5,2.598076211353316 l -1.5,2.598076211353316 l -3,0 l -1.5,-2.598076211353316 Z M 0,2.598076211353316 l 1.5,0 M 9,2.598076211353316 l -1.5,0" fill="transparent" stroke="#FF0080" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="lhupq" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 2, 0 l 0, 4" stroke-width="1" shape-rendering="crispEdges" stroke="#FF0080" stroke-linecap="square" />
<path d="M 0,2 l 4,0" stroke-width="1" shape-rendering="crispEdges" stroke="#FF0080" stroke-linecap="square" />
</pattern>
<pattern id="aaqtb" patternUnits="userSpaceOnUse" width="4" height="4">
<circle cx="2" cy="2" r="2" fill="#8500FA" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="lufrm" patternUnits="userSpaceOnUse" width="10" height="10">
<circle cx="5" cy="5" r="2" fill="#8500FA" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="0" r="2" fill="#8500FA" stroke="#343434" stroke-width="0" />
<circle cx="0" cy="10" r="2" fill="#8500FA" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="0" r="2" fill="#8500FA" stroke="#343434" stroke-width="0" />
<circle cx="10" cy="10" r="2" fill="#8500FA" stroke="#343434" stroke-width="0" />
</pattern>
<pattern id="azwdr" patternUnits="userSpaceOnUse" width="5" height="5">
<path d="M 1.25,3.75l1.25,-2.5l1.25,2.5" fill="transparent" stroke="#8500FA" stroke-width="1" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="yffat" patternUnits="userSpaceOnUse" width="12" height="6.928203230275509">
<path d="M 4,0 l 4,0 l 2,3.4641016151377544 l -2,3.4641016151377544 l -4,0 l -2,-3.4641016151377544 Z M 0,3.4641016151377544 l 2,0 M 12,3.4641016151377544 l -2,0" fill="transparent" stroke="#8500FA" stroke-width="2" stroke-linecap="square" shape-rendering="auto" />
</pattern>
<pattern id="hslpy" patternUnits="userSpaceOnUse" width="4" height="4">
<path d="M 0,4 l 4,-4 M -1,1 l 2,-2 M 3,5 l 2,-2" stroke-width="1" shape-rendering="auto" stroke="#8500FA" stroke-linecap="square" />
</pattern>
<pattern id="zsvlo" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="2" fill="none" stroke="#8500FA" stroke-width="1" />
<circle cx="0" cy="0" r="2" fill="none" stroke="#8500FA" stroke-width="1" />
<circle cx="0" cy="5" r="2" fill="none" stroke="#8500FA" stroke-width="1" />
<circle cx="5" cy="0" r="2" fill="none" stroke="#8500FA" stroke-width="1" />
<circle cx="5" cy="5" r="2" fill="none" stroke="#8500FA" stroke-width="1" />
</pattern>
<pattern id="muhub" patternUnits="userSpaceOnUse" width="10" height="10">
<path d="M 0,7.5 l 10,-5 M 0,2.5 l 10,-5 M 0,12.5 l 10,-5" stroke-width="2" shape-rendering="auto" stroke="#8500FA" stroke-linecap="square" />
</pattern>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 27 KiB

82
www/canvas_patterns.json Normal file
View file

@ -0,0 +1,82 @@
{
"1": "url(#tprke)",
"2": "url(#szylj)",
"3": "url(#ooumd)",
"4": "url(#fheyp)",
"5": "url(#ssscs)",
"6": "url(#htafw)",
"7": "url(#mwmpe)",
"8": "url(#ipzlm)",
"9": "url(#ryfgx)",
"10": "url(#gktcz)",
"11": "url(#nkuai)",
"13": "url(#tnder)",
"14": "url(#hfgks)",
"15": "url(#mrlhp)",
"16": "url(#cmsda)",
"17": "url(#wsewh)",
"18": "url(#htoli)",
"19": "url(#aoggk)",
"20": "url(#mfcrf)",
"21": "url(#igtrq)",
"22": "url(#wgyom)",
"23": "url(#qxsnm)",
"24": "url(#gmgmx)",
"25": "url(#qubbv)",
"27": "url(#ttene)",
"28": "url(#fazqm)",
"31": "url(#nfcwz)",
"32": "url(#rwmct)",
"33": "url(#iiitd)",
"34": "url(#eajyr)",
"35": "url(#aknuz)",
"36": "url(#vhjmw)",
"37": "url(#mrist)",
"38": "url(#mbbhg)",
"39": "url(#hfuqj)",
"40": "url(#jrwrh)",
"41": "url(#bncvb)",
"42": "url(#upgob)",
"43": "url(#krnlo)",
"44": "url(#sgkgd)",
"46": "url(#mfhbm)",
"47": "url(#skgis)",
"48": "url(#oswbs)",
"49": "url(#lsouj)",
"50": "url(#bvkgt)",
"51": "url(#fgivr)",
"52": "url(#yfcyp)",
"53": "url(#gmbhz)",
"54": "url(#yfydn)",
"55": "url(#gxqls)",
"56": "url(#cfbwj)",
"57": "url(#fsgnv)",
"58": "url(#vakgp)",
"59": "url(#oaprx)",
"60": "url(#fxicb)",
"61": "url(#hjala)",
"62": "url(#xjxrr)",
"63": "url(#yzmaq)",
"64": "url(#dguls)",
"65": "url(#hltax)",
"67": "url(#czaje)",
"70": "url(#julqj)",
"72": "url(#gic)",
"73": "url(#ohqxb)",
"74": "url(#bvwec)",
"75": "url(#jdlwh)",
"76": "url(#wwcdm)",
"77": "url(#wcyvp)",
"78": "url(#ljnqw)",
"79": "url(#xcdur)",
"80": "url(#nxkhz)",
"81": "url(#cpnap)",
"82": "url(#lhupq)",
"84": "url(#aaqtb)",
"85": "url(#lufrm)",
"86": "url(#azwdr)",
"87": "url(#yffat)",
"88": "url(#hslpy)",
"89": "url(#zsvlo)",
"90": "url(#muhub)"
}

155
www/coco.css Normal file
View file

@ -0,0 +1,155 @@
body{
font-family:sans-serif;
background: darkblue;
margin:0;
}
#svgCanvas{
width:100vw;
height:100vh;
}
.catNav{
position:absolute;
top:0;
color:white;
bottom:0;
overflow-y: auto;
padding: 10px;
}
#catNav {
left: 0;
}
#catNav2 {
right:0;
}
.catNav ul{
list-style:none;
padding:0px;
margin: 30px 0px;
border-width: 3px;
}
#catNav ul {
border-left-style: solid;
}
#catNav2 ul {
border-right-style: solid;
}
.catNav li{
padding:2px;
}
.catNav li:hover{
cursor:pointer;
text-decoration:underline;
}
#catNav li{
transform: rotate(-30deg);
transform-origin: top left;
padding-left: 5px;
}
#catNav2 li{
transform: rotate(30deg);
text-align:right;
transform-origin: top right;
padding-right: 5px;
}
g{
cursor:grab;
}
g:active{
cursor:grabbing;
}
svg text{
fill:white;
font-size: 40pt;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.buttons{
position:absolute;
top: 10px;
text-align:center;
width: 300px;
left: calc(50% - 150px);
}
.buttons.buttons-inline{
position:static;
margin: 10px;
width:100%;
}
.buttons span, .buttons label{
color:white;
cursor:pointer;
}
.buttons span:hover{
text-decoration:underline;
}
.buttons #sceneLabel{
display:block;
}
body.hideImages svg image{
display:none;
}
body.hideLabels svg text{
display:none;
}
.supercategory-person{
border-color:rgb(250,8,0);
}
.supercategory-vehicle{
border-color:rgb(128,255,0);
}
.supercategory-outdoor{
border-color:rgb(0,255,255);
}
.supercategory-animal{
border-color:rgb(255,0,255);
}
.supercategory-accessory{
border-color:rgb(255,255,0);
}
.supercategory-sports{
border-color:rgb(0,128,255);
}
.supercategory-kitchen{
border-color:rgb(250,133,0);
}
.supercategory-furniture{
border-color:rgb(187,0,250);
}
.supercategory-food{
border-color:rgb(0,250,133);
}
.supercategory-electronic{
border-color:rgb(250,217,0);
}
.supercategory-appliance{
border-color:rgb(255,0,128);
}
.supercategory-indoor{
border-color:rgb(133,0,250);
}
#save{margin: 5px;}
#saved{
text-align: center;
}
#saved svg{
width: 30vw;
height: 20vw;
margin: 10px;
border:solid 1px white;
}

View file

@ -58,9 +58,9 @@ function getColoredTexture(i, color) {
case 6: case 6:
return textures.paths().d("hexagons").size(5).strokeWidth(2).stroke("rgba(0,0,0,0)").fill(color); return textures.paths().d("hexagons").size(5).strokeWidth(2).stroke("rgba(0,0,0,0)").fill(color);
case 7: case 7:
return textures.circles().size(4).stroke(color); return textures.circles().size(4).fill(color);
case 8: case 8:
return textures.circles().thicker().complement().stroke(color); return textures.circles().thicker().complement().fill(color);
case 9: case 9:
return textures.paths().d("caps").lighter().thicker().size(5).stroke(color); return textures.paths().d("caps").lighter().thicker().size(5).stroke(color);
case 10: case 10:
@ -70,17 +70,18 @@ function getColoredTexture(i, color) {
}; };
const categoryColors = { const categoryColors = {
"person": "#f00", "person": "#FA0800",
"vehicle": "#0f0", "vehicle": "#80FF00",
"outdoor": "#006", "outdoor": "#00FFFF",
"animal": "#ff0", "animal": "#FF00FF",
"food": "#0ff", "accessory": "#FFFF00",
"furniture": "#f0f", "food": "#00FA85",
"indoor": "#fff", "furniture": "#BB00FA",
"electronic": "#390", "indoor": "#8500FA",
"kitchen": "#930", "electronic": "#FAD900",
"accessory": "#f90", "kitchen": "#FA8500",
"sports": "#f09", "sports": "#0080FF",
"appliance": "#FF0080",
} }
function getColorForSuperCategory(name) { function getColorForSuperCategory(name) {
@ -103,8 +104,21 @@ function getTextureForCategory(id) {
class CocoCanvas { class CocoCanvas {
start(){ start(){
this.catNavEl = document.getElementById('catNav'); this.catNavEl = document.getElementById('catNav');
this.catNav2El = document.getElementById('catNav2');
this.canvas = document.getElementById('svgCanvas'); 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.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() { loadNav() {
let r = new Request('/categories.json'); let r = new Request('/categories.json');
@ -121,40 +135,63 @@ class CocoCanvas {
} }
buildNav(categories) { buildNav(categories) {
let ulEl = crel('ul'); let lastSuperCat = null;
let supercategories = []
let supercategoriesEls = {}
// create menu's per supercategory, divide these over left & right
for(let cat of categories) { for(let cat of categories) {
ulEl.appendChild( if(supercategories.indexOf(cat['supercategory']) < 0) {
crel('li', { 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'], 'id': 'category-' + cat['id'],
// 'class': 'supercategory-' + cat['supercategory'],
'on': { 'on': {
'click': (e) => { 'click': (e) => {
this.requestAnnotation(cat['id']); this.requestAnnotation(cat['id']);
} }
} }
}, cat['name']) }, cat['name']));
);
}
this.catNavEl.appendChild(ulEl);
let defsEl = document.createElementNS("http://www.w3.org/2000/svg", 'defs'); }
this.defsEl = document.createElementNS("http://www.w3.org/2000/svg", 'defs');
for(let cat of categories) { for(let cat of categories) {
let texture = getTextureForCategory(cat['id']); let texture = getTextureForCategory(cat['id']);
let sel = new dom(); let sel = new dom();
texture(sel); texture(sel);
defsEl.innerHTML += sel.toString(); this.defsEl.innerHTML += sel.toString();
} }
this.canvas.appendChild(defsEl); this.canvas.appendChild(this.defsEl);
} }
requestAnnotation(category_id) { requestAnnotation(category_id) {
let r = new Request(`/annotation.json?category=${category_id}&normalise=100`); let r = new Request(`/annotation.json?category=${category_id}&normalise=100`);
fetch(r) return fetch(r)
.then(response => response.json()) .then(response => response.json())
.then(annotation => { .then(annotation => {
this.addAnnotationAsShape(annotation); this.addAnnotationAsShape(annotation);
}).catch(function(e){ }).catch(function(e){
console.error(e); console.error(e);
});; });
} }
pointsToD(points) { pointsToD(points) {
@ -187,14 +224,15 @@ class CocoCanvas {
'transform': `translate(${x}, ${y})`, 'transform': `translate(${x}, ${y})`,
'on': { 'on': {
'mousedown': function(downE) { 'mousedown': function(downE) {
console.log(downE); // after clicking the element should be the top element
console.log(this) // and the last svg element is drawn on top.
annEl.parentNode.appendChild(annEl);
// console.log(downE);
// console.log(this)
let offset = this.getMousePosition(downE); 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 // Get initial translation amount
console.log(annEl.transform.baseVal); // console.log(annEl.transform.baseVal);
let transform = annEl.transform.baseVal.getItem(0); let transform = annEl.transform.baseVal.getItem(0);
offset.x -= transform.matrix.e; offset.x -= transform.matrix.e;
offset.y -= transform.matrix.f; offset.y -= transform.matrix.f;
@ -225,10 +263,112 @@ class CocoCanvas {
}); });
annEl.appendChild(pathEl); annEl.appendChild(pathEl);
} }
console.log(annEl);
this.canvas.appendChild(annEl); 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(); let cc = new CocoCanvas();
cc.start(); cc.start();
//for testing only:
//cc.requestAnnotation(1).then((e) => cc.convertToImages());

View file

@ -1,43 +1,12 @@
<html> <html>
<head> <head>
<meta charset='utf-8'> <meta charset='utf-8'>
<style type='text/css'> <link rel="stylesheet" type="text/css" href="coco.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> </head>
<body> <body>
<nav id='catNav'> <nav id='catNav' class='catNav'>
</nav>
<nav id='catNav2' class='catNav'>
</nav> </nav>
<svg <svg
id='svgCanvas' id='svgCanvas'
@ -47,6 +16,17 @@ cursor:grabbing;
viewBox="0 0 1000 1000"> viewBox="0 0 1000 1000">
</svg> </svg>
<div class='buttons'>
<label id='scenelabel'>Scene <select id='scene'>
<!-- <option>-</option> -->
<option value="1">View from a drone</option>
<option value="2">Self driving car</option>
<option value="3">Product placement detector</option>
</select></label>
<label id='convert'><input type='checkbox' id='loadImages'> images</label>
<label id='labelcheck'><input type='checkbox' id='loadLabels'> labels</label>
<label id='savewrap'><input type='submit' id='save' disabled='disabled' value='save'></label>
</div>
<!-- <script src="svg-inject.min.js"></script> --> <!-- <script src="svg-inject.min.js"></script> -->
<script src="textures.js"></script> <script src="textures.js"></script>

40
www/saved.html Normal file
View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>Plotting Data: COCO drawings</title>
<link rel="stylesheet" type="text/css" href="coco.css">
</head>
<body class='hideImages'>
<div class='buttons buttons-inline'>
<label id='convert'><input type='checkbox' id='loadImages'> images</label>
<label id='labelcheck'><input type='checkbox' id='loadLabels'> labels</label>
</div>
<div id='saved'>
{images}
</div>
<script type="text/javascript">
let loadImagesEl = document.getElementById('loadImages');
let loadLabelsEl = document.getElementById('loadLabels');
let setImageClass = function() {
if(loadImagesEl.checked) {
document.body.classList.remove('hideImages');
} else {
document.body.classList.add('hideImages');
}
}
let setLabelClass = function() {
if(loadLabelsEl.checked) {
document.body.classList.remove('hideLabels');
} else {
document.body.classList.add('hideLabels');
}
}
loadImagesEl.addEventListener('change', (e) => setImageClass());
loadLabelsEl.addEventListener('change', (e) => setLabelClass());
setImageClass();
setLabelClass();
</script>
</body>
</html>

2
www/saved/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore