diff --git a/config.yml b/config.yml index edbe538..40670b1 100644 --- a/config.yml +++ b/config.yml @@ -3,11 +3,25 @@ amazon: user_secret: "213j234/234sksjdfus83jd" mturk_sandbox: true mturk_region: us-east-1 + sqs_endpoint_url: "https://sqs.eu-west-3.amazonaws.com/" sqs_url: "https://sqs.eu-west-3.amazonaws.com/60123456789/your_queue" sqs_region_name: "eu-west-3" task_xml: "mt_task.xml" hit_db: store.db hour_rate_aim: 15 -hit_lifetime: 54000 ;15*60*60 -hit_assignment_duration: 300 ; 5*60 -hit_autoapprove_delay: 3600 \ No newline at end of file +hit_lifetime: 54000 #15*60*60 +hit_assignment_duration: 300 # 5*60 +hit_autoapprove_delay: 3600 +dummy_plotter: false +server: + port: 8888 +scanner: # size of scanarea in mm + # total visible glass (and size of the scan) + width: 255 + height: 185 + # area which can be removed + draw_width: 255 + draw_height: 70 + # part of scanner that is invissible left & top + left_padding: 0 + top_padding: 45 diff --git a/sorteerhoed/HITStore.py b/sorteerhoed/HITStore.py index 5ed18be..8769c9a 100644 --- a/sorteerhoed/HITStore.py +++ b/sorteerhoed/HITStore.py @@ -61,7 +61,7 @@ class HIT(Base): return os.path.join('www', self.getImageUrl()) def getImageUrl(self): - return f"scans/{self.id}.png" + return f"scans/{self.id}.jpg" def getStatus(self): if self.scanned_at: diff --git a/sorteerhoed/__pycache__/HITStore.cpython-37.pyc b/sorteerhoed/__pycache__/HITStore.cpython-37.pyc new file mode 100644 index 0000000..907510f Binary files /dev/null and b/sorteerhoed/__pycache__/HITStore.cpython-37.pyc differ diff --git a/sorteerhoed/__pycache__/Signal.cpython-37.pyc b/sorteerhoed/__pycache__/Signal.cpython-37.pyc new file mode 100644 index 0000000..1d0f9e4 Binary files /dev/null and b/sorteerhoed/__pycache__/Signal.cpython-37.pyc differ diff --git a/sorteerhoed/__pycache__/__init__.cpython-37.pyc b/sorteerhoed/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..834b2cf Binary files /dev/null and b/sorteerhoed/__pycache__/__init__.cpython-37.pyc differ diff --git a/sorteerhoed/__pycache__/central_management.cpython-37.pyc b/sorteerhoed/__pycache__/central_management.cpython-37.pyc new file mode 100644 index 0000000..b9c0ec2 Binary files /dev/null and b/sorteerhoed/__pycache__/central_management.cpython-37.pyc differ diff --git a/sorteerhoed/__pycache__/plotter.cpython-37.pyc b/sorteerhoed/__pycache__/plotter.cpython-37.pyc new file mode 100644 index 0000000..3c3c088 Binary files /dev/null and b/sorteerhoed/__pycache__/plotter.cpython-37.pyc differ diff --git a/sorteerhoed/__pycache__/sqs.cpython-37.pyc b/sorteerhoed/__pycache__/sqs.cpython-37.pyc new file mode 100644 index 0000000..3c4cf41 Binary files /dev/null and b/sorteerhoed/__pycache__/sqs.cpython-37.pyc differ diff --git a/sorteerhoed/__pycache__/webserver.cpython-37.pyc b/sorteerhoed/__pycache__/webserver.cpython-37.pyc new file mode 100644 index 0000000..8961a9a Binary files /dev/null and b/sorteerhoed/__pycache__/webserver.cpython-37.pyc differ diff --git a/sorteerhoed/central_management.py b/sorteerhoed/central_management.py index 18ea8d8..8017d9d 100644 --- a/sorteerhoed/central_management.py +++ b/sorteerhoed/central_management.py @@ -300,8 +300,11 @@ class CentralManagement(): Run scanimage on scaner and returns a string with the filename """ self.eventQueue.put(Signal('scan.start')) + cmd = [ - 'sudo', 'scanimage', '-d', 'epkowa' + 'sudo', 'scanimage', '-d', 'epkowa', '--format', 'jpeg', + '--resolution=100', '-l','20','-t','30','-x',(self.config['scanner']['height']), + '-y',str(self.config['scanner']['width']) ] filename = self.currentHit.getImagePath() proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -313,6 +316,7 @@ class CentralManagement(): f = io.BytesIO(o) img = Image.open(f) + img = img.transpose(Image.ROTATE_90) img.save(filename) self.eventQueue.put(Signal('hit.scanned', {'hit_id':self.currentHit.id})) diff --git a/sorteerhoed/plotter.py b/sorteerhoed/plotter.py index 0e63533..03ea3ce 100644 --- a/sorteerhoed/plotter.py +++ b/sorteerhoed/plotter.py @@ -6,17 +6,6 @@ from threading import Event from sorteerhoed.Signal import Signal import time -class PathSegment: - def __init__(self): - self.d = [] - - def add(self, i): -# self.d.append(i) - - def get(self, prop): - if prop =='d': - return self.d - class Plotter: def __init__(self, config, eventQ: Queue, runningEvent: Event): #TODO: scanningEvent -> CentralManagement.isScanning -> prevent plotter move during scan, failsafe @@ -25,6 +14,13 @@ class Plotter: self.q = Queue() 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; + self.yPadding = self.config['scanner']['top_padding'] / 10 / 2.54; + self.logger.info(f"Paddings x: {self.xPadding} inch y: {self.yPadding} inch") def park(self): self.logger.info("Queue to park plotter") @@ -37,69 +33,118 @@ class Plotter: self.q.put([0,0,0]) def start(self): - self.axiDrawCueListener() - - def axiDrawCueListener(self): - if self.config['dummy_plotter']: - while self.isRunning.is_set(): - # TODO: queue that collects a part of the path data - # on a specific limit or on a specific time interval, do the plot - # also, changing ad.pen_raise() or ad.pen_lower() trigger a new segment - # Plot with ad.plan_trajectory() ?? - plotterRan = False - try: - move = self.q.get(True, 1) - plotterRan = True - except queue.Empty as e: - self.logger.log(5, "Empty queue.") - if plotterRan: - plotterRan = False - self.eventQ.put(Signal('plotter.finished')) - else: - time.sleep(.05) - self.logger.debug(f'Dummy plotter move: {move}') - self.logger.info("Stopping dummy plotter") - else: - self.ad = axidraw.AxiDraw() - - self.ad.interactive() -# self.ad.plot_path() - - connected = self.ad.connect() - if not connected: - raise Exception("Cannot connect to Axidraw") - try: + 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 # A3, set to 1 for A4 + self.ad.options.model = 1 # 2 for A3, 1 for A4 self.ad.moveto(0,0) - plotterWidth = 22 - plotterHeight = 18 # 16? +# plotterWidth = 25 +# plotterHeight = 21 - plotterRan = False - while self.isRunning.is_set(): - # TODO: set timeout on .get() with catch block, so we can escape if no moves come in - try: - move = self.q.get(True, 1) - plotterRan = True - except queue.Empty as e: - self.logger.log(5, "Empty queue.") - if plotterRan: - plotterRan = False - self.eventQ.put(Signal('plotter.finished')) - else: - self.ad.moveto(move[0]* plotterWidth, move[1]*plotterHeight) - self.logger.debug(f'handler! {move}') - except Exception as e: - self.logger.exception(e) - finally: - self.logger.warning("Close Axidraw connection") + else: + self.ad = None + self.axiDrawCueListener() + except Exception as e: + self.logger.exception(e) + finally: + self.logger.warning("Close Axidraw connection") + if self.ad: self.ad.moveto(0,0) self.ad.disconnect() + + # send shutdown signal (if not already set) + self.isRunning.clear() + + def draw_segments(self, segments = []): + coordinates = [] + for segment in segments: + coordinate = [ + # mm to cm to inches + (segment[0]) * self.plotWidth, + (1-segment[1]) * self.plotHeight + ] + #prevent drawing when not in drwaing area + # this is a failsafe for a malicious working or glitching script, as this should also be done in the javascript + if self.pen_down: + 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 + + coordinates.append(coordinate) + self.logger.info(f"Plot: {coordinates}") + if self.ad: + self.ad.plan_trajectory(coordinates) +# 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 + """ + if pen_state != self.pen_down: + self.pen_down = pen_state + self.logger.info("Changed pen: {}".format('down' if pen_state else 'up')) + if self.ad: + if pen_state: + self.ad.pen_lower() + else: + self.ad.pen_raise() + return True + return False + + def axiDrawCueListener(self): + plotterRan = False + segments = [] + while self.isRunning.is_set(): + # TODO: queue that collects a part of the path data + # on a specific limit or on a specific time interval, do the plot + # also, changing ad.pen_raise() or ad.pen_lower() trigger a new segment + # Plot with ad.plan_trajectory() ?? + try: + # if no info comes in for .5sec, plot that segment + segment = self.q.get(True, .5) - # send shutdown signal (if not already set) - self.isRunning.clear() \ No newline at end of file + # 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) > 1: + 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): + # segments to plot! + self.draw_segments(segments) + plotterRan = True + segments = [] + elif plotterRan: + plotterRan = False + self.eventQ.put(Signal('plotter.finished')) +# else: +# time.sleep(.05) +# self.logger.debug(f'Plotter move: {move}') + self.logger.info("Stopping plotter") + diff --git a/sorteerhoed/webserver.py b/sorteerhoed/webserver.py index 837d5d4..185274a 100644 --- a/sorteerhoed/webserver.py +++ b/sorteerhoed/webserver.py @@ -233,9 +233,15 @@ def strokes2D(strokes): return d; class DrawPageHandler(tornado.web.RequestHandler): - def initialize(self, store: HITStore, path: str): + def initialize(self, store: HITStore, path: str, width: int, height: int, draw_width: int, draw_height: int, top_padding: int, left_padding: int): self.store = store self.path = path + self.width = width + self.height = height + self.draw_width = draw_width + self.draw_height = draw_height + self.top_padding = top_padding + self.left_padding = left_padding def get(self): try: @@ -258,7 +264,14 @@ class DrawPageHandler(tornado.web.RequestHandler): logger.info(f"Image url: {image}") self.set_header("Access-Control-Allow-Origin", "*") - contents = open(os.path.join(self.path, 'index.html'), 'r').read().replace("{IMAGE_URL}", image) + contents = open(os.path.join(self.path, 'index.html'), 'r').read() + contents = contents.replace("{IMAGE_URL}", image)\ + .replace("{WIDTH}", str(self.width))\ + .replace("{HEIGHT}", str(self.height))\ + .replace("{DRAW_WIDTH}", str(self.draw_width))\ + .replace("{DRAW_HEIGHT}", str(self.draw_height))\ + .replace("{TOP_PADDING}", str(self.left_padding))\ + .replace("{LEFT_PADDING}", str(self.top_padding)) self.write(contents) class StatusPage(): @@ -342,7 +355,16 @@ class Server: }), (r"/status/ws", StatusWebSocketHandler), (r"/draw", DrawPageHandler, - dict(store = self.store, path=self.web_root)), + dict( + store = self.store, + path=self.web_root, + width=self.config['scanner']['width'], + height=self.config['scanner']['height'], + draw_width=self.config['scanner']['draw_width'], + draw_height=self.config['scanner']['draw_height'], + top_padding=self.config['scanner']['top_padding'], + left_padding=self.config['scanner']['left_padding'] + )), (r"/(.*)", StaticFileWithHeaderHandler, {"path": self.web_root}), ], debug=True, autoreload=False) diff --git a/www/index.html b/www/index.html index c9e183b..526e538 100644 --- a/www/index.html +++ b/www/index.html @@ -12,6 +12,7 @@ right:0; width:100%; height:100%; + font-family: sans-serif; } path { @@ -19,7 +20,6 @@ stroke: red; stroke-width: 2px; } - body.submitted path{ stroke:darkgray; } @@ -28,38 +28,121 @@ display:none; } - #wrapper { - height: 600px; - width: 600px; - position: relative; - background:#ccc; + /*#wrapper { + height: calc({DRAW_HEIGHT}/{HEIGHT} * 100%); + width: calc({DRAW_WIDTH}/{WIDTH} * 100%); + position: absolute; + left: calc(({WIDTH} - {DRAW_WIDTH})/2/{WIDTH} * 100%); + top: calc(({HEIGHT} - {DRAW_HEIGHT})/2/{HEIGHT} * 100%); + background:none; cursor: url(cursor.png) 6 6, auto; + }*/ + #wrapper { + position:absolute; + top:0; + right:0; + bottom:0; + left:0; + background:none; + cursor: url(cursor.png) 6 6, auto; + } + .gray{ + position:absolute; + background:rgba(255,255,255,0.7); + } + #gray_top{ + left:0; + right:0; + top:0; + height:calc({TOP_PADDING}/{HEIGHT} * 100%); + } + #gray_bottom{ + left:0; + right:0; + bottom:0; + height:calc(({HEIGHT} - {DRAW_HEIGHT} - {TOP_PADDING})/{HEIGHT} * 100%); + } + #gray_left{ + left:0; + top:calc({TOP_PADDING}/{HEIGHT} * 100%); + height: calc({DRAW_HEIGHT}/{HEIGHT} * 100%); + width: calc({LEFT_PADDING}/{WIDTH} * 100%); + } + #gray_right{ + right:0; + top:calc({TOP_PADDING}/{HEIGHT} * 100%); + height: calc({DRAW_HEIGHT}/{HEIGHT} * 100%); + width: calc(({WIDTH} - {DRAW_WIDTH} - {LEFT_PADDING})/{WIDTH} * 100%); } html, body{ height: 100%; width: 100%; + margin:0; + background:gray; } + #interface{ + background:white; + height: 0; + overflow: hidden; + padding-top: calc({HEIGHT}/{WIDTH} * 100%); + background: white; + position: relative; + margin: 0 auto; + background-size: 100% 100%; + } + #innerface{ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + @media (min-aspect-ratio: {WIDTH}/{HEIGHT}) { + #interface { + height: 100vh; + width: calc({WIDTH}/{HEIGHT} * 100vh); + padding-top:0; + } + } + #info{ + position: absolute; + bottom: 5px; + width: 600px; + left: calc(50% - 250px); + z-index: 999; + } + .buttons{ + text-align: center; + } - -
-
- - - - -
-
- - -
-
+
+
+
+ + + + +
+
+
    +
  • Drag the mouse to trace the clearest lines drawing above
  • +
  • Follow the lines as precise as possible
  • +
  • Press submit when you're done.
  • +
  • You'll receive a submission token, to fill in at Mechanical Turk
  • +
+
+ + +
+
+
+
+
+
+
+