Clean up scanimation, add pen test script, update pipfile, level the scanned images
This commit is contained in:
parent
dab6245792
commit
ce649d30b1
33 changed files with 392 additions and 728 deletions
3
Pipfile
3
Pipfile
|
@ -13,8 +13,9 @@ httpagentparser = "*"
|
|||
geoip2 = "*"
|
||||
ink-extensions = "*"
|
||||
python-magic = "*"
|
||||
Pillow = "*"
|
||||
tqdm = "*"
|
||||
serial = "*"
|
||||
pillow = "*"
|
||||
pyserial = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
|
116
Pipfile.lock
generated
116
Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "29b194a198b1d9a791ea020295d0ef6d5969969f9d606e2457444485e1bd6f7a"
|
||||
"sha256": "3c0954c7917f567561faffcf18031aa0718c5144698d0de7022dd9ad9d49b1c4"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -21,18 +21,18 @@
|
|||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:5db4db12a017be2a0b07ec662584b7b9e8afa05894c8aaac145576a7c39a9886",
|
||||
"sha256:7fb8bf70ff2403991c8ae7bc548333811be6e432c7665721364ea0c858eb824e"
|
||||
"sha256:98fdd6fa754e17c0d9e87fbb464c43c7e72aaa6b4f78b418eba47b7d15deffee",
|
||||
"sha256:fdaae8edd63ae114107d375862069d17de23e00489a65169b8141ddee6bdf78b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.10.41"
|
||||
"version": "==1.10.48"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:5bfffa38ebba26ab462bb40e858702390fbe3ae2093a2177a8cde050ad6cb7e3",
|
||||
"sha256:62ddff63be904781f97ced737836a66f5b72579af788c905cfdab32d2970e15e"
|
||||
"sha256:29370f50af7870661609fbfbc4ed01ef2fd531b87b98729700526d1a4b3a2f89",
|
||||
"sha256:7f60edf33c6f5b7c1c9b9377267bdc56495f52704607f713d4c3bd1d82a08334"
|
||||
],
|
||||
"version": "==1.13.41"
|
||||
"version": "==1.13.48"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
|
@ -72,11 +72,11 @@
|
|||
},
|
||||
"geoip2": {
|
||||
"hashes": [
|
||||
"sha256:a37ddac2d200ffb97c736da8b8ba9d5d8dc47da6ec0f162a461b681ecac53a14",
|
||||
"sha256:f7ffe9d258e71a42cf622ce6350d976de1d0312b9f2fbce3975c7d838b57ecf0"
|
||||
"sha256:5869e987bc54c0d707264fec4710661332cc38d2dca5a7f9bb5362d0308e2ce0",
|
||||
"sha256:99ec12d2f1271a73a0a4a2b663fe6ce25fd02289c0a6bef05c0a1c3b30ee95a4"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.9.0"
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"httpagentparser": {
|
||||
"hashes": [
|
||||
|
@ -155,45 +155,37 @@
|
|||
},
|
||||
"maxminddb": {
|
||||
"hashes": [
|
||||
"sha256:449a1713d37320d777d0db286286ab22890f0a176492ecf3ad8d9319108f2f79"
|
||||
"sha256:d0ce131d901eb11669996b49a59f410efd3da2c6dbe2c0094fe2fef8d85b6336"
|
||||
],
|
||||
"version": "==1.5.1"
|
||||
"version": "==1.5.2"
|
||||
},
|
||||
"pillow": {
|
||||
"hashes": [
|
||||
"sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031",
|
||||
"sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71",
|
||||
"sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c",
|
||||
"sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340",
|
||||
"sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa",
|
||||
"sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b",
|
||||
"sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573",
|
||||
"sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e",
|
||||
"sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab",
|
||||
"sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9",
|
||||
"sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e",
|
||||
"sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291",
|
||||
"sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12",
|
||||
"sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871",
|
||||
"sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281",
|
||||
"sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08",
|
||||
"sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41",
|
||||
"sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2",
|
||||
"sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5",
|
||||
"sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb",
|
||||
"sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547",
|
||||
"sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75",
|
||||
"sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9",
|
||||
"sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1",
|
||||
"sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a",
|
||||
"sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96",
|
||||
"sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132",
|
||||
"sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a",
|
||||
"sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5",
|
||||
"sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0"
|
||||
"sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be",
|
||||
"sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946",
|
||||
"sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837",
|
||||
"sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f",
|
||||
"sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00",
|
||||
"sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d",
|
||||
"sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533",
|
||||
"sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a",
|
||||
"sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358",
|
||||
"sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda",
|
||||
"sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435",
|
||||
"sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2",
|
||||
"sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313",
|
||||
"sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff",
|
||||
"sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317",
|
||||
"sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2",
|
||||
"sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614",
|
||||
"sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0",
|
||||
"sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386",
|
||||
"sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9",
|
||||
"sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636",
|
||||
"sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==6.2.1"
|
||||
"version": "==7.0.0"
|
||||
},
|
||||
"pyserial": {
|
||||
"hashes": [
|
||||
|
@ -205,11 +197,11 @@
|
|||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
|
||||
"sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"
|
||||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
|
||||
],
|
||||
"markers": "python_version >= '2.7'",
|
||||
"version": "==2.8.0"
|
||||
"version": "==2.8.1"
|
||||
},
|
||||
"python-magic": {
|
||||
"hashes": [
|
||||
|
@ -221,20 +213,20 @@
|
|||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc",
|
||||
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803",
|
||||
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc",
|
||||
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15",
|
||||
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075",
|
||||
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd",
|
||||
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31",
|
||||
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f",
|
||||
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c",
|
||||
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04",
|
||||
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"
|
||||
"sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
|
||||
"sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
|
||||
"sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
|
||||
"sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
|
||||
"sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
|
||||
"sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
|
||||
"sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
|
||||
"sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
|
||||
"sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
|
||||
"sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
|
||||
"sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.2"
|
||||
"version": "==5.3"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
|
@ -285,6 +277,14 @@
|
|||
"index": "pypi",
|
||||
"version": "==6.0.3"
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:4789ccbb6fc122b5a6a85d512e4e41fc5acad77216533a6f2b8ce51e0f265c23",
|
||||
"sha256:efab950cf7cc1e4d8ee50b2bb9c8e4a89f8307b49e0b2c9cfef3ec4ca26655eb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.41.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293",
|
||||
|
|
|
@ -17,7 +17,7 @@ server:
|
|||
port: 8888
|
||||
scanner: # size of scanarea in mm
|
||||
# total visible glass (and size of the scan)
|
||||
width: 255
|
||||
width: 255
|
||||
height: 185
|
||||
# area which can be removed
|
||||
draw_width: 255
|
||||
|
@ -25,3 +25,7 @@ scanner: # size of scanarea in mm
|
|||
# part of scanner that is invissible left & top
|
||||
left_padding: 0
|
||||
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 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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>
|
|
@ -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>€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)
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
3
scanimation/interfaces/frames/.gitignore
vendored
Normal file
3
scanimation/interfaces/frames/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*
|
||||
!.gitignore
|
||||
|
|
@ -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>
|
|
@ -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);
|
||||
};
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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"> </span>
|
||||
|
||||
<span class="grid-item spec_name" id="worker_id_descriptor">human worker id</span>
|
||||
<span class="grid-item spec_value" id="worker_id"> </span>
|
||||
<span class="grid-item spec_name" id="ip_descriptor">ip address</span>
|
||||
<span class="grid-item spec_value" id="ip"> </span>
|
||||
<span class="grid-item spec_name" id="location_descriptor">location</span>
|
||||
<span class="grid-item spec_value" id="location"> </span>
|
||||
<span class="grid-item spec_name" id="browser_descriptor">browser</span>
|
||||
<span class="grid-item spec_value" id="browser"> </span>
|
||||
<span class="grid-item spec_name" id="os_descriptor">os</span>
|
||||
<span class="grid-item spec_value" id="os"> </span>
|
||||
|
||||
<span class="grid-item spec_name" id="hit_opened_descriptor">task started at</span>
|
||||
<span class="grid-item spec_value" id="hit_opened"> </span>
|
||||
<span class="grid-item spec_name" id="state_descriptor">task status</span>
|
||||
<span class="grid-item spec_value" id="state"> </span>
|
||||
<span class="grid-item spec_name" id="fee_descriptor">task fee</span>
|
||||
<span class="grid-item spec_value" id="fee"> </span>
|
||||
|
||||
|
||||
<span class="grid-item spec_name" id="elapsed_time_descriptor">time elapsed</span>
|
||||
<span class="grid-item spec_value" id="elapsed_time"> </span>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -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});
|
|
@ -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 = `—`
|
||||
hit_started = false
|
||||
}
|
||||
}
|
||||
else if(data.property === 'hit_submitted'){
|
||||
hit_finished = true;
|
||||
}
|
||||
else if(divs[data.property]){
|
||||
data.value === null ? divs[data.property].innerHTML = `—` : 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 = `—`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
makeAnimation()
|
||||
|
||||
|
||||
function timeStamp(){return window.performance && window.performance.now ? window.performance.now() : new Date().getTime()}
|
|
@ -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);
|
||||
}
|
|
@ -16,12 +16,52 @@ import io
|
|||
from PIL import Image
|
||||
import datetime
|
||||
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():
|
||||
"""
|
||||
Central management reads config and controls process flow
|
||||
|
||||
|
||||
The HIT Store is the archive of hits
|
||||
mturk thread communicates with mturk
|
||||
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.currentHit = None
|
||||
self.lastHitTime = None
|
||||
|
||||
|
||||
self.eventQueue = Queue()
|
||||
self.isRunning = threading.Event()
|
||||
self.isScanning = threading.Event()
|
||||
self.scanLock = threading.Lock()
|
||||
self.notPaused = threading.Event()
|
||||
|
||||
|
||||
|
||||
def loadConfig(self, filename, args):
|
||||
self.configFile = filename
|
||||
self.args = args
|
||||
|
||||
|
||||
self.reloadConfig()
|
||||
|
||||
|
||||
varDb = os.path.join(
|
||||
# self.config['storage_dir'],
|
||||
'hit_store.db'
|
||||
)
|
||||
)
|
||||
self.store = HITStore.Store(varDb, logLevel=logging.DEBUG if self.debug else logging.INFO)
|
||||
|
||||
|
||||
self.logger.debug(f"Loaded configuration: {self.config}")
|
||||
|
||||
def reloadConfig(self):
|
||||
|
@ -61,19 +101,19 @@ class CentralManagement():
|
|||
with open(self.configFile, 'r') as fp:
|
||||
self.logger.debug('Load config from {}'.format(self.configFile))
|
||||
self.config = yaml.safe_load(fp)
|
||||
|
||||
|
||||
if self.args.no_plotter:
|
||||
self.config['dummy_plotter'] = True
|
||||
self.config['for_real'] = self.args.for_real
|
||||
|
||||
|
||||
|
||||
# self.panopticon = Panopticon(self, self.config, self.voiceStorage)
|
||||
def start(self):
|
||||
self.isRunning.set()
|
||||
self.notPaused.set()
|
||||
|
||||
|
||||
try:
|
||||
|
||||
|
||||
# M-turk connection
|
||||
MTURK_SANDBOX = 'https://mturk-requester-sandbox.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',
|
||||
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']}")
|
||||
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)
|
||||
for pending_hit in pending_hits['HITs']:
|
||||
|
@ -101,29 +141,29 @@ class CentralManagement():
|
|||
ExpireAt=datetime.datetime.fromisoformat('2015-01-01')
|
||||
)
|
||||
self.mturk.delete_hit(HITId=pending_hit['HITId'])
|
||||
|
||||
|
||||
self.sqs = SqsListener(self.config, self.eventQueue, self.isRunning)
|
||||
sqsThread = threading.Thread(target=self.sqs.start, name='sqs')
|
||||
sqsThread.start()
|
||||
|
||||
|
||||
# the plotter itself
|
||||
self.plotter = Plotter(self.config, self.eventQueue, self.isRunning, self.scanLock)
|
||||
plotterThread = threading.Thread(target=self.plotter.start, name='plotter')
|
||||
plotterThread.start()
|
||||
|
||||
|
||||
# webserver for turks and status
|
||||
self.server = Server(self.config, self.eventQueue, self.isRunning, self.plotter.q, self.store)
|
||||
serverThread = threading.Thread(target=self.server.start, name='server')
|
||||
serverThread.start()
|
||||
|
||||
|
||||
# event listener:
|
||||
dispatcherThread = threading.Thread(target=self.eventListener, name='dispatcher')
|
||||
dispatcherThread.start()
|
||||
#
|
||||
#
|
||||
|
||||
#
|
||||
#
|
||||
|
||||
self.eventQueue.put(Signal('start', {'ding':'test'}))
|
||||
|
||||
|
||||
while self.isRunning.is_set():
|
||||
time.sleep(.5)
|
||||
except Exception as e:
|
||||
|
@ -132,9 +172,9 @@ class CentralManagement():
|
|||
self.logger.warning("Stopping Central Managment")
|
||||
self.isRunning.clear()
|
||||
self.server.stop()
|
||||
|
||||
|
||||
self.expireCurrentHit()
|
||||
|
||||
|
||||
def expireCurrentHit(self):
|
||||
if self.currentHit and self.currentHit.hit_id: # hit pending
|
||||
self.logger.warn(f"Delete hit: {self.currentHit.hit_id}")
|
||||
|
@ -163,7 +203,7 @@ class CentralManagement():
|
|||
- scan complete
|
||||
- HIT created
|
||||
- Plotter complete
|
||||
-
|
||||
-
|
||||
"""
|
||||
#TODO: make level debug()
|
||||
self.logger.info(f"SIGNAL: {signal}")
|
||||
|
@ -199,7 +239,7 @@ class CentralManagement():
|
|||
self.currentHit.turk_ip = value
|
||||
if name == 'location':
|
||||
self.currentHit.turk_country = value
|
||||
|
||||
|
||||
self.logger.debug(f'Set status: {name} to {value}')
|
||||
self.server.statusPage.set(name, value)
|
||||
elif signal.name == 'server.open':
|
||||
|
@ -214,7 +254,7 @@ class CentralManagement():
|
|||
self.plotter.park()
|
||||
self.server.statusPage.set('hit_opened', self.currentHit.open_page_at)
|
||||
# park always triggers a plotter.finished after being processed
|
||||
|
||||
|
||||
elif signal.name[:4] == 'sqs.':
|
||||
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")
|
||||
|
@ -223,7 +263,7 @@ class CentralManagement():
|
|||
else:
|
||||
sqsHit = self.currentHit
|
||||
updateStatus = True
|
||||
|
||||
|
||||
if signal.name == 'sqs.AssignmentAccepted':
|
||||
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")
|
||||
|
@ -244,7 +284,7 @@ class CentralManagement():
|
|||
if updateStatus:
|
||||
self.setLight(False)
|
||||
self.mturk.create_worker_block(WorkerId=signal.params['event']['WorkerId'], Reason='Accepted task without working on it.')
|
||||
|
||||
|
||||
elif signal.name == 'sqs.AssignmentReturned':
|
||||
self.logger.info(f'Set status progress to returned')
|
||||
sqsHit.accept_time = None
|
||||
|
@ -264,7 +304,7 @@ class CentralManagement():
|
|||
sqsHit.answer = signal.params['event']['Answer']
|
||||
if sqsHit.uuid not in sqsHit.answer:
|
||||
self.logger.critical(f"Not a valid answer given?! {sqsHit.answer}")
|
||||
|
||||
|
||||
if not sqsHit.submit_page_at:
|
||||
# page not submitted, hit is. Nevertheless, create new hit.
|
||||
try:
|
||||
|
@ -274,14 +314,14 @@ class CentralManagement():
|
|||
self.makeHit()
|
||||
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
|
||||
# Disabled after worker mail, use quals instead
|
||||
# block de worker na succesvolle submit, om dubbele workers te voorkomen
|
||||
# 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.logger.warn("Block worker after submission")
|
||||
|
||||
|
||||
|
||||
self.store.saveHIT(sqsHit)
|
||||
|
||||
|
||||
if updateStatus:
|
||||
self.logger.warning(f'update status: {sqsHit.getStatus()}')
|
||||
# TODO: have HITStore/HIT take care of this by emitting a signal
|
||||
|
@ -301,21 +341,21 @@ class CentralManagement():
|
|||
except Exception as e:
|
||||
self.logger.critical(f"Exception on handling {signal}")
|
||||
self.logger.exception(e)
|
||||
|
||||
|
||||
def makeHit(self):
|
||||
self.expireCurrentHit() # expire hit if it is there
|
||||
|
||||
|
||||
self.server.statusPage.reset()
|
||||
self.reloadConfig() # reload new config values if they are set
|
||||
|
||||
|
||||
# self.notPaused.wait()
|
||||
|
||||
|
||||
|
||||
|
||||
self.currentHit = self.store.createHIT()
|
||||
self.store.currentHit = self.currentHit
|
||||
|
||||
|
||||
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 = '''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ExternalQuestion xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd">
|
||||
|
@ -324,8 +364,8 @@ class CentralManagement():
|
|||
</ExternalQuestion>
|
||||
'''.replace("{HIT_NR}",str(self.currentHit.id))
|
||||
estimatedHitDuration = self.store.getEstimatedHitDuration()
|
||||
|
||||
|
||||
|
||||
|
||||
# set minimum rate, if they take longer we increase the pay
|
||||
fee = max(self.config['minimum_fee'], (self.config['hour_rate_aim']/3600.) * estimatedHitDuration)
|
||||
self.currentHit.fee = fee
|
||||
|
@ -341,22 +381,22 @@ class CentralManagement():
|
|||
AutoApprovalDelayInSeconds = self.config['hit_autoapprove_delay'],
|
||||
Question = question,
|
||||
)
|
||||
|
||||
|
||||
self.logger.info(f"Created hit: {new_hit}")
|
||||
if not self.config['for_real']:
|
||||
self.logger.info("https://workersandbox.mturk.com/mturk/preview?groupId=" + new_hit['HIT']['HITGroupId'])
|
||||
else:
|
||||
self.logger.info("https://worker.mturk.com/mturk/preview?groupId=" + new_hit['HIT']['HITGroupId'])
|
||||
|
||||
|
||||
self.currentHit.hit_id = new_hit['HIT']['HITId']
|
||||
|
||||
|
||||
self.store.saveHIT(self.currentHit)
|
||||
# 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_created', self.currentHit.created_at)
|
||||
self.server.statusPage.set('fee', f"${self.currentHit.fee:.2f}")
|
||||
self.server.statusPage.set('state', self.currentHit.getStatus())
|
||||
|
||||
|
||||
# mturk.send_test_event_notification()
|
||||
if self.config['amazon']['sqs_url']:
|
||||
notification_info= self.mturk.update_notification_settings(
|
||||
|
@ -383,7 +423,7 @@ class CentralManagement():
|
|||
Active=True
|
||||
)
|
||||
self.logger.debug(notification_info)
|
||||
|
||||
|
||||
def cleanDrawing(self):
|
||||
with self.scanLock:
|
||||
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
|
||||
if e:
|
||||
self.logger.critical(f"Scanner caused: {e.decode()}")
|
||||
|
||||
|
||||
self.eventQueue.put(Signal('system.reset'))
|
||||
self.eventQueue.put(Signal('scan.finished'))
|
||||
|
||||
|
||||
def reset(self) -> str:
|
||||
self.plotter.park()
|
||||
# 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.start()
|
||||
self.server.statusPage.clearAssignment()
|
||||
|
||||
|
||||
def scanImage(self) -> str:
|
||||
"""
|
||||
Run scanimage on scaner and returns a string with the filename
|
||||
"""
|
||||
|
||||
|
||||
cmd = [
|
||||
'sudo', 'scanimage', '-d', 'epkowa', '--format', 'jpeg',
|
||||
'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)
|
||||
'-x',str(181),
|
||||
'-y',str(245)
|
||||
]
|
||||
self.logger.info(f"{cmd}")
|
||||
filename = self.currentHit.getImagePath()
|
||||
|
||||
|
||||
with self.scanLock:
|
||||
self.eventQueue.put(Signal('scan.start'))
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
@ -436,23 +476,24 @@ class CentralManagement():
|
|||
if e:
|
||||
self.logger.critical(f"Scanner caused: {e.decode()}")
|
||||
#TODO: should clear self.isRunning.clear() ?
|
||||
|
||||
|
||||
try:
|
||||
f = io.BytesIO(o)
|
||||
img = Image.open(f)
|
||||
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:
|
||||
self.logger.critical("Cannot create image from scan. Did scanner work?")
|
||||
self.logger.exception(e)
|
||||
# TODO: create
|
||||
# TODO: create
|
||||
copyfile('www/basic.svg', filename)
|
||||
|
||||
|
||||
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('scan.finished'))
|
||||
|
||||
|
||||
def setLight(self, on):
|
||||
value = 1 if on else 0
|
||||
cmd = [
|
||||
|
@ -462,5 +503,3 @@ class CentralManagement():
|
|||
code = subprocess.call(cmd)
|
||||
if code > 0:
|
||||
self.logger.warning(f"Error on light change: {code}")
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ class Plotter:
|
|||
self.isRunning = runningEvent
|
||||
self.logger = logging.getLogger("sorteerhoed").getChild("plotter")
|
||||
self.pen_down = False
|
||||
|
||||
|
||||
self.plotWidth = self.config['scanner']['width'] / 10 / 2.54
|
||||
self.plotHeight = self.config['scanner']['height'] / 10 / 2.54
|
||||
self.xPadding = self.config['scanner']['left_padding'] / 10 / 2.54;
|
||||
|
@ -25,7 +25,7 @@ class Plotter:
|
|||
self.goPark = False
|
||||
self.locked = False
|
||||
self.ad = None
|
||||
|
||||
|
||||
def park(self):
|
||||
self.logger.info("Queue to park plotter")
|
||||
self.goPark = True
|
||||
|
@ -33,31 +33,30 @@ class Plotter:
|
|||
absPlotWidth = 26.5/2.54
|
||||
topLeft = absPlotWidth / self.plotWidth #ignore changes in config plotwidth
|
||||
self.q.put([(1/2.54)/absPlotWidth + topLeft,0,0])
|
||||
|
||||
|
||||
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
if not self.config['dummy_plotter']:
|
||||
self.ad = axidraw.AxiDraw()
|
||||
|
||||
|
||||
self.ad.interactive()
|
||||
# self.ad.plot_path()
|
||||
|
||||
|
||||
connected = self.ad.connect()
|
||||
if not connected:
|
||||
raise Exception("Cannot connect to Axidraw")
|
||||
|
||||
|
||||
# self.ad.options.units = 1 # set to use centimeters instead of inches
|
||||
self.ad.options.accel = 100;
|
||||
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()
|
||||
# self.ad.moveto(0,0)
|
||||
|
||||
|
||||
else:
|
||||
self.ad = None
|
||||
self.axiDrawCueListener()
|
||||
|
@ -73,17 +72,17 @@ class Plotter:
|
|||
except Exception as e:
|
||||
self.logger.warning("Error on closing axidraw:")
|
||||
self.logger.exception(e)
|
||||
|
||||
|
||||
self.logger.info("Clear running Event")
|
||||
# send shutdown signal (if not already set)
|
||||
self.isRunning.clear()
|
||||
|
||||
|
||||
def draw_segments(self, segments = []):
|
||||
if not self.locked:
|
||||
# acquire lock if not already done so
|
||||
self.scannerLock.acquire()
|
||||
self.locked = True
|
||||
|
||||
|
||||
coordinates = []
|
||||
for segment in segments:
|
||||
coordinate = [
|
||||
|
@ -97,8 +96,8 @@ class Plotter:
|
|||
if coordinate[0] < self.xPadding or coordinate[0] > self.xPadding+self.plotWidth or \
|
||||
coordinate[1] < self.yPadding or coordinate[1] > self.yPadding + self.plotHeight:
|
||||
self.logger.warn(f"Skip drawing for: {coordinates} out of bounds")
|
||||
continue
|
||||
|
||||
continue
|
||||
|
||||
coordinates.append(coordinate)
|
||||
self.logger.debug(f"Plot: {coordinates}")
|
||||
if self.ad:
|
||||
|
@ -110,7 +109,7 @@ class Plotter:
|
|||
# self.ad.moveto(move[0]* plotterWidth, move[1]*plotterHeight)
|
||||
# self.logger.debug(f'handler! {move}')
|
||||
pass
|
||||
|
||||
|
||||
def setPenDown(self, pen_state):
|
||||
"""
|
||||
False: pen raised, True: pen_lower
|
||||
|
@ -140,20 +139,20 @@ class Plotter:
|
|||
|
||||
#if self.goPark:
|
||||
# print("seg",segment)
|
||||
|
||||
|
||||
# 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 len(segments):
|
||||
self.draw_segments(segments)
|
||||
plotterRan = True
|
||||
segments = [] #reset
|
||||
|
||||
|
||||
# and change pen positions
|
||||
self.setPenDown(segment[2] == 1)
|
||||
|
||||
|
||||
|
||||
segments.append(segment)
|
||||
|
||||
|
||||
except queue.Empty as e:
|
||||
self.logger.debug("Timeout queue.")
|
||||
if len(segments):
|
||||
|
@ -174,4 +173,3 @@ class Plotter:
|
|||
# time.sleep(.05)
|
||||
# self.logger.debug(f'Plotter move: {move}')
|
||||
self.logger.info("Stopping plotter")
|
||||
|
||||
|
|
67
test_drawing.svg
Normal file
67
test_drawing.svg
Normal 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
131
test_pen.py
Normal 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()
|
|
@ -34,7 +34,7 @@
|
|||
body.submitted path{
|
||||
stroke:darkgray;
|
||||
}
|
||||
|
||||
|
||||
body.submitted .buttons{
|
||||
display:none;
|
||||
}
|
||||
|
@ -145,13 +145,13 @@
|
|||
</div>
|
||||
<div id='info'>
|
||||
<ul>
|
||||
<li>Drag the mouse to trace the lines drawing above</li>
|
||||
<li>Follow the lines as precise as possible, it is only this image to complete the HIT.</li>
|
||||
<li>Drag the mouse to trace the lines above.</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><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'>
|
||||
|
@ -178,7 +178,7 @@
|
|||
var drawWidthFactor = drawWidth / svgWidth;
|
||||
var drawHeightFactor = drawHeight / svgHeight;
|
||||
</script>
|
||||
|
||||
|
||||
{SCRIPT}
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue