Save path into image & fetch latest image
This commit is contained in:
parent
6c908a572f
commit
dd09207066
3 changed files with 156 additions and 26 deletions
103
server.py
103
server.py
|
@ -1,11 +1,15 @@
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
import tornado.ioloop
|
import tornado.ioloop
|
||||||
import tornado.web
|
import tornado.web
|
||||||
import tornado.websocket
|
import tornado.websocket
|
||||||
import logging
|
|
||||||
import coloredlogs
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import coloredlogs
|
||||||
|
import glob
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("drawing")
|
logger = logging.getLogger("drawing")
|
||||||
|
@ -23,11 +27,38 @@ argParser.add_argument(
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
generated_image_dir = os.path.join('www','generated')
|
||||||
|
|
||||||
|
def strokes2D(strokes):
|
||||||
|
# strokes to a d attribute for a path
|
||||||
|
d = "";
|
||||||
|
last_stroke = None;
|
||||||
|
cmd = "";
|
||||||
|
for stroke in strokes:
|
||||||
|
if not last_stroke:
|
||||||
|
d += f"M{stroke[0]},{stroke[1]} "
|
||||||
|
cmd = 'M'
|
||||||
|
else:
|
||||||
|
if last_stroke[2] == 1:
|
||||||
|
d += " m"
|
||||||
|
cmd = 'm'
|
||||||
|
elif cmd != 'l':
|
||||||
|
d+=' l '
|
||||||
|
cmd = 'l'
|
||||||
|
|
||||||
|
rel_stroke = [stroke[0] - last_stroke[0], stroke[1] - last_stroke[1]];
|
||||||
|
d += f"{rel_stroke[0]},{rel_stroke[1]} "
|
||||||
|
last_stroke = stroke;
|
||||||
|
return d;
|
||||||
|
|
||||||
|
|
||||||
class StaticFileWithHeaderHandler(tornado.web.StaticFileHandler):
|
class StaticFileWithHeaderHandler(tornado.web.StaticFileHandler):
|
||||||
def set_extra_headers(self, path):
|
def set_extra_headers(self, path):
|
||||||
"""For subclass to add extra headers to the response"""
|
"""For subclass to add extra headers to the response"""
|
||||||
if path[-5:] == '.html':
|
if path[-5:] == '.html':
|
||||||
self.set_header("Access-Control-Allow-Origin", "*")
|
self.set_header("Access-Control-Allow-Origin", "*")
|
||||||
|
if path[-4:] == '.svg':
|
||||||
|
self.set_header("Content-Type", "image/svg+xml")
|
||||||
|
|
||||||
class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
||||||
CORS_ORIGINS = ['localhost', '.mturk.com']
|
CORS_ORIGINS = ['localhost', '.mturk.com']
|
||||||
|
@ -43,20 +74,36 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
||||||
def open(self, p = None):
|
def open(self, p = None):
|
||||||
self.__class__.connections.add(self)
|
self.__class__.connections.add(self)
|
||||||
logger.info("New client connected")
|
logger.info("New client connected")
|
||||||
self.write_message("hello!")
|
self.strokes = []
|
||||||
|
# self.write_message("hello!")
|
||||||
|
|
||||||
# the client sent the message
|
# the client sent the message
|
||||||
def on_message(self, message):
|
def on_message(self, message):
|
||||||
logger.debug(f"recieve: {message}")
|
logger.debug(f"recieve: {message}")
|
||||||
try:
|
try:
|
||||||
msg = json.loads(message)
|
msg = json.loads(message)
|
||||||
|
# TODO: sanitize input: min/max, limit strokes
|
||||||
if msg['action'] == 'move':
|
if msg['action'] == 'move':
|
||||||
pass
|
# TODO: min/max input
|
||||||
|
point = [float(msg['direction'][0]),float(msg['direction'][1]), 0]
|
||||||
|
self.strokes.append(point)
|
||||||
|
|
||||||
elif msg['action'] == 'up':
|
elif msg['action'] == 'up':
|
||||||
logger.info(f'up: {msg}')
|
logger.info(f'up: {msg}')
|
||||||
|
point = [msg['direction'][0],msg['direction'][1], 1]
|
||||||
|
self.strokes.append(point)
|
||||||
|
|
||||||
elif msg['action'] == 'submit':
|
elif msg['action'] == 'submit':
|
||||||
logger.info(f'up: {msg}')
|
logger.info(f'up: {msg}')
|
||||||
self.write_message(json.dumps('submitted'))
|
id = self.submit_strokes()
|
||||||
|
if not id:
|
||||||
|
self.write_message(json.dumps('error'))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.write_message(json.dumps({
|
||||||
|
'action': 'submitted',
|
||||||
|
'msg': f"Submission ok, please refer to your submission as: {id}"
|
||||||
|
}))
|
||||||
elif msg['action'] == 'down':
|
elif msg['action'] == 'down':
|
||||||
# not used, implicit in move?
|
# not used, implicit in move?
|
||||||
pass
|
pass
|
||||||
|
@ -72,17 +119,54 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
||||||
def on_close(self):
|
def on_close(self):
|
||||||
self.__class__.rmConnection(self)
|
self.__class__.rmConnection(self)
|
||||||
logger.info("Client disconnected")
|
logger.info("Client disconnected")
|
||||||
|
|
||||||
|
def submit_strokes(self):
|
||||||
|
if len(self.strokes) < 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
d = strokes2D(self.strokes)
|
||||||
|
svg = f"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg viewBox="0 0 600 600"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
version="1.1"
|
||||||
|
>
|
||||||
|
<path d="{d}" style="stroke:black;stroke-width:2;fill:none;" />
|
||||||
|
</svg>
|
||||||
|
"""
|
||||||
|
|
||||||
|
id = uuid.uuid4().hex
|
||||||
|
|
||||||
|
filename = os.path.join(generated_image_dir , id+'.svg')
|
||||||
|
with open(filename, 'w') as fp:
|
||||||
|
logger.info(f"Wrote {filename}")
|
||||||
|
fp.write(svg)
|
||||||
|
|
||||||
|
return id
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def rmConnection(cls, client):
|
def rmConnection(cls, client):
|
||||||
if client not in cls.connections:
|
if client not in cls.connections:
|
||||||
return
|
return
|
||||||
cls.connections.remove(client)
|
cls.connections.remove(client)
|
||||||
|
|
||||||
|
|
||||||
|
class LatestImageHandler(tornado.web.RequestHandler):
|
||||||
|
def get(self):
|
||||||
|
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||||
|
self.set_header("Content-Type", "image/svg+xml")
|
||||||
|
|
||||||
|
list_of_files = glob.glob(os.path.join(generated_image_dir,'*.svg'))
|
||||||
|
latest_file = max(list_of_files, key=os.path.getctime)
|
||||||
|
with open(latest_file, 'r') as fp:
|
||||||
|
self.write(fp.read())
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
args = argParser.parse_args()
|
args = argParser.parse_args()
|
||||||
print(logger.level)
|
|
||||||
coloredlogs.install(
|
coloredlogs.install(
|
||||||
level=logging.DEBUG if args.verbose else logging.INFO,
|
level=logging.DEBUG if args.verbose else logging.INFO,
|
||||||
)
|
)
|
||||||
|
@ -98,6 +182,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
application = tornado.web.Application([
|
application = tornado.web.Application([
|
||||||
(r"/ws(.*)", WebSocketHandler),
|
(r"/ws(.*)", WebSocketHandler),
|
||||||
|
(r"/latest.svg", LatestImageHandler), # TODO: have js request the right image, based on a 'start' button. This way we can trace the history of a drawing
|
||||||
(r"/(.*)", StaticFileWithHeaderHandler,
|
(r"/(.*)", StaticFileWithHeaderHandler,
|
||||||
{"path": 'www', "default_filename": 'index.html'}),
|
{"path": 'www', "default_filename": 'index.html'}),
|
||||||
], debug=True)
|
], debug=True)
|
||||||
|
|
0
www/generated/.gitignore
vendored
Normal file
0
www/generated/.gitignore
vendored
Normal file
|
@ -4,10 +4,14 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>MT Request: draw over the image</title>
|
<title>MT Request: draw over the image</title>
|
||||||
<style media="screen">
|
<style media="screen">
|
||||||
svg{
|
#sample, svg{
|
||||||
height:600px;
|
position:absolute;
|
||||||
width:600px;
|
top: 0;
|
||||||
border:solid 1px;
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right:0;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
path {
|
path {
|
||||||
|
@ -15,21 +19,45 @@
|
||||||
stroke: red;
|
stroke: red;
|
||||||
stroke-width: 2px;
|
stroke-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.submitted path{
|
||||||
|
stroke:darkgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.submitted .buttons{
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper {
|
||||||
|
height: 600px;
|
||||||
|
width: 600px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id='interface'>
|
<div id='interface'>
|
||||||
<img src="" id='sample'>
|
<div id='wrapper'>
|
||||||
|
<img src="/latest.svg" id='sample'>
|
||||||
<svg id="canvas">
|
<svg id="canvas">
|
||||||
<path d="" id="stroke">
|
<path d="" id="stroke" />
|
||||||
</svg>
|
</svg>
|
||||||
<button id='submit'>Submit</button>
|
</div>
|
||||||
|
<div class='buttons'>
|
||||||
|
<button id='submit'>Submit</button>
|
||||||
|
<button id='reset'>Reset</button>
|
||||||
|
</div>
|
||||||
|
<div id='message'></div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
let url = window.location.origin.replace('http', 'ws') +'/ws';
|
let url = window.location.origin.replace('http', 'ws') +'/ws';
|
||||||
let svgEl = document.getElementById("canvas");
|
let svgEl = document.getElementById("canvas");
|
||||||
let strokeEl = document.getElementById('stroke');
|
let strokeEl = document.getElementById('stroke');
|
||||||
let submitEl = document.getElementById('submit');
|
let submitEl = document.getElementById('submit');
|
||||||
|
let resetEl = document.getElementById('reset');
|
||||||
|
let messageEl = document.getElementById('message');
|
||||||
|
|
||||||
let strokes = [];
|
let strokes = [];
|
||||||
let isDrawing = false;
|
let isDrawing = false;
|
||||||
let draw = function(e) {
|
let draw = function(e) {
|
||||||
|
@ -54,6 +82,10 @@
|
||||||
isDrawing = false;
|
isDrawing = false;
|
||||||
|
|
||||||
document.body.removeEventListener('mousemove', draw);
|
document.body.removeEventListener('mousemove', draw);
|
||||||
|
|
||||||
|
let pos = svgEl.getBoundingClientRect()
|
||||||
|
let x = e.x - pos['left'];
|
||||||
|
let y = e.y - pos['top'];
|
||||||
|
|
||||||
if(strokes.length > 0){
|
if(strokes.length > 0){
|
||||||
// mark point as last of stroke
|
// mark point as last of stroke
|
||||||
|
@ -61,6 +93,7 @@
|
||||||
}
|
}
|
||||||
socket.send(JSON.stringify({
|
socket.send(JSON.stringify({
|
||||||
'action': 'up',
|
'action': 'up',
|
||||||
|
'direction': [x, y]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -89,25 +122,37 @@
|
||||||
}
|
}
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let startDrawing = function(e){
|
||||||
|
isDrawing = true;
|
||||||
|
// start drawing
|
||||||
|
document.body.addEventListener('mousemove', draw);
|
||||||
|
|
||||||
|
};
|
||||||
|
let reset = function() {
|
||||||
|
strokes = [];
|
||||||
|
strokeEl.setAttribute('d', "");
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
'action': 'reset',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let socket = new WebSocket(url);
|
let socket = new WebSocket(url);
|
||||||
|
|
||||||
socket.addEventListener('message', function(e){
|
socket.addEventListener('message', function(e){
|
||||||
console.log('receive', e.data);
|
let msg = JSON.parse(e.data);
|
||||||
if(e.data == 'submitted') {
|
console.log('receive', msg);
|
||||||
// TODO close the interface
|
if(msg['action'] == 'submitted') {
|
||||||
|
document.body.classList.add('submitted');
|
||||||
|
messageEl.innerHTML = msg['msg'];
|
||||||
|
svgEl.removeEventListener('mousedown', startDrawing);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.addEventListener('mouseup', penup);
|
document.body.addEventListener('mouseup', penup);
|
||||||
svgEl.addEventListener('mousedown', function(e){
|
svgEl.addEventListener('mousedown', startDrawing);
|
||||||
isDrawing = true;
|
resetEl.addEventListener('click', reset);
|
||||||
|
|
||||||
// start drawing
|
|
||||||
document.body.addEventListener('mousemove', draw);
|
|
||||||
|
|
||||||
});
|
|
||||||
submitEl.addEventListener('click', function(e){
|
submitEl.addEventListener('click', function(e){
|
||||||
if(!strokes.length){
|
if(!strokes.length){
|
||||||
alert('please draw before submitting');
|
alert('please draw before submitting');
|
||||||
|
|
Loading…
Reference in a new issue