`
-
-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 @@
+
+
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.