Compare commits

..

3 Commits

Author SHA1 Message Date
alban
dcdccb57e2 [fix] Adds a worker 2020-10-22 21:16:30 +02:00
alban
b31f725d04 [fix] Reworks structure and adds docker 2020-10-22 20:44:24 +02:00
alban
fd0974134b [enh] Fist working version 2020-10-21 14:39:48 +02:00
62 changed files with 344 additions and 14 deletions

0
.dockerignore Normal file
View File

3
.env.template Normal file
View File

@ -0,0 +1,3 @@
DB_HOST="127.0.0.1"
DB_PORT="6379"
DEBUG=1

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.env

29
Dockerfile Normal file
View File

@ -0,0 +1,29 @@
FROM python:3.8-slim
LABEL name=laser-app version=0.1
WORKDIR /opt
RUN apt update
RUN apt install -y --no-install-recommends build-essential\
gcc\
libagg2-dev\
libpotrace-dev\
nginx-light\
pkg-config\
python-dev\
redis-server
RUN rm -f /etc/nginx/sites-enabled/*
RUN pip3 install flask numpy pillow redis
RUN pip3 install pypotrace
COPY ./files/nginx/sites-enabled/site.conf /etc/nginx/sites-enabled
COPY . .
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
EXPOSE 80
EXPOSE 5000
EXPOSE 9001
# Start the main process.
CMD ["python", "./server.py", "-i", "db"]

43
docker-compose.yml Normal file
View File

@ -0,0 +1,43 @@
version: '2'
volumes:
laserrdb:
external: true
services:
db:
env_file: .env
image: redis:6-alpine
ports:
- "6379:6379"
volumes:
- laserrdb:/var/lib/redis
app:
env_file: .env
build: .
image: teamlaser/laser-app:latest
ports:
- "5000:5000"
depends_on:
- db
command: python ./server.py
assets:
env_file: .env
image: teamlaser/laser-app:latest
ports:
- "8080:80"
depends_on:
- app
command: nginx -g 'daemon off;error_log /dev/stdout info;'
worker:
env_file: .env
build: .
image: teamlaser/laser-app:latest
depends_on:
- db
command: bash -c "while true; do python ./worker.py; sleep 30; done"

5
entrypoint.sh Normal file
View File

@ -0,0 +1,5 @@
#!/bin/sh
set -e
exec "$@"

View File

@ -0,0 +1,13 @@
server {
server_name _;
listen 80 default_server;
listen [::]:80 default_server;
root /opt/;
location /image {
proxy_pass http://app:5000;
}
# Docker specific conf
resolver 127.0.0.11 ipv6=off;
access_log /dev/stdout;
}

View File

@ -12,12 +12,12 @@
#canvas { margin-top: 20px; border: 1px solid #ccc; display: block; } #canvas { margin-top: 20px; border: 1px solid #ccc; display: block; }
span.ui-slider-handle.ui-corner-all.ui-state-default { background: dodgerblue;} span.ui-slider-handle.ui-corner-all.ui-state-default { background: dodgerblue;}
</style> </style>
<link rel="stylesheet" type="text/css" href="bootstrap/css/bootstrap.min.css" /> <link rel="stylesheet" type="text/css" href="css/bootstrap/css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="jquery-ui/jquery-ui.css" /> <link rel="stylesheet" type="text/css" href="js/jquery-ui/jquery-ui.css" />
<script src="jquery-3.5.1.min.js"></script> <script src="js/jquery-3.5.1.min.js"></script>
<script src="jquery-ui/jquery-ui.js"></script> <script src="js/jquery-ui/jquery-ui.js"></script>
<script src="canvas.js"></script> <script src="js/canvas.js"></script>
</head> </head>
<body> <body>
<script> <script>
@ -36,7 +36,7 @@ window.ORIGINAL_JSON=window.JSON;
<video id="video" autoplay></video> <video id="video" autoplay></video>
</div> </div>
<div class="col-sm"> <div class="col-sm">
<button id="snap" class="btn btn-lg btn-primary">Take a picture!</button> <button class="snap btn btn-lg btn-primary">Take a picture!</button>
<p class="lead">By allowing the use of your camera, you will be able to take snapshots, convert them to black and white images before sending them to the laser server. How cool is that?!</p> <p class="lead">By allowing the use of your camera, you will be able to take snapshots, convert them to black and white images before sending them to the laser server. How cool is that?!</p>
</div> </div>
</div> </div>
@ -45,7 +45,7 @@ window.ORIGINAL_JSON=window.JSON;
<canvas id="canvas" width="640" height="480"></canvas> <canvas id="canvas" width="640" height="480"></canvas>
</div> </div>
<div class="col-sm"> <div class="col-sm">
<h3 class="btn btn-lg btn-primary">Not satisfied of the contrast? </h3> <h3 class="snap btn btn-lg btn-primary">Not satisfied of the contrast? </h3>
<br/> <br/>
<div id="slider"></div> <div id="slider"></div>
<br/> <br/>
@ -56,9 +56,12 @@ window.ORIGINAL_JSON=window.JSON;
<h3>Satisfied? </h3> <h3>Satisfied? </h3>
<p class="lead">Type the text visible on your image (if any) and push Send.</p> <p class="lead">Type the text visible on your image (if any) and push Send.</p>
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control form-control-lg" placeholder="Type text here"/> <input id="text" type="text" class="form-control form-control-lg" placeholder="Type text here"/>
<button id="send" class="form-control form-control-lg btn btn-lg btn-warning btn-block">Send</button> <button id="send" class="form-control form-control-lg btn btn-lg btn-warning btn-block">Send</button>
</div> </div>
<div id="messages">
</div>
</div> </div>
</div> </div>

View File

@ -1,10 +1,8 @@
$(document).ready(function(){ $(document).ready(function(){
console.log("ok")
var binary_level = 100 var binary_level = 100
var url = document.URL
// Grab elements, create settings, etc. // Grab elements, create settings, etc.
var canvas = document.getElementById('canvas'); var canvas = document.getElementById('canvas');
@ -19,6 +17,7 @@ console.log("ok")
context.drawImage(video, 0, 0, 640, 480); context.drawImage(video, 0, 0, 640, 480);
binary(context) binary(context)
$("#result").show() $("#result").show()
$('html, body').animate({scrollTop: $("#result").offset().top}, 2000);
} }
@ -62,7 +61,7 @@ console.log("ok")
} }
// Trigger photo take // Trigger photo take
document.getElementById('snap').addEventListener('click', function() { $('.snap').on('click', function() {
snap() snap()
}); });
function binary(context){ function binary(context){
@ -77,7 +76,6 @@ console.log("ok")
var thresh_red = binary_level; var thresh_red = binary_level;
var thresh_green = binary_level; var thresh_green = binary_level;
var thresh_blue = binary_level; var thresh_blue = binary_level;
console.log( binary_level )
var channels = image.data.length/4; var channels = image.data.length/4;
for(var i=0;i<channels;i++){ for(var i=0;i<channels;i++){
@ -110,6 +108,33 @@ console.log("ok")
} }
}); });
$("#send").on("click",function(){ $("#send").on("click",function(){
alert("Sorry, this is still a work in progress ¯\_(ツ)_/¯") var canvas = document.getElementById('canvas');
image_data = canvas.toDataURL();
$("#messages").html('<div class="alert alert-primary" role="alert">Sending...</div>')
$.ajax({
url: url + "/image",
method:"POST",
dataType: "json",
data:{
image_data : image_data,
text : $("#text").val()
},
}).done(function(d,m,j){
if( d.errors ){
msg = d.errors.join(" / ")
$("#messages").html(`<div class="alert alert-danger" role="alert">Something went wrong: ${msg}</div>`)
return
}
$("#messages").html(`<div class="alert alert-success" role="alert">OK, your image will be on ${d.hash_name}</div>`)
imageList = localStorage.getItem("imageList") ? JSON.parse(localStorage.getItem("imageList")) : []
imageList.push(d.hash_name)
localStorage.setItem("imageList",JSON.stringify(imageList))
})
.fail(function(d,m,jq){
$("#messages").html(`<div class="alert alert-danger" role="alert">Ooops... Something went wrong: ${jq}</div>`)
})
}) })
}) })

View File

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 262 B

View File

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 348 B

View File

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 207 B

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

72
manage.py Normal file
View File

@ -0,0 +1,72 @@
#! /usr/bin/python3
from datetime import datetime
from redis import Redis
import argparse
import json
import os
import sys
modeList = {
"list" : ["l","list"],
"delete" : ["d","delete"],
"exit" : ["x","q","exit"],
"preview" : ["p","preview"],
}
argsparser = argparse.ArgumentParser(description="manager")
argsparser.add_argument("-m","--mode",help="The action to perform. One of : {}".format(", ".join(modeList.keys())),default="list",type=str)
argsparser.add_argument("-P","--path",help="clitools path",default="~/code/lj-nano/clitools",type=str)
argsparser.add_argument("-i","--ip",help="IP address of the Redis server ",default="127.0.0.1",type=str)
argsparser.add_argument("-p","--port",help="Port of the Redis server ",default="6379",type=str)
args = argsparser.parse_args()
mode = args.mode
clipath = args.path
port = args.port
ip = args.ip
r = Redis(host=ip, port=port)
hashList = []
def refreshList():
global hashList
hashList = {}
theList = r.hgetall("images")
for i in theList:
item = json.loads(theList[i])
theList[i] = item
# Sort by reverse Date
for i in sorted(theList.items(),key=lambda x: x[1]['created_at'],reverse=True):
hashList[i[0].decode("ascii")] = i[1]
while True:
refreshList()
if mode in modeList["list"] :
print("hash\t\t\tdate\t\t\tpoints info")
for i in hashList:
item = hashList[i]
pl = item["points_list"]
created_at = item["created_at"]
print("{}\t{}\t{} points".format(i,datetime.ctime(datetime.fromtimestamp(created_at)),len(pl)))
elif mode in modeList["delete"]:
print("Select a hash to delete")
hash_name = input()
result = r.hdel("images",hash_name)
print("Result: {}".format(result))
elif mode in modeList["preview"]:
print("Select a hash to preview")
hash_name = input()
item = json.loads(hashList[bytes(hash_name,'ascii')])
points_list = json.dumps(item["points_list"])
bashCommand = "while true; do echo '"+points_list+"'; sleep 0.5; done | /usr/bin/python3 "+clipath+"/exports/tosimu.py"
os.system(bashCommand)
elif mode in modeList["exit"]:
break
print("Next action? (l)ist (d)elete e(x)it (p)review")
mode = input()

66
server.py Normal file
View File

@ -0,0 +1,66 @@
#! /usr/bin/python3
from redis import Redis
from flask import Flask, redirect, url_for, request
import flask
import hashlib
import json
import os
import time
# Redis init
environ = os.environ
host = environ['DB_HOST'] if 'DB_HOST' in os.environ else "localhost"
port = environ['DB_PORT'] if 'DB_PORT' in os.environ else 6379
debug = environ['DEBUG'] if 'DEBUG' in os.environ else False
sep='**************************************'
print("\n{}\nConnecting to Redis server on {}:{}\n{}\n".format(sep,host,port,sep))
r = Redis(host="db", port=port)
# hashlib init
m = hashlib.sha256()
# Flask app init
app = Flask(__name__)
@app.before_first_request
def setup_logging():
if not app.debug:
# In production mode, add log handler to sys.stdout.
app.logger.addHandler(logging.StreamHandler(stream=sys.stdout))
app.logger.setLevel(logging.INFO)
# Routing requests
@app.route('/image',methods = ['POST', 'GET'])
def image():
try:
data = dict()
# Clean the request
if "text" in request.form:
data["text"] = request.form["text"][:256]
else :
data["text"] = ""
if "image_data" in request.form:
data["image_data"] = request.form["image_data"]
else:
raise Exception("image_data is mandatory")
m.update( bytes(time.asctime(), 'utf-8'))
hash_name = m.hexdigest()[:16]
data["hash_name"] = hash_name
# Save to queue
r.rpush("image-convert",json.dumps(data))
return( json.dumps({
"message":"ok",
"hash_name":hash_name
}) )
except Exception as e:
print("woah",e)
return( json.dumps( {"errors":[ str(e)]}))
# Run Flask
if __name__ == '__main__':
app.run(host= '0.0.0.0',debug = True)

70
worker.py Normal file
View File

@ -0,0 +1,70 @@
#! /usr/bin/python3
from io import BytesIO
from numpy import asarray
from PIL import Image
from redis import Redis
import base64
import json
import os
import potrace
import re
import time
color = 65280
environ = os.environ
host = environ['DB_HOST'] if 'DB_HOST' in os.environ else "localhost"
port = environ['DB_PORT'] if 'DB_PORT' in os.environ else 6379
r = Redis(host="db", port=port)
def convertImg(src, image_path="/tmp/"):
base64_data = re.sub('^data:image/.+;base64,', '', src)
byte_data = base64.b64decode(base64_data)
image_data = BytesIO(byte_data)
img = Image.open(image_data).convert("1")
return img
# Read results from redis
data = r.lpop('image-convert')
while data :
try:
item = json.loads(data)
image_data = item["image_data"]
text = item["text"]
hash_name = item["hash_name"]
# Vectorize the image
image = convertImg( image_data )
bmp = potrace.Bitmap( asarray( image ) )
# Trace the bitmap to a path
path = bmp.trace(turdsize=16,alphamax=0.0, opticurve=0, opttolerance=1.0)
pl = []
for curve in path:
start = curve.start_point
pl.append([int(start[0]),int(start[1]),0])
pl.append([int(start[0]),int(start[1]),color])
for segment in curve:
end_point_x, end_point_y = segment.end_point
if segment.is_corner:
c_x, c_y = segment.c
pl.append([int(c_x),int(c_y),color])
pass
#
else:
c1_x, c1_y = segment.c1
x, y = segment.c2
pl.append([int(x),int(y),color])
pl.append([int(c1_x),int(c1_y),color])
#
pl.append([start[0],start[1],0])
item["points_list"] = pl
item["created_at"] = time.time()
r.hset("images",hash_name, json.dumps(item))
print("Handled image {}".format(hash_name))
data = r.lpop('image-convert')
except Exception as e:
print("woops",e)
break