diff --git a/Pipfile b/Pipfile index 9f4df52..c020eba 100644 --- a/Pipfile +++ b/Pipfile @@ -13,8 +13,9 @@ httpagentparser = "*" geoip2 = "*" ink-extensions = "*" python-magic = "*" +Pillow = "*" +tqdm = "*" serial = "*" -pillow = "*" pyserial = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 38136b4..6c3a1a3 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -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", diff --git a/config.yml b/config.yml index 40670b1..cc56545 100644 --- a/config.yml +++ b/config.yml @@ -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 diff --git a/scanimation/interfaces/backend/amazon.svg b/scanimation/interfaces/backend/amazon.svg deleted file mode 100644 index 43961b1..0000000 --- a/scanimation/interfaces/backend/amazon.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - Standard Mark - Dark - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/scanimation/interfaces/backend/font/BebasNeue-Regular.eot b/scanimation/interfaces/backend/font/BebasNeue-Regular.eot deleted file mode 100644 index 63df3a7..0000000 Binary files a/scanimation/interfaces/backend/font/BebasNeue-Regular.eot and /dev/null differ diff --git a/scanimation/interfaces/backend/font/BebasNeue-Regular.otf b/scanimation/interfaces/backend/font/BebasNeue-Regular.otf deleted file mode 100644 index 4f5676c..0000000 Binary files a/scanimation/interfaces/backend/font/BebasNeue-Regular.otf and /dev/null differ diff --git a/scanimation/interfaces/backend/font/BebasNeue-Regular.ttf b/scanimation/interfaces/backend/font/BebasNeue-Regular.ttf deleted file mode 100644 index 76e22b8..0000000 Binary files a/scanimation/interfaces/backend/font/BebasNeue-Regular.ttf and /dev/null differ diff --git a/scanimation/interfaces/backend/font/BebasNeue-Regular.woff b/scanimation/interfaces/backend/font/BebasNeue-Regular.woff deleted file mode 100644 index 457f916..0000000 Binary files a/scanimation/interfaces/backend/font/BebasNeue-Regular.woff and /dev/null differ diff --git a/scanimation/interfaces/backend/font/BebasNeue-Regular.woff2 b/scanimation/interfaces/backend/font/BebasNeue-Regular.woff2 deleted file mode 100644 index b4099a9..0000000 Binary files a/scanimation/interfaces/backend/font/BebasNeue-Regular.woff2 and /dev/null differ diff --git a/scanimation/interfaces/backend/font/DroidSansFallbackFull.ttf b/scanimation/interfaces/backend/font/DroidSansFallbackFull.ttf deleted file mode 100644 index 89959f5..0000000 Binary files a/scanimation/interfaces/backend/font/DroidSansFallbackFull.ttf and /dev/null differ diff --git a/scanimation/interfaces/backend/font/FreeSans.ttf b/scanimation/interfaces/backend/font/FreeSans.ttf deleted file mode 100644 index 7d1be71..0000000 Binary files a/scanimation/interfaces/backend/font/FreeSans.ttf and /dev/null differ diff --git a/scanimation/interfaces/backend/index.html b/scanimation/interfaces/backend/index.html deleted file mode 100644 index 3fe5993..0000000 --- a/scanimation/interfaces/backend/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
worker idip addresscountryfeetask start timetask completion time
-
- - - - diff --git a/scanimation/interfaces/backend/script.js b/scanimation/interfaces/backend/script.js deleted file mode 100644 index 3995bdb..0000000 --- a/scanimation/interfaces/backend/script.js +++ /dev/null @@ -1,12 +0,0 @@ - - -let list = document.querySelector("tbody") -let testdata = `AMZ3976824398234.183.281.221united states€0.20wed 30 oct 14:5604m 37s` - -for(let i=0; i < 200; i++){ - - let entry = document.createElement('tr') - entry.innerHTML = testdata - list.append(entry) - -} diff --git a/scanimation/interfaces/backend/style.css b/scanimation/interfaces/backend/style.css deleted file mode 100644 index 771db0b..0000000 --- a/scanimation/interfaces/backend/style.css +++ /dev/null @@ -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; -} diff --git a/scanimation/interfaces/frames/.gitignore b/scanimation/interfaces/frames/.gitignore new file mode 100644 index 0000000..a5baada --- /dev/null +++ b/scanimation/interfaces/frames/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore + diff --git a/scanimation/interfaces/index.html b/scanimation/interfaces/index.html deleted file mode 100644 index e5ada97..0000000 --- a/scanimation/interfaces/index.html +++ /dev/null @@ -1,3 +0,0 @@ -worker specs
-frame animation
-backend diff --git a/scanimation/interfaces/worker_specs/dateformat.js b/scanimation/interfaces/worker_specs/dateformat.js deleted file mode 100644 index 466f649..0000000 --- a/scanimation/interfaces/worker_specs/dateformat.js +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Date Format 1.2.3 - * (c) 2007-2009 Steven Levithan - * MIT license - * - * Includes enhancements by Scott Trenda - * and 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); -}; diff --git a/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.eot b/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.eot deleted file mode 100644 index 63df3a7..0000000 Binary files a/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.eot and /dev/null differ diff --git a/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.otf b/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.otf deleted file mode 100644 index 4f5676c..0000000 Binary files a/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.otf and /dev/null differ diff --git a/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.ttf b/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.ttf deleted file mode 100644 index 76e22b8..0000000 Binary files a/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.ttf and /dev/null differ diff --git a/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.woff b/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.woff deleted file mode 100644 index 457f916..0000000 Binary files a/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.woff and /dev/null differ diff --git a/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.woff2 b/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.woff2 deleted file mode 100644 index b4099a9..0000000 Binary files a/scanimation/interfaces/worker_specs/font/BebasNeue-Regular.woff2 and /dev/null differ diff --git a/scanimation/interfaces/worker_specs/font/DroidSansFallbackFull.ttf b/scanimation/interfaces/worker_specs/font/DroidSansFallbackFull.ttf deleted file mode 100644 index 89959f5..0000000 Binary files a/scanimation/interfaces/worker_specs/font/DroidSansFallbackFull.ttf and /dev/null differ diff --git a/scanimation/interfaces/worker_specs/font/FreeSans.ttf b/scanimation/interfaces/worker_specs/font/FreeSans.ttf deleted file mode 100644 index 7d1be71..0000000 Binary files a/scanimation/interfaces/worker_specs/font/FreeSans.ttf and /dev/null differ diff --git a/scanimation/interfaces/worker_specs/index.html b/scanimation/interfaces/worker_specs/index.html deleted file mode 100644 index 5f8d9fe..0000000 --- a/scanimation/interfaces/worker_specs/index.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - -
- - - -
- human intelligent task id -   - - human worker id -   - ip address -   - location -   - browser -   - os -   - - task started at -   - task status -   - task fee -   - - - time elapsed -   -
- - - - - - - -
- - - - diff --git a/scanimation/interfaces/worker_specs/reconnecting-websocket.min.js b/scanimation/interfaces/worker_specs/reconnecting-websocket.min.js deleted file mode 100644 index 3015099..0000000 --- a/scanimation/interfaces/worker_specs/reconnecting-websocket.min.js +++ /dev/null @@ -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}); diff --git a/scanimation/interfaces/worker_specs/script.js b/scanimation/interfaces/worker_specs/script.js deleted file mode 100644 index 70da85d..0000000 --- a/scanimation/interfaces/worker_specs/script.js +++ /dev/null @@ -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()} diff --git a/scanimation/interfaces/worker_specs/style.css b/scanimation/interfaces/worker_specs/style.css deleted file mode 100644 index 8b97640..0000000 --- a/scanimation/interfaces/worker_specs/style.css +++ /dev/null @@ -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); -} diff --git a/sorteerhoed/central_management.py b/sorteerhoed/central_management.py index 4e16296..a57604a 100644 --- a/sorteerhoed/central_management.py +++ b/sorteerhoed/central_management.py @@ -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 = ''' @@ -324,8 +364,8 @@ class CentralManagement(): '''.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}") - - diff --git a/sorteerhoed/plotter.py b/sorteerhoed/plotter.py index 44a0707..0d24760 100644 --- a/sorteerhoed/plotter.py +++ b/sorteerhoed/plotter.py @@ -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") - diff --git a/test_drawing.svg b/test_drawing.svg new file mode 100644 index 0000000..d0d4ec5 --- /dev/null +++ b/test_drawing.svg @@ -0,0 +1,67 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/test_pen.py b/test_pen.py new file mode 100644 index 0000000..347c459 --- /dev/null +++ b/test_pen.py @@ -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() diff --git a/www/index.html b/www/index.html index 914de0a..b05e2e4 100644 --- a/www/index.html +++ b/www/index.html @@ -34,7 +34,7 @@ body.submitted path{ stroke:darkgray; } - + body.submitted .buttons{ display:none; } @@ -145,13 +145,13 @@
    -
  • Drag the mouse to trace the lines drawing above
  • -
  • Follow the lines as precise as possible, it is only this image to complete the HIT.
  • +
  • Drag the mouse to trace the lines above.
  • +
  • Try to be as precise as possible: there's only one image per HIT.
  • Press submit when you're done.
  • Please watch the clock! timing is strict because the tracing is live streamed to us. Unfortunately, due to high abandonment rates we have to keep the timer strict.
- +
@@ -178,7 +178,7 @@ var drawWidthFactor = drawWidth / svgWidth; var drawHeightFactor = drawHeight / svgHeight; - + {SCRIPT}