Clean up scanimation, add pen test script, update pipfile, level the scanned images

This commit is contained in:
mt 2020-01-08 17:55:45 +01:00
parent dab6245792
commit ce649d30b1
33 changed files with 392 additions and 728 deletions

View File

@ -13,8 +13,9 @@ httpagentparser = "*"
geoip2 = "*" geoip2 = "*"
ink-extensions = "*" ink-extensions = "*"
python-magic = "*" python-magic = "*"
Pillow = "*"
tqdm = "*"
serial = "*" serial = "*"
pillow = "*"
pyserial = "*" pyserial = "*"
[dev-packages] [dev-packages]

116
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "29b194a198b1d9a791ea020295d0ef6d5969969f9d606e2457444485e1bd6f7a" "sha256": "3c0954c7917f567561faffcf18031aa0718c5144698d0de7022dd9ad9d49b1c4"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -21,18 +21,18 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:5db4db12a017be2a0b07ec662584b7b9e8afa05894c8aaac145576a7c39a9886", "sha256:98fdd6fa754e17c0d9e87fbb464c43c7e72aaa6b4f78b418eba47b7d15deffee",
"sha256:7fb8bf70ff2403991c8ae7bc548333811be6e432c7665721364ea0c858eb824e" "sha256:fdaae8edd63ae114107d375862069d17de23e00489a65169b8141ddee6bdf78b"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.10.41" "version": "==1.10.48"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:5bfffa38ebba26ab462bb40e858702390fbe3ae2093a2177a8cde050ad6cb7e3", "sha256:29370f50af7870661609fbfbc4ed01ef2fd531b87b98729700526d1a4b3a2f89",
"sha256:62ddff63be904781f97ced737836a66f5b72579af788c905cfdab32d2970e15e" "sha256:7f60edf33c6f5b7c1c9b9377267bdc56495f52704607f713d4c3bd1d82a08334"
], ],
"version": "==1.13.41" "version": "==1.13.48"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
@ -72,11 +72,11 @@
}, },
"geoip2": { "geoip2": {
"hashes": [ "hashes": [
"sha256:a37ddac2d200ffb97c736da8b8ba9d5d8dc47da6ec0f162a461b681ecac53a14", "sha256:5869e987bc54c0d707264fec4710661332cc38d2dca5a7f9bb5362d0308e2ce0",
"sha256:f7ffe9d258e71a42cf622ce6350d976de1d0312b9f2fbce3975c7d838b57ecf0" "sha256:99ec12d2f1271a73a0a4a2b663fe6ce25fd02289c0a6bef05c0a1c3b30ee95a4"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.9.0" "version": "==3.0.0"
}, },
"httpagentparser": { "httpagentparser": {
"hashes": [ "hashes": [
@ -155,45 +155,37 @@
}, },
"maxminddb": { "maxminddb": {
"hashes": [ "hashes": [
"sha256:449a1713d37320d777d0db286286ab22890f0a176492ecf3ad8d9319108f2f79" "sha256:d0ce131d901eb11669996b49a59f410efd3da2c6dbe2c0094fe2fef8d85b6336"
], ],
"version": "==1.5.1" "version": "==1.5.2"
}, },
"pillow": { "pillow": {
"hashes": [ "hashes": [
"sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031", "sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be",
"sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71", "sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946",
"sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c", "sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837",
"sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340", "sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f",
"sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa", "sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00",
"sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b", "sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d",
"sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573", "sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533",
"sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e", "sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a",
"sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab", "sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358",
"sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9", "sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda",
"sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e", "sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435",
"sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291", "sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2",
"sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12", "sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313",
"sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871", "sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff",
"sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281", "sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317",
"sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08", "sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2",
"sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41", "sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614",
"sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2", "sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0",
"sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5", "sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386",
"sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb", "sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9",
"sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547", "sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636",
"sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75", "sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865"
"sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9",
"sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1",
"sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a",
"sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96",
"sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132",
"sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a",
"sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5",
"sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0"
], ],
"index": "pypi", "index": "pypi",
"version": "==6.2.1" "version": "==7.0.0"
}, },
"pyserial": { "pyserial": {
"hashes": [ "hashes": [
@ -205,11 +197,11 @@
}, },
"python-dateutil": { "python-dateutil": {
"hashes": [ "hashes": [
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
], ],
"markers": "python_version >= '2.7'", "markers": "python_version >= '2.7'",
"version": "==2.8.0" "version": "==2.8.1"
}, },
"python-magic": { "python-magic": {
"hashes": [ "hashes": [
@ -221,20 +213,20 @@
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4" "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.2" "version": "==5.3"
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
@ -285,6 +277,14 @@
"index": "pypi", "index": "pypi",
"version": "==6.0.3" "version": "==6.0.3"
}, },
"tqdm": {
"hashes": [
"sha256:4789ccbb6fc122b5a6a85d512e4e41fc5acad77216533a6f2b8ce51e0f265c23",
"sha256:efab950cf7cc1e4d8ee50b2bb9c8e4a89f8307b49e0b2c9cfef3ec4ca26655eb"
],
"index": "pypi",
"version": "==4.41.1"
},
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293",

View File

@ -17,7 +17,7 @@ server:
port: 8888 port: 8888
scanner: # size of scanarea in mm scanner: # size of scanarea in mm
# total visible glass (and size of the scan) # total visible glass (and size of the scan)
width: 255 width: 255
height: 185 height: 185
# area which can be removed # area which can be removed
draw_width: 255 draw_width: 255
@ -25,3 +25,7 @@ scanner: # size of scanarea in mm
# part of scanner that is invissible left & top # part of scanner that is invissible left & top
left_padding: 0 left_padding: 0
top_padding: 45 top_padding: 45
level:
min: 0
max: 90
gamma: 0.9

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,33 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="style.css" />
<script src='/socket.io/socket.io.js'></script>
</head>
<body>
<div id="wrapper">
<div id="logo"> <img src="amazon.svg" /> </div>
<table>
<thead>
<tr>
<th>worker id</th>
<th>ip address</th>
<th>country</th>
<th>fee</th>
<th>task start time</th>
<th>task completion time</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<script src="script.js"></script>
</body>
</html>

View File

@ -1,12 +0,0 @@
let list = document.querySelector("tbody")
let testdata = `<td>AMZ3976824398</td><td>234.183.281.221</td><td>united states</td><td>&euro;0.20</td><td>wed 30 oct 14:56</td><td>04m 37s</td>`
for(let i=0; i < 200; i++){
let entry = document.createElement('tr')
entry.innerHTML = testdata
list.append(entry)
}

View File

@ -1,110 +0,0 @@
@font-face {
font-family: 'bebas';
src: url('font/BebasNeue-Regular.ttf');
}
@font-face {
font-family: 'freesans';
src: url('font/FreeSans.ttf')
}
:root{
--base-font-size: 12px;
--spec_name-font-size: 120%;
--spec_value-font-size: 250%;
--base-color: #271601; /* tekst */
--alt-color: #FFF5DF; /* achtergrond */
--amazon-color: #F0C14C;
/* ////// GAT ACHTERKANT PLEK & POSITIE /////// */
/* */ /* */
/* */ --pos-x: 20px; /* */
/* */ --pos-y: 20px; /* */
/* */ --width: 90%; /* 115mm */
/* */ --height: 100%; /* 500mm */
/* */ /* */
/* //////////////////////////////////////////// */
}
html, body{
margin: 0;
padding: 0;
border: 0;
text-decoration: none;
font-family: 'freesans';
font-size: var(--base-font-size);
line-height: 1.1;
background: #555;
overflow: hidden;
}
#logo{
background: #555;
width: 100%;
padding: 2% 0% 1% 0%;
text-align: right;
}
#wrapper{
position: absolute;
left: var(--pos-x);
top: var(--pos-y);
width: var(--width);
/* height: var(--height); */
background: var(--alt-color);
box-sizing: border-box;
/* padding: 2%; */
}
table{
display: grid;
border-collapse: collapse;
min-width: 100%;
grid-template-columns: repeat(6, 1fr);
}
thead, tbody, tr{
display: contents;
}
th,
td {
padding: 2%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
th {
position: -webkit-sticky;
position: sticky;
background-image: linear-gradient(var(--alt-color), var(--amazon-color)) ;
top: 0;
text-align: left;
font-weight: normal;
font-size: 1.1rem;
color: var(--base-color);
}
th:last-child {
border: 0;
}
td {
padding-top: 2%;
padding-bottom: 2%;
color: #808080;
}
tr:nth-child(even) td {
background: #f8f6f9;
}

View File

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

View File

@ -1,3 +0,0 @@
<a href="worker_specs/index.html">worker specs</a><br />
<a href="animation/index.html">frame animation</a><br />
<a href="backend/index.html">backend</a>

View File

@ -1,125 +0,0 @@
/*
* Date Format 1.2.3
* (c) 2007-2009 Steven Levithan <stevenlevithan.com>
* MIT license
*
* Includes enhancements by Scott Trenda <scott.trenda.net>
* and Kris Kowal <cixar.com/~kris.kowal/>
*
* Accepts a date, a mask, or a date and a mask.
* Returns a formatted version of the given date.
* The date defaults to the current date/time.
* The mask defaults to dateFormat.masks.default.
*/
var dateFormat = function () {
var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
timezoneClip = /[^-+\dA-Z]/g,
pad = function (val, len) {
val = String(val);
len = len || 2;
while (val.length < len) val = "0" + val;
return val;
};
// Regexes and supporting functions are cached through closure
return function (date, mask, utc) {
var dF = dateFormat;
// You can't provide utc if you skip other args (use the "UTC:" mask prefix)
if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
mask = date;
date = undefined;
}
// Passing date through Date applies Date.parse, if necessary
date = date ? new Date(date) : new Date;
if (isNaN(date)) throw SyntaxError("invalid date");
mask = String(dF.masks[mask] || mask || dF.masks["default"]);
// Allow setting the utc argument via the mask
if (mask.slice(0, 4) == "UTC:") {
mask = mask.slice(4);
utc = true;
}
var _ = utc ? "getUTC" : "get",
d = date[_ + "Date"](),
D = date[_ + "Day"](),
m = date[_ + "Month"](),
y = date[_ + "FullYear"](),
H = date[_ + "Hours"](),
M = date[_ + "Minutes"](),
s = date[_ + "Seconds"](),
L = date[_ + "Milliseconds"](),
o = utc ? 0 : date.getTimezoneOffset(),
flags = {
d: d,
dd: pad(d),
ddd: dF.i18n.dayNames[D],
dddd: dF.i18n.dayNames[D + 7],
m: m + 1,
mm: pad(m + 1),
mmm: dF.i18n.monthNames[m],
mmmm: dF.i18n.monthNames[m + 12],
yy: String(y).slice(2),
yyyy: y,
h: H % 12 || 12,
hh: pad(H % 12 || 12),
H: H,
HH: pad(H),
M: M,
MM: pad(M),
s: s,
ss: pad(s),
l: pad(L, 3),
L: pad(L > 99 ? Math.round(L / 10) : L),
t: H < 12 ? "a" : "p",
tt: H < 12 ? "am" : "pm",
T: H < 12 ? "A" : "P",
TT: H < 12 ? "AM" : "PM",
Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
};
return mask.replace(token, function ($0) {
return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
});
};
}();
// Some common format strings
dateFormat.masks = {
"default": "ddd mmm dd yyyy HH:MM:ss",
shortDate: "m/d/yy",
mediumDate: "mmm d, yyyy",
longDate: "mmmm d, yyyy",
fullDate: "dddd, mmmm d, yyyy",
shortTime: "h:MM TT",
mediumTime: "h:MM:ss TT",
longTime: "h:MM:ss TT Z",
isoDate: "yyyy-mm-dd",
isoTime: "HH:MM:ss",
isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
};
// Internationalization strings
dateFormat.i18n = {
dayNames: [
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
],
monthNames: [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
]
};
// For convenience...
Date.prototype.format = function (mask, utc) {
return dateFormat(this, mask, utc);
};

View File

@ -1,58 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="style.css" />
<script src='dateformat.js'></script>
<script src='reconnecting-websocket.min.js'></script>
</head>
<body>
<div id="wrapper">
<!-- <div class="phase" id="waiting_for_human">
<span class="narrative_phase_text">waiting for human worker to accept task</span>
</div>
<div class="phase" id="human_accepted_task">
<span class="narrative_phase_text">task accepted by human worker</span>
</div> -->
<div class="phase" id="worker_specs">
<span class="grid-item spec_name" id="hit_id_descriptor">human intelligent task id</span>
<span class="grid-item spec_value" id="hit_id">&nbsp;</span>
<span class="grid-item spec_name" id="worker_id_descriptor">human worker id</span>
<span class="grid-item spec_value" id="worker_id">&nbsp;</span>
<span class="grid-item spec_name" id="ip_descriptor">ip address</span>
<span class="grid-item spec_value" id="ip">&nbsp;</span>
<span class="grid-item spec_name" id="location_descriptor">location</span>
<span class="grid-item spec_value" id="location">&nbsp;</span>
<span class="grid-item spec_name" id="browser_descriptor">browser</span>
<span class="grid-item spec_value" id="browser">&nbsp;</span>
<span class="grid-item spec_name" id="os_descriptor">os</span>
<span class="grid-item spec_value" id="os">&nbsp;</span>
<span class="grid-item spec_name" id="hit_opened_descriptor">task started at</span>
<span class="grid-item spec_value" id="hit_opened">&nbsp;</span>
<span class="grid-item spec_name" id="state_descriptor">task status</span>
<span class="grid-item spec_value" id="state">&nbsp;</span>
<span class="grid-item spec_name" id="fee_descriptor">task fee</span>
<span class="grid-item spec_value" id="fee">&nbsp;</span>
<span class="grid-item spec_name" id="elapsed_time_descriptor">time elapsed</span>
<span class="grid-item spec_value" id="elapsed_time">&nbsp;</span>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

View File

@ -1 +0,0 @@
!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a});

View File

@ -1,118 +0,0 @@
// DOM STUFF ///////////////////////////////////////////////////////////////////
let divs = {},
spec_names = [
'worker_id',
'ip',
'location',
'browser',
'os',
'state',
'fee',
'hit_created',
'hit_opened',
'hit_submitted',
'elapsed_time',
'hit_id'
]
divs.linkDOM = function(name){
divs[name] = document.getElementById(`${name}`)
}
spec_names.forEach(function(name){
divs.linkDOM(name)
})
let request_time = timeStamp(),
hit_started = false,
elapsed_time,
hit_finished = false
// SOCKET STUFF ////////////////////////////////////////////////////////////////
let ws = new ReconnectingWebSocket('ws://localhost:8888/status/ws')
ws.addEventListener('open', () => {
// ws.send('hi server')
})
ws.addEventListener('message', (event) => {
console.log('message: ' + event.data)
let data = JSON.parse(event.data)
if(data.property === 'hit_opened') {
if(data.value != null){
hit_started = true
hit_finished = false
request_time = new Date()
divs[data.property].innerHTML = `${request_time.format('dd mmm HH:MM:ss')}`
}else{
divs[data.property].innerHTML = `&mdash;`
hit_started = false
}
}
else if(data.property === 'hit_submitted'){
hit_finished = true;
}
else if(divs[data.property]){
data.value === null ? divs[data.property].innerHTML = `&mdash;` : divs[data.property].innerHTML = `${data.value}`
}
})
// ANIMATION STUFF /////////////////////////////////////////////////////////////
let frames,
frames_per_sec = 10,
current_frame = 0
function makeAnimation(){
let now,
delta = 0,
last = timeStamp(),
step = 1/frames_per_sec
function frame() {
now = timeStamp()
delta += Math.min(1, (now - last) / 1000)
while(delta > step){
delta -= step
update(step)
}
last = now
requestAnimationFrame(frame)
}
requestAnimationFrame(frame)
}
function update(step){
if(!hit_finished) elapsed_time = `${new Date((Date.now() - request_time)).format('MM"m "ss"s"')}`
if(hit_started){
divs['elapsed_time'].innerHTML = elapsed_time
}else{
divs['elapsed_time'].innerHTML = `&mdash;`
}
}
makeAnimation()
function timeStamp(){return window.performance && window.performance.now ? window.performance.now() : new Date().getTime()}

View File

@ -1,93 +0,0 @@
@font-face {
font-family: 'bebas';
src: url('font/BebasNeue-Regular.ttf');
}
@font-face {
font-family: 'freesans';
src: url('font/FreeSans.ttf')
}
:root{
--base-font-size: 22px;
--spec_name-font-size: 120%;
--spec_value-font-size: 250%;
--base-color: #271601; /* tekst */
--alt-color: #FFF5DF; /* achtergrond */
/* /////// GAT VOORKANT PLEK & POSITIE //////// */
/* */ /* */
/* */ --pos-x: 555px; /* */
/* */ --pos-y: 120px; /* */
/* */ --width: 420px; /* 115mm */
/* */ --height: 1000px; /* 270mm */
/* */ /* */
/* //////////////////////////////////////////// */
}
html, body{
margin: 0;
padding: 0;
border: 0;
text-decoration: none;
font-family: 'bebas';
font-size: var(--base-font-size);
line-height: 1.1;
background: var(--alt-color);
overflow: hidden;
}
#wrapper{
position: absolute;
left: var(--pos-x);
top: var(--pos-y);
width: var(--width);
height: var(--height);
background: var(--alt-color);
box-sizing: border-box;
padding: 2%;
}
#worker_specs{
display:grid;
grid-template-columns: 1fr ;
grid-template-rows: repeat(50, 1fr 2fr);
}
.grid-item{
color: var(--base-color);
}
.spec_name{
font-size: var(--spec_name-font-size);
font-family: 'freesans';
}
.spec_value{
font-size: var(--spec_value-font-size);
padding-bottom: 2%;
}
.phase{
display:none;
}
.phase:not(#worker_specs){
padding-top: 100%;
text-align: center;
}
.narrative_phase_text{
font-size: var(--spec_value-font-size);
}

View File

@ -16,12 +16,52 @@ import io
from PIL import Image from PIL import Image
import datetime import datetime
from shutil import copyfile from shutil import copyfile
import colorsys
class Level(object):
# Level effect adapted from https://stackoverflow.com/a/3125421
def __init__(self, minv, maxv, gamma):
self.minv= minv/255.0
self.maxv= maxv/255.0
self._interval= self.maxv - self.minv
self._invgamma= 1.0/gamma
def new_level(self, value):
if value <= self.minv: return 0.0
if value >= self.maxv: return 1.0
return ((value - self.minv)/self._interval)**self._invgamma
def convert_and_level(self, band_values):
h, s, v= colorsys.rgb_to_hsv(*(i/255.0 for i in band_values))
new_v= self.new_level(v)
return tuple(int(255*i)
for i
in colorsys.hsv_to_rgb(h, s, new_v))
@classmethod
def level_image(cls, image, minv=0, maxv=255, gamma=1.0):
"""Level the brightness of image (a PIL.Image instance)
All values minv will become 0
All values maxv will become 255
gamma controls the curve for all values between minv and maxv"""
if image.mode != "RGB":
raise ValueError("this works with RGB images only")
new_image= image.copy()
leveller= cls(minv, maxv, gamma)
levelled_data= [
leveller.convert_and_level(data)
for data in image.getdata()]
new_image.putdata(levelled_data)
return new_image
class CentralManagement(): class CentralManagement():
""" """
Central management reads config and controls process flow Central management reads config and controls process flow
The HIT Store is the archive of hits The HIT Store is the archive of hits
mturk thread communicates with mturk mturk thread communicates with mturk
server thread is tornado communicating with the turkers and with the status interface on the installation server thread is tornado communicating with the turkers and with the status interface on the installation
@ -34,26 +74,26 @@ class CentralManagement():
self.debug = debug_mode self.debug = debug_mode
self.currentHit = None self.currentHit = None
self.lastHitTime = None self.lastHitTime = None
self.eventQueue = Queue() self.eventQueue = Queue()
self.isRunning = threading.Event() self.isRunning = threading.Event()
self.isScanning = threading.Event() self.isScanning = threading.Event()
self.scanLock = threading.Lock() self.scanLock = threading.Lock()
self.notPaused = threading.Event() self.notPaused = threading.Event()
def loadConfig(self, filename, args): def loadConfig(self, filename, args):
self.configFile = filename self.configFile = filename
self.args = args self.args = args
self.reloadConfig() self.reloadConfig()
varDb = os.path.join( varDb = os.path.join(
# self.config['storage_dir'], # self.config['storage_dir'],
'hit_store.db' 'hit_store.db'
) )
self.store = HITStore.Store(varDb, logLevel=logging.DEBUG if self.debug else logging.INFO) self.store = HITStore.Store(varDb, logLevel=logging.DEBUG if self.debug else logging.INFO)
self.logger.debug(f"Loaded configuration: {self.config}") self.logger.debug(f"Loaded configuration: {self.config}")
def reloadConfig(self): def reloadConfig(self):
@ -61,19 +101,19 @@ class CentralManagement():
with open(self.configFile, 'r') as fp: with open(self.configFile, 'r') as fp:
self.logger.debug('Load config from {}'.format(self.configFile)) self.logger.debug('Load config from {}'.format(self.configFile))
self.config = yaml.safe_load(fp) self.config = yaml.safe_load(fp)
if self.args.no_plotter: if self.args.no_plotter:
self.config['dummy_plotter'] = True self.config['dummy_plotter'] = True
self.config['for_real'] = self.args.for_real self.config['for_real'] = self.args.for_real
# self.panopticon = Panopticon(self, self.config, self.voiceStorage) # self.panopticon = Panopticon(self, self.config, self.voiceStorage)
def start(self): def start(self):
self.isRunning.set() self.isRunning.set()
self.notPaused.set() self.notPaused.set()
try: try:
# M-turk connection # M-turk connection
MTURK_SANDBOX = 'https://mturk-requester-sandbox.us-east-1.amazonaws.com' MTURK_SANDBOX = 'https://mturk-requester-sandbox.us-east-1.amazonaws.com'
MTURK_REAL = 'https://mturk-requester.us-east-1.amazonaws.com' MTURK_REAL = 'https://mturk-requester.us-east-1.amazonaws.com'
@ -84,12 +124,12 @@ class CentralManagement():
region_name='us-east-1', region_name='us-east-1',
endpoint_url = MTURK_REAL if self.config['for_real'] else MTURK_SANDBOX endpoint_url = MTURK_REAL if self.config['for_real'] else MTURK_SANDBOX
) )
self.logger.info(f"Mechanical turk account balance: {self.mturk.get_account_balance()['AvailableBalance']}") self.logger.info(f"Mechanical turk account balance: {self.mturk.get_account_balance()['AvailableBalance']}")
if not self.config['for_real']: if not self.config['for_real']:
self.logger.info("Remove block from sandbox worker account") self.logger.info("Remove block from sandbox worker account")
self.mturk.delete_worker_block(WorkerId='A1CK46PK9VEUH5', Reason='Myself on Sandbox') self.mturk.delete_worker_block(WorkerId='A1CK46PK9VEUH5', Reason='Myself on Sandbox')
# clear any pending hits: # clear any pending hits:
pending_hits = self.mturk.list_hits(MaxResults=100) pending_hits = self.mturk.list_hits(MaxResults=100)
for pending_hit in pending_hits['HITs']: for pending_hit in pending_hits['HITs']:
@ -101,29 +141,29 @@ class CentralManagement():
ExpireAt=datetime.datetime.fromisoformat('2015-01-01') ExpireAt=datetime.datetime.fromisoformat('2015-01-01')
) )
self.mturk.delete_hit(HITId=pending_hit['HITId']) self.mturk.delete_hit(HITId=pending_hit['HITId'])
self.sqs = SqsListener(self.config, self.eventQueue, self.isRunning) self.sqs = SqsListener(self.config, self.eventQueue, self.isRunning)
sqsThread = threading.Thread(target=self.sqs.start, name='sqs') sqsThread = threading.Thread(target=self.sqs.start, name='sqs')
sqsThread.start() sqsThread.start()
# the plotter itself # the plotter itself
self.plotter = Plotter(self.config, self.eventQueue, self.isRunning, self.scanLock) self.plotter = Plotter(self.config, self.eventQueue, self.isRunning, self.scanLock)
plotterThread = threading.Thread(target=self.plotter.start, name='plotter') plotterThread = threading.Thread(target=self.plotter.start, name='plotter')
plotterThread.start() plotterThread.start()
# webserver for turks and status # webserver for turks and status
self.server = Server(self.config, self.eventQueue, self.isRunning, self.plotter.q, self.store) self.server = Server(self.config, self.eventQueue, self.isRunning, self.plotter.q, self.store)
serverThread = threading.Thread(target=self.server.start, name='server') serverThread = threading.Thread(target=self.server.start, name='server')
serverThread.start() serverThread.start()
# event listener: # event listener:
dispatcherThread = threading.Thread(target=self.eventListener, name='dispatcher') dispatcherThread = threading.Thread(target=self.eventListener, name='dispatcher')
dispatcherThread.start() dispatcherThread.start()
# #
# #
self.eventQueue.put(Signal('start', {'ding':'test'})) self.eventQueue.put(Signal('start', {'ding':'test'}))
while self.isRunning.is_set(): while self.isRunning.is_set():
time.sleep(.5) time.sleep(.5)
except Exception as e: except Exception as e:
@ -132,9 +172,9 @@ class CentralManagement():
self.logger.warning("Stopping Central Managment") self.logger.warning("Stopping Central Managment")
self.isRunning.clear() self.isRunning.clear()
self.server.stop() self.server.stop()
self.expireCurrentHit() self.expireCurrentHit()
def expireCurrentHit(self): def expireCurrentHit(self):
if self.currentHit and self.currentHit.hit_id: # hit pending if self.currentHit and self.currentHit.hit_id: # hit pending
self.logger.warn(f"Delete hit: {self.currentHit.hit_id}") self.logger.warn(f"Delete hit: {self.currentHit.hit_id}")
@ -163,7 +203,7 @@ class CentralManagement():
- scan complete - scan complete
- HIT created - HIT created
- Plotter complete - Plotter complete
- -
""" """
#TODO: make level debug() #TODO: make level debug()
self.logger.info(f"SIGNAL: {signal}") self.logger.info(f"SIGNAL: {signal}")
@ -199,7 +239,7 @@ class CentralManagement():
self.currentHit.turk_ip = value self.currentHit.turk_ip = value
if name == 'location': if name == 'location':
self.currentHit.turk_country = value self.currentHit.turk_country = value
self.logger.debug(f'Set status: {name} to {value}') self.logger.debug(f'Set status: {name} to {value}')
self.server.statusPage.set(name, value) self.server.statusPage.set(name, value)
elif signal.name == 'server.open': elif signal.name == 'server.open':
@ -214,7 +254,7 @@ class CentralManagement():
self.plotter.park() self.plotter.park()
self.server.statusPage.set('hit_opened', self.currentHit.open_page_at) self.server.statusPage.set('hit_opened', self.currentHit.open_page_at)
# park always triggers a plotter.finished after being processed # park always triggers a plotter.finished after being processed
elif signal.name[:4] == 'sqs.': elif signal.name[:4] == 'sqs.':
if signal.params['event']['HITId'] != self.currentHit.hit_id: if signal.params['event']['HITId'] != self.currentHit.hit_id:
self.logger.warning(f"SQS hit.info hit_id != currenthit.id: {signal}, update status for older HIT") self.logger.warning(f"SQS hit.info hit_id != currenthit.id: {signal}, update status for older HIT")
@ -223,7 +263,7 @@ class CentralManagement():
else: else:
sqsHit = self.currentHit sqsHit = self.currentHit
updateStatus = True updateStatus = True
if signal.name == 'sqs.AssignmentAccepted': if signal.name == 'sqs.AssignmentAccepted':
self.logger.info(f'Set status progress to accepted') self.logger.info(f'Set status progress to accepted')
sqsHit.accept_time = datetime.datetime.strptime(signal.params['event']['EventTimestamp'],"%Y-%m-%dT%H:%M:%SZ") sqsHit.accept_time = datetime.datetime.strptime(signal.params['event']['EventTimestamp'],"%Y-%m-%dT%H:%M:%SZ")
@ -244,7 +284,7 @@ class CentralManagement():
if updateStatus: if updateStatus:
self.setLight(False) self.setLight(False)
self.mturk.create_worker_block(WorkerId=signal.params['event']['WorkerId'], Reason='Accepted task without working on it.') self.mturk.create_worker_block(WorkerId=signal.params['event']['WorkerId'], Reason='Accepted task without working on it.')
elif signal.name == 'sqs.AssignmentReturned': elif signal.name == 'sqs.AssignmentReturned':
self.logger.info(f'Set status progress to returned') self.logger.info(f'Set status progress to returned')
sqsHit.accept_time = None sqsHit.accept_time = None
@ -264,7 +304,7 @@ class CentralManagement():
sqsHit.answer = signal.params['event']['Answer'] sqsHit.answer = signal.params['event']['Answer']
if sqsHit.uuid not in sqsHit.answer: if sqsHit.uuid not in sqsHit.answer:
self.logger.critical(f"Not a valid answer given?! {sqsHit.answer}") self.logger.critical(f"Not a valid answer given?! {sqsHit.answer}")
if not sqsHit.submit_page_at: if not sqsHit.submit_page_at:
# page not submitted, hit is. Nevertheless, create new hit. # page not submitted, hit is. Nevertheless, create new hit.
try: try:
@ -274,14 +314,14 @@ class CentralManagement():
self.makeHit() self.makeHit()
else: else:
sqsHit.submit_hit_at = datetime.datetime.strptime(signal.params['event']['EventTimestamp'],"%Y-%m-%dT%H:%M:%SZ") 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 # block de worker na succesvolle submit, om dubbele workers te voorkomen
# Disabled after worker mail, use quals instead # TODO: 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.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.logger.warn("Block worker after submission")
self.store.saveHIT(sqsHit) self.store.saveHIT(sqsHit)
if updateStatus: if updateStatus:
self.logger.warning(f'update status: {sqsHit.getStatus()}') self.logger.warning(f'update status: {sqsHit.getStatus()}')
# TODO: have HITStore/HIT take care of this by emitting a signal # TODO: have HITStore/HIT take care of this by emitting a signal
@ -301,21 +341,21 @@ class CentralManagement():
except Exception as e: except Exception as e:
self.logger.critical(f"Exception on handling {signal}") self.logger.critical(f"Exception on handling {signal}")
self.logger.exception(e) self.logger.exception(e)
def makeHit(self): def makeHit(self):
self.expireCurrentHit() # expire hit if it is there self.expireCurrentHit() # expire hit if it is there
self.server.statusPage.reset() self.server.statusPage.reset()
self.reloadConfig() # reload new config values if they are set self.reloadConfig() # reload new config values if they are set
# self.notPaused.wait() # self.notPaused.wait()
self.currentHit = self.store.createHIT() self.currentHit = self.store.createHIT()
self.store.currentHit = self.currentHit self.store.currentHit = self.currentHit
self.logger.info(f"Make HIT {self.currentHit.id}") 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"?> question = '''<?xml version="1.0" encoding="UTF-8"?>
<ExternalQuestion xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd"> <ExternalQuestion xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd">
@ -324,8 +364,8 @@ class CentralManagement():
</ExternalQuestion> </ExternalQuestion>
'''.replace("{HIT_NR}",str(self.currentHit.id)) '''.replace("{HIT_NR}",str(self.currentHit.id))
estimatedHitDuration = self.store.getEstimatedHitDuration() estimatedHitDuration = self.store.getEstimatedHitDuration()
# set minimum rate, if they take longer we increase the pay # set minimum rate, if they take longer we increase the pay
fee = max(self.config['minimum_fee'], (self.config['hour_rate_aim']/3600.) * estimatedHitDuration) fee = max(self.config['minimum_fee'], (self.config['hour_rate_aim']/3600.) * estimatedHitDuration)
self.currentHit.fee = fee self.currentHit.fee = fee
@ -341,22 +381,22 @@ class CentralManagement():
AutoApprovalDelayInSeconds = self.config['hit_autoapprove_delay'], AutoApprovalDelayInSeconds = self.config['hit_autoapprove_delay'],
Question = question, Question = question,
) )
self.logger.info(f"Created hit: {new_hit}") self.logger.info(f"Created hit: {new_hit}")
if not self.config['for_real']: if not self.config['for_real']:
self.logger.info("https://workersandbox.mturk.com/mturk/preview?groupId=" + new_hit['HIT']['HITGroupId']) self.logger.info("https://workersandbox.mturk.com/mturk/preview?groupId=" + new_hit['HIT']['HITGroupId'])
else: else:
self.logger.info("https://worker.mturk.com/mturk/preview?groupId=" + new_hit['HIT']['HITGroupId']) self.logger.info("https://worker.mturk.com/mturk/preview?groupId=" + new_hit['HIT']['HITGroupId'])
self.currentHit.hit_id = new_hit['HIT']['HITId'] self.currentHit.hit_id = new_hit['HIT']['HITId']
self.store.saveHIT(self.currentHit) self.store.saveHIT(self.currentHit)
# TODO: have HITStore/HIT take care of this by emitting a signal # TODO: have HITStore/HIT take care of this by emitting a signal
self.server.statusPage.set('hit_id', new_hit['HIT']['HITId']) self.server.statusPage.set('hit_id', new_hit['HIT']['HITId'])
self.server.statusPage.set('hit_created', self.currentHit.created_at) self.server.statusPage.set('hit_created', self.currentHit.created_at)
self.server.statusPage.set('fee', f"${self.currentHit.fee:.2f}") self.server.statusPage.set('fee', f"${self.currentHit.fee:.2f}")
self.server.statusPage.set('state', self.currentHit.getStatus()) self.server.statusPage.set('state', self.currentHit.getStatus())
# mturk.send_test_event_notification() # mturk.send_test_event_notification()
if self.config['amazon']['sqs_url']: if self.config['amazon']['sqs_url']:
notification_info= self.mturk.update_notification_settings( notification_info= self.mturk.update_notification_settings(
@ -383,7 +423,7 @@ class CentralManagement():
Active=True Active=True
) )
self.logger.debug(notification_info) self.logger.debug(notification_info)
def cleanDrawing(self): def cleanDrawing(self):
with self.scanLock: with self.scanLock:
self.eventQueue.put(Signal('scan.start')) self.eventQueue.put(Signal('scan.start'))
@ -401,33 +441,33 @@ class CentralManagement():
time.sleep(5) # sleep a few seconds for scanner to return to start position time.sleep(5) # sleep a few seconds for scanner to return to start position
if e: if e:
self.logger.critical(f"Scanner caused: {e.decode()}") self.logger.critical(f"Scanner caused: {e.decode()}")
self.eventQueue.put(Signal('system.reset')) self.eventQueue.put(Signal('system.reset'))
self.eventQueue.put(Signal('scan.finished')) self.eventQueue.put(Signal('scan.finished'))
def reset(self) -> str: def reset(self) -> str:
self.plotter.park() self.plotter.park()
# Very confusing to have scanning on a reset (because often nothing has happened), so don't do itd # Very confusing to have scanning on a reset (because often nothing has happened), so don't do itd
# scan = threading.Thread(target=self.cleanDrawing, name='reset') # scan = threading.Thread(target=self.cleanDrawing, name='reset')
# scan.start() # scan.start()
self.server.statusPage.clearAssignment() self.server.statusPage.clearAssignment()
def scanImage(self) -> str: def scanImage(self) -> str:
""" """
Run scanimage on scaner and returns a string with the filename Run scanimage on scaner and returns a string with the filename
""" """
cmd = [ cmd = [
'sudo', 'scanimage', '-d', 'epkowa', '--format', 'jpeg', 'sudo', 'scanimage', '-d', 'epkowa', '--format', 'tiff',
'--resolution=100', # lower res, faster (more powerful) scan & wipe '--resolution=100', # lower res, faster (more powerful) scan & wipe
'-l','25' #y axis, margin from top of the scanner, hence increasing this, moves the scanned image upwards '-l','25' #y axis, margin from top of the scanner, hence increasing this, moves the scanned image upwards
,'-t','22', # x axis, margin from left side scanner (seen from the outside) ,'-t','22', # x axis, margin from left side scanner (seen from the outside)
'-x',str(181), '-x',str(181),
'-y',str(245) '-y',str(245)
] ]
self.logger.info(f"{cmd}") self.logger.info(f"{cmd}")
filename = self.currentHit.getImagePath() filename = self.currentHit.getImagePath()
with self.scanLock: with self.scanLock:
self.eventQueue.put(Signal('scan.start')) self.eventQueue.put(Signal('scan.start'))
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@ -436,23 +476,24 @@ class CentralManagement():
if e: if e:
self.logger.critical(f"Scanner caused: {e.decode()}") self.logger.critical(f"Scanner caused: {e.decode()}")
#TODO: should clear self.isRunning.clear() ? #TODO: should clear self.isRunning.clear() ?
try: try:
f = io.BytesIO(o) f = io.BytesIO(o)
img = Image.open(f) img = Image.open(f)
img = img.transpose(Image.ROTATE_90).transpose(Image.FLIP_TOP_BOTTOM) img = img.transpose(Image.ROTATE_90).transpose(Image.FLIP_TOP_BOTTOM)
img.save(filename) tunedImg = Level.level_image(img, self.config['level']['min'], self.config['level']['max'], self.config['level']['gamma'])
tunedImg.save(filename)
except Exception as e: except Exception as e:
self.logger.critical("Cannot create image from scan. Did scanner work?") self.logger.critical("Cannot create image from scan. Did scanner work?")
self.logger.exception(e) self.logger.exception(e)
# TODO: create # TODO: create
copyfile('www/basic.svg', filename) copyfile('www/basic.svg', filename)
time.sleep(5) # sleep a few seconds for scanner to return to start position time.sleep(5) # sleep a few seconds for scanner to return to start position
self.eventQueue.put(Signal('hit.scanned', {'hit_id':self.currentHit.id})) self.eventQueue.put(Signal('hit.scanned', {'hit_id':self.currentHit.id}))
self.eventQueue.put(Signal('scan.finished')) self.eventQueue.put(Signal('scan.finished'))
def setLight(self, on): def setLight(self, on):
value = 1 if on else 0 value = 1 if on else 0
cmd = [ cmd = [
@ -462,5 +503,3 @@ class CentralManagement():
code = subprocess.call(cmd) code = subprocess.call(cmd)
if code > 0: if code > 0:
self.logger.warning(f"Error on light change: {code}") self.logger.warning(f"Error on light change: {code}")

View File

@ -15,7 +15,7 @@ class Plotter:
self.isRunning = runningEvent self.isRunning = runningEvent
self.logger = logging.getLogger("sorteerhoed").getChild("plotter") self.logger = logging.getLogger("sorteerhoed").getChild("plotter")
self.pen_down = False self.pen_down = False
self.plotWidth = self.config['scanner']['width'] / 10 / 2.54 self.plotWidth = self.config['scanner']['width'] / 10 / 2.54
self.plotHeight = self.config['scanner']['height'] / 10 / 2.54 self.plotHeight = self.config['scanner']['height'] / 10 / 2.54
self.xPadding = self.config['scanner']['left_padding'] / 10 / 2.54; self.xPadding = self.config['scanner']['left_padding'] / 10 / 2.54;
@ -25,7 +25,7 @@ class Plotter:
self.goPark = False self.goPark = False
self.locked = False self.locked = False
self.ad = None self.ad = None
def park(self): def park(self):
self.logger.info("Queue to park plotter") self.logger.info("Queue to park plotter")
self.goPark = True self.goPark = True
@ -33,31 +33,30 @@ class Plotter:
absPlotWidth = 26.5/2.54 absPlotWidth = 26.5/2.54
topLeft = absPlotWidth / self.plotWidth #ignore changes in config plotwidth topLeft = absPlotWidth / self.plotWidth #ignore changes in config plotwidth
self.q.put([(1/2.54)/absPlotWidth + topLeft,0,0]) self.q.put([(1/2.54)/absPlotWidth + topLeft,0,0])
def start(self): def start(self):
try: try:
if not self.config['dummy_plotter']: if not self.config['dummy_plotter']:
self.ad = axidraw.AxiDraw() self.ad = axidraw.AxiDraw()
self.ad.interactive() self.ad.interactive()
# self.ad.plot_path() # self.ad.plot_path()
connected = self.ad.connect() connected = self.ad.connect()
if not connected: if not connected:
raise Exception("Cannot connect to Axidraw") raise Exception("Cannot connect to Axidraw")
# self.ad.options.units = 1 # set to use centimeters instead of inches # self.ad.options.units = 1 # set to use centimeters instead of inches
self.ad.options.accel = 100; self.ad.options.accel = 100;
self.ad.options.speed_penup = 100 self.ad.options.speed_penup = 100
self.ad.options.speed_pendown = 100 self.ad.options.speed_pendown = 100
self.ad.options.model = 1 # 2 for A3, 1 for A4 self.ad.options.model = 1 # 2 for A3, 1 for A4
self.ad.options.pen_pos_up = 100 self.ad.options.pen_pos_up = 100
self.park() self.park()
# self.ad.moveto(0,0) # self.ad.moveto(0,0)
else: else:
self.ad = None self.ad = None
self.axiDrawCueListener() self.axiDrawCueListener()
@ -73,17 +72,17 @@ class Plotter:
except Exception as e: except Exception as e:
self.logger.warning("Error on closing axidraw:") self.logger.warning("Error on closing axidraw:")
self.logger.exception(e) self.logger.exception(e)
self.logger.info("Clear running Event") self.logger.info("Clear running Event")
# send shutdown signal (if not already set) # send shutdown signal (if not already set)
self.isRunning.clear() self.isRunning.clear()
def draw_segments(self, segments = []): def draw_segments(self, segments = []):
if not self.locked: if not self.locked:
# acquire lock if not already done so # acquire lock if not already done so
self.scannerLock.acquire() self.scannerLock.acquire()
self.locked = True self.locked = True
coordinates = [] coordinates = []
for segment in segments: for segment in segments:
coordinate = [ coordinate = [
@ -97,8 +96,8 @@ class Plotter:
if coordinate[0] < self.xPadding or coordinate[0] > self.xPadding+self.plotWidth or \ if coordinate[0] < self.xPadding or coordinate[0] > self.xPadding+self.plotWidth or \
coordinate[1] < self.yPadding or coordinate[1] > self.yPadding + self.plotHeight: coordinate[1] < self.yPadding or coordinate[1] > self.yPadding + self.plotHeight:
self.logger.warn(f"Skip drawing for: {coordinates} out of bounds") self.logger.warn(f"Skip drawing for: {coordinates} out of bounds")
continue continue
coordinates.append(coordinate) coordinates.append(coordinate)
self.logger.debug(f"Plot: {coordinates}") self.logger.debug(f"Plot: {coordinates}")
if self.ad: if self.ad:
@ -110,7 +109,7 @@ class Plotter:
# self.ad.moveto(move[0]* plotterWidth, move[1]*plotterHeight) # self.ad.moveto(move[0]* plotterWidth, move[1]*plotterHeight)
# self.logger.debug(f'handler! {move}') # self.logger.debug(f'handler! {move}')
pass pass
def setPenDown(self, pen_state): def setPenDown(self, pen_state):
""" """
False: pen raised, True: pen_lower False: pen raised, True: pen_lower
@ -140,20 +139,20 @@ class Plotter:
#if self.goPark: #if self.goPark:
# print("seg",segment) # print("seg",segment)
# change of pen state? draw previous segments! # change of pen state? draw previous segments!
if (segment[2] == 1 and not self.pen_down) or (segment[2] == 0 and self.pen_down) or len(segments) > 150: if (segment[2] == 1 and not self.pen_down) or (segment[2] == 0 and self.pen_down) or len(segments) > 150:
if len(segments): if len(segments):
self.draw_segments(segments) self.draw_segments(segments)
plotterRan = True plotterRan = True
segments = [] #reset segments = [] #reset
# and change pen positions # and change pen positions
self.setPenDown(segment[2] == 1) self.setPenDown(segment[2] == 1)
segments.append(segment) segments.append(segment)
except queue.Empty as e: except queue.Empty as e:
self.logger.debug("Timeout queue.") self.logger.debug("Timeout queue.")
if len(segments): if len(segments):
@ -174,4 +173,3 @@ class Plotter:
# time.sleep(.05) # time.sleep(.05)
# self.logger.debug(f'Plotter move: {move}') # self.logger.debug(f'Plotter move: {move}')
self.logger.info("Stopping plotter") self.logger.info("Stopping plotter")

67
test_drawing.svg Normal file
View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
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"
width="297mm"
height="210mm"
viewBox="0 0 297 210"
version="1.1"
id="svg8">
<defs
id="defs2" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(0,-87)"
id="layer1">
<rect
style="fill:none;stroke:#000000;stroke-width:0.26458332"
y="157.72922"
x="158.74643"
height="65.767853"
width="65.767853"
id="rect10" />
<g
style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round"
transform="matrix(1.3678984,0,0,1.3678984,-38.928611,-70.723286)"
id="g3757">
<g
transform="translate(72.433243,200.95856)"
id="g3755">
<path
d="M 274,22.1 V 649 H 56.7 495"
style="stroke-width:0.1004in"
transform="scale(0.027244,-0.027244)"
id="path3747" />
<path
d="M 466,31.5 H 132 V 356 H 410 132 v 287 h 322"
style="stroke-width:0.1004in"
transform="matrix(0.027244,0,0,-0.027244,14.929478,0)"
id="path3749" />
<path
d="m 78.8,107 53.2,-44 51,-25.2 63,-12.6 72,-3.1 79,31.5 53,47.4 19,63 -3,56 -34,57 -73,47 -129,57 -72,47 -38,51 -3,63 25,66 72,44 70,10 66,-7 50,-25 47,-35"
style="stroke-width:0.1004in"
transform="matrix(0.027244,0,0,-0.027244,29.613763,0)"
id="path3751" />
<path
d="M 274,22.1 V 649 H 56.7 495"
style="stroke-width:0.1004in"
transform="matrix(0.027244,0,0,-0.027244,44.46151,0)"
id="path3753" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

131
test_pen.py Normal file
View File

@ -0,0 +1,131 @@
from pyaxidraw import axidraw
import time
import subprocess
import io
from PIL import Image
import datetime
import tqdm
import colorsys
# TODO: argparse: skip_draw, skip_scan, loop [n times]
class Level(object):
def __init__(self, minv, maxv, gamma):
self.minv= minv/255.0
self.maxv= maxv/255.0
self._interval= self.maxv - self.minv
self._invgamma= 1.0/gamma
def new_level(self, value):
if value <= self.minv: return 0.0
if value >= self.maxv: return 1.0
return ((value - self.minv)/self._interval)**self._invgamma
def convert_and_level(self, band_values):
h, s, v= colorsys.rgb_to_hsv(*(i/255.0 for i in band_values))
new_v= self.new_level(v)
return tuple(int(255*i)
for i
in colorsys.hsv_to_rgb(h, s, new_v))
def level_image(image, minv=0, maxv=255, gamma=1.0):
"""Level the brightness of image (a PIL.Image instance)
All values minv will become 0
All values maxv will become 255
gamma controls the curve for all values between minv and maxv"""
if image.mode != "RGB":
raise ValueError("this works with RGB images only")
new_image= image.copy()
leveller= Level(minv, maxv, gamma)
levelled_data= [
leveller.convert_and_level(data)
for data in image.getdata()]
new_image.putdata(levelled_data)
return new_image
now = datetime.datetime.now().isoformat()
print(f"test plotter/scanner/pen at {now}")
absPlotWidth = 26.5/2.54
topLeft = absPlotWidth / (245/ 10 / 2.54)
parkX = (1/2.54)/absPlotWidth + topLeft
parkPos = [26.5/2.54,2,0]
print(parkPos)
ad = axidraw.AxiDraw()
ad.interactive()
ad.connect()
ad.pen_raise()
ad.disconnect()
ad = axidraw.AxiDraw()
ad.plot_setup('test_drawing.svg') # Load file & configure plot context
# Plotting options can be set, here after plot_setup().
ad.plot_run()
ad.disconnect()
ad = axidraw.AxiDraw()
# park
ad.interactive()
ad.connect()
ad.pen_raise()
ad.moveto(parkPos[0], parkPos[1])
try:
sleeptime = 2
print(f"plotted, now wait for the ink to dry {sleeptime}s")
for i in tqdm.tqdm(range(sleeptime)):
time.sleep(1)
cmd = [
'sudo', 'scanimage', '-d', 'epkowa', '--format', 'tiff',
'--resolution=100', # lower res, faster (more powerful) scan & wipe
'-l','25' #y axis, margin from top of the scanner, hence increasing this, moves the scanned image upwards
,'-t','22', # x axis, margin from left side scanner (seen from the outside)
'-x',str(181),
'-y',str(245)
]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# opens connection to scanner, but only starts scanning when output becomes ready:
o, e = proc.communicate(80)
print('scanned')
f = io.BytesIO(o)
img = Image.open(f)
img = img.transpose(Image.ROTATE_90).transpose(Image.FLIP_TOP_BOTTOM)
img.save(f'test-{now}.png')
print(f'\tsaved to test-{now}.png')
print('effects:')
leveldImg = level_image(img, 0, 125, 0.95)
leveldImg.save(f'test-{now}-leveled.png')
print(f'\tsaved to test-{now}-leveled.png')
except Exception as e:
print(e)
finally:
time.sleep(5) # scanner says its done before it slides back
print('reset pen')
ad.moveto(0,0)
ad.disconnect()
print('disable motors')
# disable motors
ad = axidraw.AxiDraw()
ad.plot_setup()
ad.options.mode = "manual"
ad.options.manual_cmd = "disable_xy"
ad.plot_run()

View File

@ -34,7 +34,7 @@
body.submitted path{ body.submitted path{
stroke:darkgray; stroke:darkgray;
} }
body.submitted .buttons{ body.submitted .buttons{
display:none; display:none;
} }
@ -145,13 +145,13 @@
</div> </div>
<div id='info'> <div id='info'>
<ul> <ul>
<li>Drag the mouse to trace the lines drawing above</li> <li>Drag the mouse to trace the lines above.</li>
<li>Follow the lines as precise as possible, it is only this image to complete the HIT.</li> <li>Try to be as precise as possible: there's only one image per HIT.</li>
<li>Press submit when you're done.</li> <li>Press submit when you're done.</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> <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> </ul>
<div class='buttons'> <div class='buttons'>
<button id='submit'>Submit</button> <button id='submit'>Submit</button>
<!-- <button id='reset'>Reset</button>--> <!-- <button id='reset'>Reset</button>-->
<form method='post' action='' id='finishedForm'> <form method='post' action='' id='finishedForm'>
@ -178,7 +178,7 @@
var drawWidthFactor = drawWidth / svgWidth; var drawWidthFactor = drawWidth / svgWidth;
var drawHeightFactor = drawHeight / svgHeight; var drawHeightFactor = drawHeight / svgHeight;
</script> </script>
{SCRIPT} {SCRIPT}
</body> </body>
</html> </html>