HIT now on https://guest.rubenvandeven.com:8888 and integrates in Mturk interface, TODO: sync timer

This commit is contained in:
Ruben van de Ven 2019-12-18 18:49:07 +01:00
parent 7abd748f18
commit dab6245792
10 changed files with 308 additions and 249 deletions

4
.gitignore vendored
View file

@ -3,4 +3,8 @@ __pycache__
node_modules
.pydevproject
.project
config.local.yml
hit_store.db
www/scans/*.svg
#scanimation/interfa

View file

@ -23,4 +23,4 @@ pyserial = "*"
python_version = "3.7"
[packages.a209065]
path = "./../../AxiDraw_API_v253r3"
path = "./../AxiDraw_API_v253r3"

119
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "89332bbe3bba257b19a9566bd526d79af55c9ea4671357aab103126c35b67797"
"sha256": "29b194a198b1d9a791ea020295d0ef6d5969969f9d606e2457444485e1bd6f7a"
},
"pipfile-spec": 6,
"requires": {
@ -17,29 +17,29 @@
},
"default": {
"a209065": {
"path": "./../../AxiDraw_API_v253r3"
"path": "./../AxiDraw_API_v253r3"
},
"boto3": {
"hashes": [
"sha256:2945d8246e4c8875e3ae4aafffd028330ef681efc33e1475c1a805ed42540403",
"sha256:3d84311ce0f2fba83810f0e840b08e2b66246fc80dbb0a71fb992116f47b95bd"
"sha256:5db4db12a017be2a0b07ec662584b7b9e8afa05894c8aaac145576a7c39a9886",
"sha256:7fb8bf70ff2403991c8ae7bc548333811be6e432c7665721364ea0c858eb824e"
],
"index": "pypi",
"version": "==1.10.6"
"version": "==1.10.41"
},
"botocore": {
"hashes": [
"sha256:ac1a5caa10e3c4452714b17e6f30f05b4b6e57e0c80b19c1f4d72b234edf6646",
"sha256:fa6b9e619423f3891e7c11b98f2183da8173e3fed995271e93fd4a712ef45777"
"sha256:5bfffa38ebba26ab462bb40e858702390fbe3ae2093a2177a8cde050ad6cb7e3",
"sha256:62ddff63be904781f97ced737836a66f5b72579af788c905cfdab32d2970e15e"
],
"version": "==1.13.6"
"version": "==1.13.41"
},
"certifi": {
"hashes": [
"sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50",
"sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"
"sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
"sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
],
"version": "==2019.9.11"
"version": "==2019.11.28"
},
"chardet": {
"hashes": [
@ -124,34 +124,34 @@
},
"lxml": {
"hashes": [
"sha256:02ca7bf899da57084041bb0f6095333e4d239948ad3169443f454add9f4e9cb4",
"sha256:096b82c5e0ea27ce9138bcbb205313343ee66a6e132f25c5ed67e2c8d960a1bc",
"sha256:0a920ff98cf1aac310470c644bc23b326402d3ef667ddafecb024e1713d485f1",
"sha256:1409b14bf83a7d729f92e2a7fbfe7ec929d4883ca071b06e95c539ceedb6497c",
"sha256:17cae1730a782858a6e2758fd20dd0ef7567916c47757b694a06ffafdec20046",
"sha256:17e3950add54c882e032527795c625929613adbd2ce5162b94667334458b5a36",
"sha256:1f4f214337f6ee5825bf90a65d04d70aab05526c08191ab888cb5149501923c5",
"sha256:2e8f77db25b0a96af679e64ff9bf9dddb27d379c9900c3272f3041c4d1327c9d",
"sha256:4dffd405390a45ecb95ab5ab1c1b847553c18b0ef8ed01e10c1c8b1a76452916",
"sha256:6b899931a5648862c7b88c795eddff7588fb585e81cecce20f8d9da16eff96e0",
"sha256:726c17f3e0d7a7200718c9a890ccfeab391c9133e363a577a44717c85c71db27",
"sha256:760c12276fee05c36f95f8040180abc7fbebb9e5011447a97cdc289b5d6ab6fc",
"sha256:796685d3969815a633827c818863ee199440696b0961e200b011d79b9394bbe7",
"sha256:891fe897b49abb7db470c55664b198b1095e4943b9f82b7dcab317a19116cd38",
"sha256:9277562f175d2334744ad297568677056861070399cec56ff06abbe2564d1232",
"sha256:a471628e20f03dcdfde00770eeaf9c77811f0c331c8805219ca7b87ac17576c5",
"sha256:a63b4fd3e2cabdcc9d918ed280bdde3e8e9641e04f3c59a2a3109644a07b9832",
"sha256:ae88588d687bd476be588010cbbe551e9c2872b816f2da8f01f6f1fda74e1ef0",
"sha256:b0b84408d4eabc6de9dd1e1e0bc63e7731e890c0b378a62443e5741cfd0ae90a",
"sha256:be78485e5d5f3684e875dab60f40cddace2f5b2a8f7fede412358ab3214c3a6f",
"sha256:c27eaed872185f047bb7f7da2d21a7d8913457678c9a100a50db6da890bc28b9",
"sha256:c7fccd08b14aa437fe096c71c645c0f9be0655a9b1a4b7cffc77bcb23b3d61d2",
"sha256:c81cb40bff373ab7a7446d6bbca0190bccc5be3448b47b51d729e37799bb5692",
"sha256:d11874b3c33ee441059464711cd365b89fa1a9cf19ae75b0c189b01fbf735b84",
"sha256:e9c028b5897901361d81a4718d1db217b716424a0283afe9d6735fe0caf70f79",
"sha256:fe489d486cd00b739be826e8c1be188ddb74c7a1ca784d93d06fda882a6a1681"
"sha256:00ac0d64949fef6b3693813fe636a2d56d97a5a49b5bbb86e4cc4cc50ebc9ea2",
"sha256:0571e607558665ed42e450d7bf0e2941d542c18e117b1ebbf0ba72f287ad841c",
"sha256:0e3f04a7615fdac0be5e18b2406529521d6dbdb0167d2a690ee328bef7807487",
"sha256:13cf89be53348d1c17b453867da68704802966c433b2bb4fa1f970daadd2ef70",
"sha256:217262fcf6a4c2e1c7cb1efa08bd9ebc432502abc6c255c4abab611e8be0d14d",
"sha256:223e544828f1955daaf4cefbb4853bc416b2ec3fd56d4f4204a8b17007c21250",
"sha256:277cb61fede2f95b9c61912fefb3d43fbd5f18bf18a14fae4911b67984486f5d",
"sha256:3213f753e8ae86c396e0e066866e64c6b04618e85c723b32ecb0909885211f74",
"sha256:4690984a4dee1033da0af6df0b7a6bde83f74e1c0c870623797cec77964de34d",
"sha256:4fcc472ef87f45c429d3b923b925704aa581f875d65bac80f8ab0c3296a63f78",
"sha256:61409bd745a265a742f2693e4600e4dbd45cc1daebe1d5fad6fcb22912d44145",
"sha256:678f1963f755c5d9f5f6968dded7b245dd1ece8cf53c1aa9d80e6734a8c7f41d",
"sha256:6c6d03549d4e2734133badb9ab1c05d9f0ef4bcd31d83e5d2b4747c85cfa21da",
"sha256:6e74d5f4d6ecd6942375c52ffcd35f4318a61a02328f6f1bd79fcb4ffedf969e",
"sha256:7b4fc7b1ecc987ca7aaf3f4f0e71bbfbd81aaabf87002558f5bc95da3a865bcd",
"sha256:7ed386a40e172ddf44c061ad74881d8622f791d9af0b6f5be20023029129bc85",
"sha256:8f54f0924d12c47a382c600c880770b5ebfc96c9fd94cf6f6bdc21caf6163ea7",
"sha256:ad9b81351fdc236bda538efa6879315448411a81186c836d4b80d6ca8217cdb9",
"sha256:bbd00e21ea17f7bcc58dccd13869d68441b32899e89cf6cfa90d624a9198ce85",
"sha256:c3c289762cc09735e2a8f8a49571d0e8b4f57ea831ea11558247b5bdea0ac4db",
"sha256:cf4650942de5e5685ad308e22bcafbccfe37c54aa7c0e30cd620c2ee5c93d336",
"sha256:cfcbc33c9c59c93776aa41ab02e55c288a042211708b72fdb518221cc803abc8",
"sha256:e301055deadfedbd80cf94f2f65ff23126b232b0d1fea28f332ce58137bcdb18",
"sha256:ebbfe24df7f7b5c6c7620702496b6419f6a9aa2fd7f005eb731cc80d7b4692b9",
"sha256:eff69ddbf3ad86375c344339371168640951c302450c5d3e9936e98d6459db06",
"sha256:f6ed60a62c5f1c44e789d2cf14009423cb1646b44a43e40a9cf6a21f077678a1"
],
"version": "==4.4.1"
"version": "==4.4.2"
},
"maxminddb": {
"hashes": [
@ -221,22 +221,20 @@
},
"pyyaml": {
"hashes": [
"sha256:0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9",
"sha256:01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4",
"sha256:5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8",
"sha256:5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696",
"sha256:7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34",
"sha256:7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9",
"sha256:87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73",
"sha256:9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299",
"sha256:a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b",
"sha256:b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae",
"sha256:b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681",
"sha256:bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41",
"sha256:f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
],
"index": "pypi",
"version": "==5.1.2"
"version": "==5.2"
},
"requests": {
"hashes": [
@ -262,17 +260,17 @@
},
"six": {
"hashes": [
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
"sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
],
"version": "==1.12.0"
"version": "==1.13.0"
},
"sqlalchemy": {
"hashes": [
"sha256:0f0768b5db594517e1f5e1572c73d14cf295140756431270d89496dc13d5e46c"
"sha256:bfb8f464a5000b567ac1d350b9090cf081180ec1ab4aa87e7bca12dab25320ec"
],
"index": "pypi",
"version": "==1.3.10"
"version": "==1.3.12"
},
"tornado": {
"hashes": [
@ -289,11 +287,10 @@
},
"urllib3": {
"hashes": [
"sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398",
"sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"
"sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293",
"sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"
],
"markers": "python_version >= '3.4'",
"version": "==1.25.6"
"version": "==1.25.7"
}
},
"develop": {}

View file

@ -1,6 +1,16 @@
from pyaxidraw import axidraw
ad = axidraw.AxiDraw()
# connect/disconnect once because often first connection attempt fails
ad.interactive()
ad.connect()
ad.pen_raise()
ad.disconnect()
ad.plot_setup()
ad.pen_raise()
ad.options.mode = "manual"
ad.options.manual_cmd = "disable_xy"
ad.plot_run()

View file

@ -60,7 +60,7 @@ class HIT(Base):
def getImagePath(self):
return os.path.join('../scanimation/interfaces/frames', f"{self.id:06d}.jpg")
return os.path.join('scanimation/interfaces/frames', f"{self.id:06d}.jpg")
# def getImageUrl(self):
# return f"{self.id}.jpg"

View file

@ -86,6 +86,9 @@ class CentralManagement():
)
self.logger.info(f"Mechanical turk account balance: {self.mturk.get_account_balance()['AvailableBalance']}")
if not self.config['for_real']:
self.logger.info("Remove block from sandbox worker account")
self.mturk.delete_worker_block(WorkerId='A1CK46PK9VEUH5', Reason='Myself on Sandbox')
# clear any pending hits:
pending_hits = self.mturk.list_hits(MaxResults=100)
@ -173,7 +176,7 @@ class CentralManagement():
self.currentHit.scanned_at = datetime.datetime.utcnow()
self.server.statusPage.set('state', self.currentHit.getStatus())
time_diff = datetime.datetime.now() - self.lastHitTime
to_wait = 150 - time_diff.total_seconds()
to_wait = 10 - time_diff.total_seconds()
if to_wait > 0:
self.logger.warn(f"Sleep until next hit: {to_wait}s")
time.sleep(to_wait)
@ -272,8 +275,9 @@ class CentralManagement():
else:
sqsHit.submit_hit_at = datetime.datetime.strptime(signal.params['event']['EventTimestamp'],"%Y-%m-%dT%H:%M:%SZ")
# Merijn: hier block ik de worker na succesvolle submit, om dubbele workers te voorkomen
self.mturk.create_worker_block(WorkerId=signal.params['event']['WorkerId'], Reason='Every worker can only work once on the taks.')
self.logger.warn("Block worker after submission")
# Disabled after worker mail, use quals instead
#self.mturk.create_worker_block(WorkerId=signal.params['event']['WorkerId'], Reason='Every worker can only work once on the taks.')
#self.logger.warn("Block worker after submission")
self.store.saveHIT(sqsHit)
@ -312,7 +316,13 @@ class CentralManagement():
self.logger.info(f"Make HIT {self.currentHit.id}")
question = open(self.config['amazon']['task_xml'], mode='r').read().replace("{HIT_NR}",str(self.currentHit.id))
# question = open(self.config['amazon']['task_xml'], mode='r').read().replace("{HIT_NR}",str(self.currentHit.id))
question = '''<?xml version="1.0" encoding="UTF-8"?>
<ExternalQuestion xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd">
<ExternalURL>https://guest.rubenvandeven.com:8888/draw?id={HIT_NR}</ExternalURL>
<FrameHeight>0</FrameHeight>
</ExternalQuestion>
'''.replace("{HIT_NR}",str(self.currentHit.id))
estimatedHitDuration = self.store.getEstimatedHitDuration()

View file

@ -52,6 +52,7 @@ class Plotter:
self.ad.options.speed_penup = 100
self.ad.options.speed_pendown = 100
self.ad.options.model = 1 # 2 for A3, 1 for A4
self.ad.options.pen_pos_up = 100
self.park()

View file

@ -38,7 +38,7 @@ class StaticFileWithHeaderHandler(tornado.web.StaticFileHandler):
class WebSocketHandler(tornado.websocket.WebSocketHandler):
CORS_ORIGINS = ['localhost', '.mturk.com', 'here.rubenvandeven.com']
CORS_ORIGINS = ['localhost', '.mturk.com', 'here.rubenvandeven.com', 'guest.rubenvandeven.com']
connections = set()
def initialize(self, config, plotterQ: Queue, eventQ: Queue, store: HITStore):
@ -125,7 +125,8 @@ class WebSocketHandler(tornado.websocket.WebSocketHandler):
self.write_message(json.dumps({
'action': 'submitted',
'msg': f"Submission ok, please copy this token to your HIT at Mechanical Turk: {self.hit.uuid}"
'msg': f"Submission ok, please copy this token to your HIT at Mechanical Turk: {self.hit.uuid}",
'code': str(self.hit.uuid)
}))
self.close()
@ -284,6 +285,14 @@ class DrawPageHandler(tornado.web.RequestHandler):
self.write("HIT already submitted")
return
assignmentId = self.get_query_argument('assignmentId', '')
if len(assignmentId) < 1:
logger.critical("Accessing page without assignment id. Allowing it for debug purposes... fingers crossed?")
previewOnly = False
if assignmentId == 'ASSIGNMENT_ID_NOT_AVAILABLE':
previewOnly = True
previous_hit = self.store.getLastSubmittedHit()
if not previous_hit:
# start with basic svg
@ -301,7 +310,10 @@ class DrawPageHandler(tornado.web.RequestHandler):
.replace("{DRAW_WIDTH}", str(self.draw_width))\
.replace("{DRAW_HEIGHT}", str(self.draw_height))\
.replace("{TOP_PADDING}", str(self.top_padding))\
.replace("{LEFT_PADDING}", str(self.left_padding))
.replace("{LEFT_PADDING}", str(self.left_padding))\
.replace("{SCRIPT}", '' if previewOnly else '<script type="text/javascript" src="/assignment.js"></script>')\
.replace("{ASSIGNMENT}", '' if previewOnly else str(assignmentId)) # TODO: fix unsafe inserting of GET variable
self.write(contents)
if 'X-Forwarded-For' in self.request.headers:
@ -311,16 +323,18 @@ class DrawPageHandler(tornado.web.RequestHandler):
logger.info(f"Request from {ip}")
self.eventQ.put(Signal('hit.info', dict(hit_id=hit.id, ip=ip)))
try:
geoip = self.geoip_reader.country(ip)
logger.debug(f"Geo {geoip}")
self.eventQ.put(Signal('hit.info', dict(hit_id=hit.id, location=geoip.country.name)))
except Exception as e:
logger.exception(e)
logger.info("No geo IP possible")
self.eventQ.put(Signal('hit.info', dict(hit_id=hit.id, location='Unknown')))
if not previewOnly:
self.eventQ.put(Signal('hit.info', dict(hit_id=hit.id, ip=ip)))
try:
geoip = self.geoip_reader.country(ip)
logger.debug(f"Geo {geoip}")
self.eventQ.put(Signal('hit.info', dict(hit_id=hit.id, location=geoip.country.name)))
except Exception as e:
logger.exception(e)
logger.info("No geo IP possible")
self.eventQ.put(Signal('hit.info', dict(hit_id=hit.id, location='Unknown')))
class BackendHandler(tornado.web.RequestHandler):
def initialize(self, store: HITStore, path: str):

174
www/assignment.js Normal file
View file

@ -0,0 +1,174 @@
let url = window.location.origin.replace('http', 'ws') +'/ws?' + window.location.search.substring(1);
let svgEl = document.getElementById("canvas");
let strokeEl = document.getElementById('stroke');
let submitEl = document.getElementById('submit');
let resetEl = document.getElementById('reset');
let messageEl = document.getElementById('message');
let innerFaceEl = document.getElementById('innerface'); // wrapper within the interface
let strokes = [];
let isDrawing = false;
let hasMouseDown = false;
let currentPoint = null;
let getCoordinates = function(e) {
// convert event coordinates into relative positions on x & y axis
let box = innerFaceEl.getBoundingClientRect();
let x = (e.x - box['left']) / box['width'];
let y = (e.y - box['top']) / box['height'];
return {'x': x, 'y': y};
}
let isInsideBounds = function(pos) {
return !(pos['x'] < 0 || pos['y'] < 0 || pos['x'] > 1 || pos['y'] > 1);
}
let isInsideDrawingBounds = function(pos) {
if(pos['x'] > xPadding && pos['x'] < (xPadding+drawWidthFactor) && pos['y'] > yPadding && pos['y'] < yPadding+drawHeightFactor) {
return true;
}
return false;
}
let draw = function(e) {
let pos = getCoordinates(e);
if(!isInsideBounds(pos)) {
// outside of bounds
return;
}
if(isDrawing && !isInsideDrawingBounds(pos)){
stopDrawing(pos);
}
if(!isDrawing && hasMouseDown && isInsideDrawingBounds(pos)){
isDrawing = true;
}
if(isDrawing) {
strokes.push([pos['x'], pos['y'], 0]);
let d = strokes2D(strokes);
strokeEl.setAttribute('d', d);
}
console.log([pos['x'], pos['y']], isDrawing);
socket.send(JSON.stringify({
'action': 'move',
'direction': [pos['x'], pos['y']],
'mouse': isDrawing,
}));
};
let stopDrawing = function(pos) {
if(!isDrawing) {
return;
}
isDrawing = false;
//document.body.removeEventListener('mousemove', draw);
if(strokes.length > 0){
// mark point as last of stroke
strokes[strokes.length - 1][2] = 1;
}
socket.send(JSON.stringify({
'action': 'up',
'direction': [pos['x'], pos['y']]
}));
}
let penup = function(e) {
if(!hasMouseDown) {
return;
}
hasMouseDown = false;
let pos = getCoordinates(e);
stopDrawing(pos);
};
let strokes2D = function(strokes) {
// strokes to a d attribute for a path
let d = "";
let last_stroke = undefined;
let cmd = "";
for (let stroke of strokes) {
if(!last_stroke) {
d += `M${stroke[0]*svgWidth*10},${stroke[1]*svgHeight*10} `;
cmd = 'M';
} else {
if (last_stroke[2] == 1) {
d += " m";
cmd = 'm';
} else if (cmd != 'l') {
d+=' l ';
cmd = 'l';
}
let rel_stroke = [stroke[0] - last_stroke[0], stroke[1] - last_stroke[1]];
d += `${rel_stroke[0]*svgWidth*10},${rel_stroke[1]*svgHeight*10} `;
}
last_stroke = stroke;
}
return d;
}
let startDrawing = function(e){
hasMouseDown = true;
};
/*let reset = function() {
strokes = [];
strokeEl.setAttribute('d', "");
socket.send(JSON.stringify({
'action': 'reset',
}));
}*/
let socket = new WebSocket(url);
document.body.addEventListener('mousemove', draw);
document.body.addEventListener('mouseup', penup);
document.body.addEventListener('mousedown', startDrawing);
socket.addEventListener('message', function(e){
let msg = JSON.parse(e.data);
console.log('receive', msg);
if(msg['action'] == 'submitted') {
document.body.classList.add('submitted');
messageEl.innerHTML = msg['msg'];
svgEl.removeEventListener('mousedown', startDrawing);
}
// submit the completion form only after the shape has been sent to the server
let u = new URL(location);
let url = u.searchParams.get('turkSubmitTo');
let formEl = document.getElementById('finishedForm');
document.getElementById('surveycode').value = msg['code'];
formEl.action = url + '/mturk/externalSubmit';
formEl.submit();
// let assignmentId = u.searchParams.get('assignmentId');
// var formData = new FormData();
// formData.append('assigmentId', assignmentId);
// let r = new Request(url + '/mturk/externalSubmit', {method: 'POST', body: formData});
// fetch(r).then(function(response) {
// console.log(response);
// });
});
//resetEl.addEventListener('click', reset);
submitEl.addEventListener('click', function(e){
if(!strokes.length){
alert('please draw before submitting');
return;
}
socket.send(JSON.stringify({
'action': 'submit',
'd': strokeEl.getAttribute('d')
}));
document.body.removeEventListener('mousemove', draw);
document.body.removeEventListener('mouseup', penup);
document.body.removeEventListener('mousedown', startDrawing);
});

View file

@ -134,7 +134,7 @@
</style>
</head>
<body>
<div id='interface'">
<div id='interface'>
<div id='innerface'>
<div id='wrapper'>
<!-- <img src="{IMAGE_URL}" id='sample'>-->
@ -146,13 +146,18 @@
<div id='info'>
<ul>
<li>Drag the mouse to trace the lines drawing above</li>
<li>Follow the lines as precise as possible</li>
<li>Follow the lines as precise as possible, it is only this image to complete the HIT.</li>
<li>Press submit when you're done.</li>
<li>You'll receive a submission token, to fill in at Mechanical Turk</li>
<li><strong>Please watch the clock!</strong> timing is strict because the tracing is live streamed to us. Unfortunately, due to high abandonment rates we have to keep the timer strict.</li>
</ul>
<div class='buttons'>
<button id='submit'>Submit</button>
<!-- <button id='reset'>Reset</button>-->
<form method='post' action='' id='finishedForm'>
<input type='hidden' name='surveycode' id='surveycode'>
<input type='hidden' name='assignmentId' value='{ASSIGNMENT}'>
</form>
</div>
<div id='message'></div>
</div>
@ -162,174 +167,18 @@
<div class='gray' id='gray_right'></div>
</div>
</div>
<script type="text/javascript">
let url = window.location.origin.replace('http', 'ws') +'/ws?' + window.location.search.substring(1);
let svgEl = document.getElementById("canvas");
let strokeEl = document.getElementById('stroke');
let submitEl = document.getElementById('submit');
let resetEl = document.getElementById('reset');
let messageEl = document.getElementById('message');
let innerFaceEl = document.getElementById('innerface'); // wrapper within the interface
let svgWidth = {WIDTH};
let svgHeight = {HEIGHT};
let drawWidth = {DRAW_WIDTH};
let drawHeight = {DRAW_HEIGHT};
let xPadding = {LEFT_PADDING} / svgWidth;
let yPadding = {TOP_PADDING} / svgHeight;
let drawWidthFactor = drawWidth / svgWidth;
let drawHeightFactor = drawHeight / svgHeight;
let strokes = [];
let isDrawing = false;
let hasMouseDown = false;
let currentPoint = null;
let getCoordinates = function(e) {
// convert event coordinates into relative positions on x & y axis
let box = innerFaceEl.getBoundingClientRect();
let x = (e.x - box['left']) / box['width'];
let y = (e.y - box['top']) / box['height'];
return {'x': x, 'y': y};
}
let isInsideBounds = function(pos) {
return !(pos['x'] < 0 || pos['y'] < 0 || pos['x'] > 1 || pos['y'] > 1);
}
let isInsideDrawingBounds = function(pos) {
if(pos['x'] > xPadding && pos['x'] < (xPadding+drawWidthFactor) && pos['y'] > yPadding && pos['y'] < yPadding+drawHeightFactor) {
return true;
}
return false;
}
let draw = function(e) {
let pos = getCoordinates(e);
if(!isInsideBounds(pos)) {
// outside of bounds
return;
}
if(isDrawing && !isInsideDrawingBounds(pos)){
stopDrawing(pos);
}
if(!isDrawing && hasMouseDown && isInsideDrawingBounds(pos)){
isDrawing = true;
}
if(isDrawing) {
strokes.push([pos['x'], pos['y'], 0]);
let d = strokes2D(strokes);
strokeEl.setAttribute('d', d);
}
<script type='text/javascript'>
var svgWidth = {WIDTH};
var svgHeight = {HEIGHT};
var drawWidth = {DRAW_WIDTH};
var drawHeight = {DRAW_HEIGHT};
console.log([pos['x'], pos['y']], isDrawing);
socket.send(JSON.stringify({
'action': 'move',
'direction': [pos['x'], pos['y']],
'mouse': isDrawing,
}));
};
let stopDrawing = function(pos) {
if(!isDrawing) {
return;
}
isDrawing = false;
//document.body.removeEventListener('mousemove', draw);
if(strokes.length > 0){
// mark point as last of stroke
strokes[strokes.length - 1][2] = 1;
}
socket.send(JSON.stringify({
'action': 'up',
'direction': [pos['x'], pos['y']]
}));
}
let penup = function(e) {
if(!hasMouseDown) {
return;
}
hasMouseDown = false;
let pos = getCoordinates(e);
stopDrawing(pos);
};
let strokes2D = function(strokes) {
// strokes to a d attribute for a path
let d = "";
let last_stroke = undefined;
let cmd = "";
for (let stroke of strokes) {
if(!last_stroke) {
d += `M${stroke[0]*svgWidth*10},${stroke[1]*svgHeight*10} `;
cmd = 'M';
} else {
if (last_stroke[2] == 1) {
d += " m";
cmd = 'm';
} else if (cmd != 'l') {
d+=' l ';
cmd = 'l';
}
let rel_stroke = [stroke[0] - last_stroke[0], stroke[1] - last_stroke[1]];
d += `${rel_stroke[0]*svgWidth*10},${rel_stroke[1]*svgHeight*10} `;
}
last_stroke = stroke;
}
return d;
}
let startDrawing = function(e){
hasMouseDown = true;
};
/*let reset = function() {
strokes = [];
strokeEl.setAttribute('d', "");
socket.send(JSON.stringify({
'action': 'reset',
}));
}*/
let socket = new WebSocket(url);
document.body.addEventListener('mousemove', draw);
document.body.addEventListener('mouseup', penup);
document.body.addEventListener('mousedown', startDrawing);
socket.addEventListener('message', function(e){
let msg = JSON.parse(e.data);
console.log('receive', msg);
if(msg['action'] == 'submitted') {
document.body.classList.add('submitted');
messageEl.innerHTML = msg['msg'];
svgEl.removeEventListener('mousedown', startDrawing);
}
});
//resetEl.addEventListener('click', reset);
submitEl.addEventListener('click', function(e){
if(!strokes.length){
alert('please draw before submitting');
return;
}
socket.send(JSON.stringify({
'action': 'submit',
'd': strokeEl.getAttribute('d')
}));
document.body.removeEventListener('mousemove', draw);
document.body.removeEventListener('mouseup', penup);
document.body.removeEventListener('mousedown', startDrawing);
});
var xPadding = {LEFT_PADDING} / svgWidth;
var yPadding = {TOP_PADDING} / svgHeight;
var drawWidthFactor = drawWidth / svgWidth;
var drawHeightFactor = drawHeight / svgHeight;
</script>
{SCRIPT}
</body>
</html>